processing_options.go 26 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178
  1. package main
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "errors"
  6. "fmt"
  7. "net/http"
  8. "net/url"
  9. "regexp"
  10. "strconv"
  11. "strings"
  12. "sync"
  13. "github.com/imgproxy/imgproxy/v2/structdiff"
  14. )
  15. type urlOption struct {
  16. Name string
  17. Args []string
  18. }
  19. type urlOptions []urlOption
  20. type processingHeaders struct {
  21. Accept string
  22. Width string
  23. ViewportWidth string
  24. DPR string
  25. }
  26. type gravityType int
  27. const (
  28. gravityUnknown gravityType = iota
  29. gravityCenter
  30. gravityNorth
  31. gravityEast
  32. gravitySouth
  33. gravityWest
  34. gravityNorthWest
  35. gravityNorthEast
  36. gravitySouthWest
  37. gravitySouthEast
  38. gravitySmart
  39. gravityFocusPoint
  40. )
  41. var gravityTypes = map[string]gravityType{
  42. "ce": gravityCenter,
  43. "no": gravityNorth,
  44. "ea": gravityEast,
  45. "so": gravitySouth,
  46. "we": gravityWest,
  47. "nowe": gravityNorthWest,
  48. "noea": gravityNorthEast,
  49. "sowe": gravitySouthWest,
  50. "soea": gravitySouthEast,
  51. "sm": gravitySmart,
  52. "fp": gravityFocusPoint,
  53. }
  54. type resizeType int
  55. const (
  56. resizeFit resizeType = iota
  57. resizeFill
  58. resizeAuto
  59. )
  60. var resizeTypes = map[string]resizeType{
  61. "fit": resizeFit,
  62. "fill": resizeFill,
  63. "auto": resizeAuto,
  64. }
  65. type rgbColor struct{ R, G, B uint8 }
  66. var hexColorRegex = regexp.MustCompile("^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")
  67. const (
  68. hexColorLongFormat = "%02x%02x%02x"
  69. hexColorShortFormat = "%1x%1x%1x"
  70. )
  71. type gravityOptions struct {
  72. Type gravityType
  73. X, Y float64
  74. }
  75. type extendOptions struct {
  76. Enabled bool
  77. Gravity gravityOptions
  78. }
  79. type cropOptions struct {
  80. Width float64
  81. Height float64
  82. Gravity gravityOptions
  83. }
  84. type paddingOptions struct {
  85. Enabled bool
  86. Top int
  87. Right int
  88. Bottom int
  89. Left int
  90. }
  91. type trimOptions struct {
  92. Enabled bool
  93. Threshold float64
  94. Smart bool
  95. Color rgbColor
  96. EqualHor bool
  97. EqualVer bool
  98. }
  99. type watermarkOptions struct {
  100. Enabled bool
  101. Opacity float64
  102. Replicate bool
  103. Gravity gravityOptions
  104. Scale float64
  105. }
  106. type processingOptions struct {
  107. ResizingType resizeType
  108. Width int
  109. Height int
  110. Dpr float64
  111. Gravity gravityOptions
  112. Enlarge bool
  113. Extend extendOptions
  114. Crop cropOptions
  115. Padding paddingOptions
  116. Trim trimOptions
  117. Rotate int
  118. Format imageType
  119. Quality int
  120. MaxBytes int
  121. Flatten bool
  122. Background rgbColor
  123. Blur float32
  124. Sharpen float32
  125. StripMetadata bool
  126. StripColorProfile bool
  127. AutoRotate bool
  128. CacheBuster string
  129. Watermark watermarkOptions
  130. PreferWebP bool
  131. EnforceWebP bool
  132. PreferAvif bool
  133. EnforceAvif bool
  134. Filename string
  135. UsedPresets []string
  136. }
  137. const (
  138. imageURLCtxKey = ctxKey("imageUrl")
  139. processingOptionsCtxKey = ctxKey("processingOptions")
  140. urlTokenPlain = "plain"
  141. maxClientHintDPR = 8
  142. msgForbidden = "Forbidden"
  143. msgInvalidURL = "Invalid URL"
  144. msgInvalidSource = "Invalid Source"
  145. )
  146. func (gt gravityType) String() string {
  147. for k, v := range gravityTypes {
  148. if v == gt {
  149. return k
  150. }
  151. }
  152. return ""
  153. }
  154. func (gt gravityType) MarshalJSON() ([]byte, error) {
  155. for k, v := range gravityTypes {
  156. if v == gt {
  157. return []byte(fmt.Sprintf("%q", k)), nil
  158. }
  159. }
  160. return []byte("null"), nil
  161. }
  162. func (rt resizeType) String() string {
  163. for k, v := range resizeTypes {
  164. if v == rt {
  165. return k
  166. }
  167. }
  168. return ""
  169. }
  170. func (rt resizeType) MarshalJSON() ([]byte, error) {
  171. for k, v := range resizeTypes {
  172. if v == rt {
  173. return []byte(fmt.Sprintf("%q", k)), nil
  174. }
  175. }
  176. return []byte("null"), nil
  177. }
  178. var (
  179. _newProcessingOptions processingOptions
  180. newProcessingOptionsOnce sync.Once
  181. )
  182. func newProcessingOptions() *processingOptions {
  183. newProcessingOptionsOnce.Do(func() {
  184. _newProcessingOptions = processingOptions{
  185. ResizingType: resizeFit,
  186. Width: 0,
  187. Height: 0,
  188. Gravity: gravityOptions{Type: gravityCenter},
  189. Enlarge: false,
  190. Extend: extendOptions{Enabled: false, Gravity: gravityOptions{Type: gravityCenter}},
  191. Padding: paddingOptions{Enabled: false},
  192. Trim: trimOptions{Enabled: false, Threshold: 10, Smart: true},
  193. Rotate: 0,
  194. Quality: 0,
  195. MaxBytes: 0,
  196. Format: imageTypeUnknown,
  197. Background: rgbColor{255, 255, 255},
  198. Blur: 0,
  199. Sharpen: 0,
  200. Dpr: 1,
  201. Watermark: watermarkOptions{Opacity: 1, Replicate: false, Gravity: gravityOptions{Type: gravityCenter}},
  202. StripMetadata: conf.StripMetadata,
  203. StripColorProfile: conf.StripColorProfile,
  204. AutoRotate: conf.AutoRotate,
  205. }
  206. })
  207. po := _newProcessingOptions
  208. po.UsedPresets = make([]string, 0, len(conf.Presets))
  209. return &po
  210. }
  211. func (po *processingOptions) getQuality() int {
  212. q := po.Quality
  213. if q == 0 {
  214. q = conf.FormatQuality[po.Format]
  215. }
  216. if q == 0 {
  217. q = conf.Quality
  218. }
  219. return q
  220. }
  221. func (po *processingOptions) isPresetUsed(name string) bool {
  222. for _, usedName := range po.UsedPresets {
  223. if usedName == name {
  224. return true
  225. }
  226. }
  227. return false
  228. }
  229. func (po *processingOptions) presetUsed(name string) {
  230. po.UsedPresets = append(po.UsedPresets, name)
  231. }
  232. func (po *processingOptions) Diff() structdiff.Entries {
  233. return structdiff.Diff(newProcessingOptions(), po)
  234. }
  235. func (po *processingOptions) String() string {
  236. return po.Diff().String()
  237. }
  238. func (po *processingOptions) MarshalJSON() ([]byte, error) {
  239. return po.Diff().MarshalJSON()
  240. }
  241. func colorFromHex(hexcolor string) (rgbColor, error) {
  242. c := rgbColor{}
  243. if !hexColorRegex.MatchString(hexcolor) {
  244. return c, fmt.Errorf("Invalid hex color: %s", hexcolor)
  245. }
  246. if len(hexcolor) == 3 {
  247. fmt.Sscanf(hexcolor, hexColorShortFormat, &c.R, &c.G, &c.B)
  248. c.R *= 17
  249. c.G *= 17
  250. c.B *= 17
  251. } else {
  252. fmt.Sscanf(hexcolor, hexColorLongFormat, &c.R, &c.G, &c.B)
  253. }
  254. return c, nil
  255. }
  256. func decodeBase64URL(parts []string) (string, string, error) {
  257. var format string
  258. encoded := strings.Join(parts, "")
  259. urlParts := strings.Split(encoded, ".")
  260. if len(urlParts[0]) == 0 {
  261. return "", "", errors.New("Image URL is empty")
  262. }
  263. if len(urlParts) > 2 {
  264. return "", "", fmt.Errorf("Multiple formats are specified: %s", encoded)
  265. }
  266. if len(urlParts) == 2 && len(urlParts[1]) > 0 {
  267. format = urlParts[1]
  268. }
  269. imageURL, err := base64.RawURLEncoding.DecodeString(strings.TrimRight(urlParts[0], "="))
  270. if err != nil {
  271. return "", "", fmt.Errorf("Invalid url encoding: %s", encoded)
  272. }
  273. fullURL := fmt.Sprintf("%s%s", conf.BaseURL, string(imageURL))
  274. return fullURL, format, nil
  275. }
  276. func decodePlainURL(parts []string) (string, string, error) {
  277. var format string
  278. encoded := strings.Join(parts, "/")
  279. urlParts := strings.Split(encoded, "@")
  280. if len(urlParts[0]) == 0 {
  281. return "", "", errors.New("Image URL is empty")
  282. }
  283. if len(urlParts) > 2 {
  284. return "", "", fmt.Errorf("Multiple formats are specified: %s", encoded)
  285. }
  286. if len(urlParts) == 2 && len(urlParts[1]) > 0 {
  287. format = urlParts[1]
  288. }
  289. unescaped, err := url.PathUnescape(urlParts[0])
  290. if err != nil {
  291. return "", "", fmt.Errorf("Invalid url encoding: %s", encoded)
  292. }
  293. fullURL := fmt.Sprintf("%s%s", conf.BaseURL, unescaped)
  294. return fullURL, format, nil
  295. }
  296. func decodeURL(parts []string) (string, string, error) {
  297. if len(parts) == 0 {
  298. return "", "", errors.New("Image URL is empty")
  299. }
  300. if parts[0] == urlTokenPlain && len(parts) > 1 {
  301. return decodePlainURL(parts[1:])
  302. }
  303. return decodeBase64URL(parts)
  304. }
  305. func parseDimension(d *int, name, arg string) error {
  306. if v, err := strconv.Atoi(arg); err == nil && v >= 0 {
  307. *d = v
  308. } else {
  309. return fmt.Errorf("Invalid %s: %s", name, arg)
  310. }
  311. return nil
  312. }
  313. func parseBoolOption(str string) bool {
  314. b, err := strconv.ParseBool(str)
  315. if err != nil {
  316. logWarning("`%s` is not a valid boolean value. Treated as false", str)
  317. }
  318. return b
  319. }
  320. func isGravityOffcetValid(gravity gravityType, offset float64) bool {
  321. if gravity == gravityCenter {
  322. return true
  323. }
  324. return offset >= 0 && (gravity != gravityFocusPoint || offset <= 1)
  325. }
  326. func parseGravity(g *gravityOptions, args []string) error {
  327. nArgs := len(args)
  328. if nArgs > 3 {
  329. return fmt.Errorf("Invalid gravity arguments: %v", args)
  330. }
  331. if t, ok := gravityTypes[args[0]]; ok {
  332. g.Type = t
  333. } else {
  334. return fmt.Errorf("Invalid gravity: %s", args[0])
  335. }
  336. if g.Type == gravitySmart && nArgs > 1 {
  337. return fmt.Errorf("Invalid gravity arguments: %v", args)
  338. } else if g.Type == gravityFocusPoint && nArgs != 3 {
  339. return fmt.Errorf("Invalid gravity arguments: %v", args)
  340. }
  341. if nArgs > 1 {
  342. if x, err := strconv.ParseFloat(args[1], 64); err == nil && isGravityOffcetValid(g.Type, x) {
  343. g.X = x
  344. } else {
  345. return fmt.Errorf("Invalid gravity X: %s", args[1])
  346. }
  347. }
  348. if nArgs > 2 {
  349. if y, err := strconv.ParseFloat(args[2], 64); err == nil && isGravityOffcetValid(g.Type, y) {
  350. g.Y = y
  351. } else {
  352. return fmt.Errorf("Invalid gravity Y: %s", args[2])
  353. }
  354. }
  355. return nil
  356. }
  357. func applyWidthOption(po *processingOptions, args []string) error {
  358. if len(args) > 1 {
  359. return fmt.Errorf("Invalid width arguments: %v", args)
  360. }
  361. return parseDimension(&po.Width, "width", args[0])
  362. }
  363. func applyHeightOption(po *processingOptions, args []string) error {
  364. if len(args) > 1 {
  365. return fmt.Errorf("Invalid height arguments: %v", args)
  366. }
  367. return parseDimension(&po.Height, "height", args[0])
  368. }
  369. func applyEnlargeOption(po *processingOptions, args []string) error {
  370. if len(args) > 1 {
  371. return fmt.Errorf("Invalid enlarge arguments: %v", args)
  372. }
  373. po.Enlarge = parseBoolOption(args[0])
  374. return nil
  375. }
  376. func applyExtendOption(po *processingOptions, args []string) error {
  377. if len(args) > 4 {
  378. return fmt.Errorf("Invalid extend arguments: %v", args)
  379. }
  380. po.Extend.Enabled = parseBoolOption(args[0])
  381. if len(args) > 1 {
  382. if err := parseGravity(&po.Extend.Gravity, args[1:]); err != nil {
  383. return err
  384. }
  385. if po.Extend.Gravity.Type == gravitySmart {
  386. return errors.New("extend doesn't support smart gravity")
  387. }
  388. }
  389. return nil
  390. }
  391. func applySizeOption(po *processingOptions, args []string) (err error) {
  392. if len(args) > 7 {
  393. return fmt.Errorf("Invalid size arguments: %v", args)
  394. }
  395. if len(args) >= 1 && len(args[0]) > 0 {
  396. if err = applyWidthOption(po, args[0:1]); err != nil {
  397. return
  398. }
  399. }
  400. if len(args) >= 2 && len(args[1]) > 0 {
  401. if err = applyHeightOption(po, args[1:2]); err != nil {
  402. return
  403. }
  404. }
  405. if len(args) >= 3 && len(args[2]) > 0 {
  406. if err = applyEnlargeOption(po, args[2:3]); err != nil {
  407. return
  408. }
  409. }
  410. if len(args) >= 4 && len(args[3]) > 0 {
  411. if err = applyExtendOption(po, args[3:]); err != nil {
  412. return
  413. }
  414. }
  415. return nil
  416. }
  417. func applyResizingTypeOption(po *processingOptions, args []string) error {
  418. if len(args) > 1 {
  419. return fmt.Errorf("Invalid resizing type arguments: %v", args)
  420. }
  421. if r, ok := resizeTypes[args[0]]; ok {
  422. po.ResizingType = r
  423. } else {
  424. return fmt.Errorf("Invalid resize type: %s", args[0])
  425. }
  426. return nil
  427. }
  428. func applyResizeOption(po *processingOptions, args []string) error {
  429. if len(args) > 8 {
  430. return fmt.Errorf("Invalid resize arguments: %v", args)
  431. }
  432. if len(args[0]) > 0 {
  433. if err := applyResizingTypeOption(po, args[0:1]); err != nil {
  434. return err
  435. }
  436. }
  437. if len(args) > 1 {
  438. if err := applySizeOption(po, args[1:]); err != nil {
  439. return err
  440. }
  441. }
  442. return nil
  443. }
  444. func applyDprOption(po *processingOptions, args []string) error {
  445. if len(args) > 1 {
  446. return fmt.Errorf("Invalid dpr arguments: %v", args)
  447. }
  448. if d, err := strconv.ParseFloat(args[0], 64); err == nil && d > 0 {
  449. po.Dpr = d
  450. } else {
  451. return fmt.Errorf("Invalid dpr: %s", args[0])
  452. }
  453. return nil
  454. }
  455. func applyGravityOption(po *processingOptions, args []string) error {
  456. return parseGravity(&po.Gravity, args)
  457. }
  458. func applyCropOption(po *processingOptions, args []string) error {
  459. if len(args) > 5 {
  460. return fmt.Errorf("Invalid crop arguments: %v", args)
  461. }
  462. if w, err := strconv.ParseFloat(args[0], 64); err == nil && w >= 0 {
  463. po.Crop.Width = w
  464. } else {
  465. return fmt.Errorf("Invalid crop width: %s", args[0])
  466. }
  467. if len(args) > 1 {
  468. if h, err := strconv.ParseFloat(args[1], 64); err == nil && h >= 0 {
  469. po.Crop.Height = h
  470. } else {
  471. return fmt.Errorf("Invalid crop height: %s", args[1])
  472. }
  473. }
  474. if len(args) > 2 {
  475. return parseGravity(&po.Crop.Gravity, args[2:])
  476. }
  477. return nil
  478. }
  479. func applyPaddingOption(po *processingOptions, args []string) error {
  480. nArgs := len(args)
  481. if nArgs < 1 || nArgs > 4 {
  482. return fmt.Errorf("Invalid padding arguments: %v", args)
  483. }
  484. po.Padding.Enabled = true
  485. if nArgs > 0 && len(args[0]) > 0 {
  486. if err := parseDimension(&po.Padding.Top, "padding top (+all)", args[0]); err != nil {
  487. return err
  488. }
  489. po.Padding.Right = po.Padding.Top
  490. po.Padding.Bottom = po.Padding.Top
  491. po.Padding.Left = po.Padding.Top
  492. }
  493. if nArgs > 1 && len(args[1]) > 0 {
  494. if err := parseDimension(&po.Padding.Right, "padding right (+left)", args[1]); err != nil {
  495. return err
  496. }
  497. po.Padding.Left = po.Padding.Right
  498. }
  499. if nArgs > 2 && len(args[2]) > 0 {
  500. if err := parseDimension(&po.Padding.Bottom, "padding bottom", args[2]); err != nil {
  501. return err
  502. }
  503. }
  504. if nArgs > 3 && len(args[3]) > 0 {
  505. if err := parseDimension(&po.Padding.Left, "padding left", args[3]); err != nil {
  506. return err
  507. }
  508. }
  509. if po.Padding.Top == 0 && po.Padding.Right == 0 && po.Padding.Bottom == 0 && po.Padding.Left == 0 {
  510. po.Padding.Enabled = false
  511. }
  512. return nil
  513. }
  514. func applyTrimOption(po *processingOptions, args []string) error {
  515. nArgs := len(args)
  516. if nArgs > 4 {
  517. return fmt.Errorf("Invalid trim arguments: %v", args)
  518. }
  519. if t, err := strconv.ParseFloat(args[0], 64); err == nil && t >= 0 {
  520. po.Trim.Enabled = true
  521. po.Trim.Threshold = t
  522. } else {
  523. return fmt.Errorf("Invalid trim threshold: %s", args[0])
  524. }
  525. if nArgs > 1 && len(args[1]) > 0 {
  526. if c, err := colorFromHex(args[1]); err == nil {
  527. po.Trim.Color = c
  528. po.Trim.Smart = false
  529. } else {
  530. return fmt.Errorf("Invalid trim color: %s", args[1])
  531. }
  532. }
  533. if nArgs > 2 && len(args[2]) > 0 {
  534. po.Trim.EqualHor = parseBoolOption(args[2])
  535. }
  536. if nArgs > 3 && len(args[3]) > 0 {
  537. po.Trim.EqualVer = parseBoolOption(args[3])
  538. }
  539. return nil
  540. }
  541. func applyRotateOption(po *processingOptions, args []string) error {
  542. if len(args) > 1 {
  543. return fmt.Errorf("Invalid rotate arguments: %v", args)
  544. }
  545. if r, err := strconv.Atoi(args[0]); err == nil && r%90 == 0 {
  546. po.Rotate = r
  547. } else {
  548. return fmt.Errorf("Invalid rotation angle: %s", args[0])
  549. }
  550. return nil
  551. }
  552. func applyQualityOption(po *processingOptions, args []string) error {
  553. if len(args) > 1 {
  554. return fmt.Errorf("Invalid quality arguments: %v", args)
  555. }
  556. if q, err := strconv.Atoi(args[0]); err == nil && q >= 0 && q <= 100 {
  557. po.Quality = q
  558. } else {
  559. return fmt.Errorf("Invalid quality: %s", args[0])
  560. }
  561. return nil
  562. }
  563. func applyMaxBytesOption(po *processingOptions, args []string) error {
  564. if len(args) > 1 {
  565. return fmt.Errorf("Invalid max_bytes arguments: %v", args)
  566. }
  567. if max, err := strconv.Atoi(args[0]); err == nil && max >= 0 {
  568. po.MaxBytes = max
  569. } else {
  570. return fmt.Errorf("Invalid max_bytes: %s", args[0])
  571. }
  572. return nil
  573. }
  574. func applyBackgroundOption(po *processingOptions, args []string) error {
  575. switch len(args) {
  576. case 1:
  577. if len(args[0]) == 0 {
  578. po.Flatten = false
  579. } else if c, err := colorFromHex(args[0]); err == nil {
  580. po.Flatten = true
  581. po.Background = c
  582. } else {
  583. return fmt.Errorf("Invalid background argument: %s", err)
  584. }
  585. case 3:
  586. po.Flatten = true
  587. if r, err := strconv.ParseUint(args[0], 10, 8); err == nil && r <= 255 {
  588. po.Background.R = uint8(r)
  589. } else {
  590. return fmt.Errorf("Invalid background red channel: %s", args[0])
  591. }
  592. if g, err := strconv.ParseUint(args[1], 10, 8); err == nil && g <= 255 {
  593. po.Background.G = uint8(g)
  594. } else {
  595. return fmt.Errorf("Invalid background green channel: %s", args[1])
  596. }
  597. if b, err := strconv.ParseUint(args[2], 10, 8); err == nil && b <= 255 {
  598. po.Background.B = uint8(b)
  599. } else {
  600. return fmt.Errorf("Invalid background blue channel: %s", args[2])
  601. }
  602. default:
  603. return fmt.Errorf("Invalid background arguments: %v", args)
  604. }
  605. return nil
  606. }
  607. func applyBlurOption(po *processingOptions, args []string) error {
  608. if len(args) > 1 {
  609. return fmt.Errorf("Invalid blur arguments: %v", args)
  610. }
  611. if b, err := strconv.ParseFloat(args[0], 32); err == nil && b >= 0 {
  612. po.Blur = float32(b)
  613. } else {
  614. return fmt.Errorf("Invalid blur: %s", args[0])
  615. }
  616. return nil
  617. }
  618. func applySharpenOption(po *processingOptions, args []string) error {
  619. if len(args) > 1 {
  620. return fmt.Errorf("Invalid sharpen arguments: %v", args)
  621. }
  622. if s, err := strconv.ParseFloat(args[0], 32); err == nil && s >= 0 {
  623. po.Sharpen = float32(s)
  624. } else {
  625. return fmt.Errorf("Invalid sharpen: %s", args[0])
  626. }
  627. return nil
  628. }
  629. func applyPresetOption(po *processingOptions, args []string) error {
  630. for _, preset := range args {
  631. if p, ok := conf.Presets[preset]; ok {
  632. if po.isPresetUsed(preset) {
  633. logWarning("Recursive preset usage is detected: %s", preset)
  634. continue
  635. }
  636. po.presetUsed(preset)
  637. if err := applyProcessingOptions(po, p); err != nil {
  638. return err
  639. }
  640. } else {
  641. return fmt.Errorf("Unknown preset: %s", preset)
  642. }
  643. }
  644. return nil
  645. }
  646. func applyWatermarkOption(po *processingOptions, args []string) error {
  647. if len(args) > 7 {
  648. return fmt.Errorf("Invalid watermark arguments: %v", args)
  649. }
  650. if o, err := strconv.ParseFloat(args[0], 64); err == nil && o >= 0 && o <= 1 {
  651. po.Watermark.Enabled = o > 0
  652. po.Watermark.Opacity = o
  653. } else {
  654. return fmt.Errorf("Invalid watermark opacity: %s", args[0])
  655. }
  656. if len(args) > 1 && len(args[1]) > 0 {
  657. if args[1] == "re" {
  658. po.Watermark.Replicate = true
  659. } else if g, ok := gravityTypes[args[1]]; ok && g != gravityFocusPoint && g != gravitySmart {
  660. po.Watermark.Gravity.Type = g
  661. } else {
  662. return fmt.Errorf("Invalid watermark position: %s", args[1])
  663. }
  664. }
  665. if len(args) > 2 && len(args[2]) > 0 {
  666. if x, err := strconv.Atoi(args[2]); err == nil {
  667. po.Watermark.Gravity.X = float64(x)
  668. } else {
  669. return fmt.Errorf("Invalid watermark X offset: %s", args[2])
  670. }
  671. }
  672. if len(args) > 3 && len(args[3]) > 0 {
  673. if y, err := strconv.Atoi(args[3]); err == nil {
  674. po.Watermark.Gravity.Y = float64(y)
  675. } else {
  676. return fmt.Errorf("Invalid watermark Y offset: %s", args[3])
  677. }
  678. }
  679. if len(args) > 4 && len(args[4]) > 0 {
  680. if s, err := strconv.ParseFloat(args[4], 64); err == nil && s >= 0 {
  681. po.Watermark.Scale = s
  682. } else {
  683. return fmt.Errorf("Invalid watermark scale: %s", args[4])
  684. }
  685. }
  686. return nil
  687. }
  688. func applyFormatOption(po *processingOptions, args []string) error {
  689. if len(args) > 1 {
  690. return fmt.Errorf("Invalid format arguments: %v", args)
  691. }
  692. if f, ok := imageTypes[args[0]]; ok {
  693. po.Format = f
  694. } else {
  695. return fmt.Errorf("Invalid image format: %s", args[0])
  696. }
  697. if !imageTypeSaveSupport(po.Format) {
  698. return fmt.Errorf("Resulting image format is not supported: %s", po.Format)
  699. }
  700. return nil
  701. }
  702. func applyCacheBusterOption(po *processingOptions, args []string) error {
  703. if len(args) > 1 {
  704. return fmt.Errorf("Invalid cache buster arguments: %v", args)
  705. }
  706. po.CacheBuster = args[0]
  707. return nil
  708. }
  709. func applyFilenameOption(po *processingOptions, args []string) error {
  710. if len(args) > 1 {
  711. return fmt.Errorf("Invalid filename arguments: %v", args)
  712. }
  713. po.Filename = args[0]
  714. return nil
  715. }
  716. func applyStripMetadataOption(po *processingOptions, args []string) error {
  717. if len(args) > 1 {
  718. return fmt.Errorf("Invalid strip metadata arguments: %v", args)
  719. }
  720. po.StripMetadata = parseBoolOption(args[0])
  721. return nil
  722. }
  723. func applyStripColorProfileOption(po *processingOptions, args []string) error {
  724. if len(args) > 1 {
  725. return fmt.Errorf("Invalid strip color profile arguments: %v", args)
  726. }
  727. po.StripColorProfile = parseBoolOption(args[0])
  728. return nil
  729. }
  730. func applyAutoRotateOption(po *processingOptions, args []string) error {
  731. if len(args) > 1 {
  732. return fmt.Errorf("Invalid auto rotate arguments: %v", args)
  733. }
  734. po.AutoRotate = parseBoolOption(args[0])
  735. return nil
  736. }
  737. func applyProcessingOption(po *processingOptions, name string, args []string) error {
  738. switch name {
  739. case "format", "f", "ext":
  740. return applyFormatOption(po, args)
  741. case "resize", "rs":
  742. return applyResizeOption(po, args)
  743. case "resizing_type", "rt":
  744. return applyResizingTypeOption(po, args)
  745. case "size", "s":
  746. return applySizeOption(po, args)
  747. case "width", "w":
  748. return applyWidthOption(po, args)
  749. case "height", "h":
  750. return applyHeightOption(po, args)
  751. case "enlarge", "el":
  752. return applyEnlargeOption(po, args)
  753. case "extend", "ex":
  754. return applyExtendOption(po, args)
  755. case "dpr":
  756. return applyDprOption(po, args)
  757. case "gravity", "g":
  758. return applyGravityOption(po, args)
  759. case "crop", "c":
  760. return applyCropOption(po, args)
  761. case "trim", "t":
  762. return applyTrimOption(po, args)
  763. case "rotate", "rot":
  764. return applyRotateOption(po, args)
  765. case "padding", "pd":
  766. return applyPaddingOption(po, args)
  767. case "quality", "q":
  768. return applyQualityOption(po, args)
  769. case "max_bytes", "mb":
  770. return applyMaxBytesOption(po, args)
  771. case "background", "bg":
  772. return applyBackgroundOption(po, args)
  773. case "blur", "bl":
  774. return applyBlurOption(po, args)
  775. case "sharpen", "sh":
  776. return applySharpenOption(po, args)
  777. case "watermark", "wm":
  778. return applyWatermarkOption(po, args)
  779. case "preset", "pr":
  780. return applyPresetOption(po, args)
  781. case "cachebuster", "cb":
  782. return applyCacheBusterOption(po, args)
  783. case "strip_metadata", "sm":
  784. return applyStripMetadataOption(po, args)
  785. case "strip_color_profile", "scp":
  786. return applyStripColorProfileOption(po, args)
  787. case "auto_rotate", "ar":
  788. return applyAutoRotateOption(po, args)
  789. case "filename", "fn":
  790. return applyFilenameOption(po, args)
  791. }
  792. return fmt.Errorf("Unknown processing option: %s", name)
  793. }
  794. func applyProcessingOptions(po *processingOptions, options urlOptions) error {
  795. for _, opt := range options {
  796. if err := applyProcessingOption(po, opt.Name, opt.Args); err != nil {
  797. return err
  798. }
  799. }
  800. return nil
  801. }
  802. func isAllowedSource(imageURL string) bool {
  803. if len(conf.AllowedSources) == 0 {
  804. return true
  805. }
  806. for _, val := range conf.AllowedSources {
  807. if strings.HasPrefix(imageURL, string(val)) {
  808. return true
  809. }
  810. }
  811. return false
  812. }
  813. func parseURLOptions(opts []string) (urlOptions, []string) {
  814. parsed := make(urlOptions, 0, len(opts))
  815. urlStart := len(opts) + 1
  816. for i, opt := range opts {
  817. args := strings.Split(opt, ":")
  818. if len(args) == 1 {
  819. urlStart = i
  820. break
  821. }
  822. parsed = append(parsed, urlOption{Name: args[0], Args: args[1:]})
  823. }
  824. var rest []string
  825. if urlStart < len(opts) {
  826. rest = opts[urlStart:]
  827. } else {
  828. rest = []string{}
  829. }
  830. return parsed, rest
  831. }
  832. func defaultProcessingOptions(headers *processingHeaders) (*processingOptions, error) {
  833. po := newProcessingOptions()
  834. if strings.Contains(headers.Accept, "image/webp") {
  835. po.PreferWebP = conf.EnableWebpDetection || conf.EnforceWebp
  836. po.EnforceWebP = conf.EnforceWebp
  837. }
  838. if strings.Contains(headers.Accept, "image/avif") {
  839. po.PreferAvif = conf.EnableAvifDetection || conf.EnforceAvif
  840. po.EnforceAvif = conf.EnforceAvif
  841. }
  842. if conf.EnableClientHints && len(headers.ViewportWidth) > 0 {
  843. if vw, err := strconv.Atoi(headers.ViewportWidth); err == nil {
  844. po.Width = vw
  845. }
  846. }
  847. if conf.EnableClientHints && len(headers.Width) > 0 {
  848. if w, err := strconv.Atoi(headers.Width); err == nil {
  849. po.Width = w
  850. }
  851. }
  852. if conf.EnableClientHints && len(headers.DPR) > 0 {
  853. if dpr, err := strconv.ParseFloat(headers.DPR, 64); err == nil && (dpr > 0 && dpr <= maxClientHintDPR) {
  854. po.Dpr = dpr
  855. }
  856. }
  857. if _, ok := conf.Presets["default"]; ok {
  858. if err := applyPresetOption(po, []string{"default"}); err != nil {
  859. return po, err
  860. }
  861. }
  862. return po, nil
  863. }
  864. func parsePathOptions(parts []string, headers *processingHeaders) (string, *processingOptions, error) {
  865. po, err := defaultProcessingOptions(headers)
  866. if err != nil {
  867. return "", po, err
  868. }
  869. options, urlParts := parseURLOptions(parts)
  870. if err = applyProcessingOptions(po, options); err != nil {
  871. return "", po, err
  872. }
  873. url, extension, err := decodeURL(urlParts)
  874. if err != nil {
  875. return "", po, err
  876. }
  877. if len(extension) > 0 {
  878. if err = applyFormatOption(po, []string{extension}); err != nil {
  879. return "", po, err
  880. }
  881. }
  882. return url, po, nil
  883. }
  884. func parsePathPresets(parts []string, headers *processingHeaders) (string, *processingOptions, error) {
  885. po, err := defaultProcessingOptions(headers)
  886. if err != nil {
  887. return "", po, err
  888. }
  889. presets := strings.Split(parts[0], ":")
  890. urlParts := parts[1:]
  891. if err = applyPresetOption(po, presets); err != nil {
  892. return "", nil, err
  893. }
  894. url, extension, err := decodeURL(urlParts)
  895. if err != nil {
  896. return "", po, err
  897. }
  898. if len(extension) > 0 {
  899. if err = applyFormatOption(po, []string{extension}); err != nil {
  900. return "", po, err
  901. }
  902. }
  903. return url, po, nil
  904. }
  905. func parsePath(ctx context.Context, r *http.Request) (context.Context, error) {
  906. var err error
  907. path := trimAfter(r.RequestURI, '?')
  908. if len(conf.PathPrefix) > 0 {
  909. path = strings.TrimPrefix(path, conf.PathPrefix)
  910. }
  911. path = strings.TrimPrefix(path, "/")
  912. parts := strings.Split(path, "/")
  913. if len(parts) < 2 {
  914. return ctx, newError(404, fmt.Sprintf("Invalid path: %s", path), msgInvalidURL)
  915. }
  916. if !conf.AllowInsecure {
  917. if err = validatePath(parts[0], strings.TrimPrefix(path, parts[0])); err != nil {
  918. return ctx, newError(403, err.Error(), msgForbidden)
  919. }
  920. }
  921. headers := &processingHeaders{
  922. Accept: r.Header.Get("Accept"),
  923. Width: r.Header.Get("Width"),
  924. ViewportWidth: r.Header.Get("Viewport-Width"),
  925. DPR: r.Header.Get("DPR"),
  926. }
  927. var imageURL string
  928. var po *processingOptions
  929. if conf.OnlyPresets {
  930. imageURL, po, err = parsePathPresets(parts[1:], headers)
  931. } else {
  932. imageURL, po, err = parsePathOptions(parts[1:], headers)
  933. }
  934. if err != nil {
  935. return ctx, newError(404, err.Error(), msgInvalidURL)
  936. }
  937. if !isAllowedSource(imageURL) {
  938. return ctx, newError(404, "Invalid source", msgInvalidSource)
  939. }
  940. ctx = context.WithValue(ctx, imageURLCtxKey, imageURL)
  941. ctx = context.WithValue(ctx, processingOptionsCtxKey, po)
  942. return ctx, nil
  943. }
  944. func getImageURL(ctx context.Context) string {
  945. str, _ := ctx.Value(imageURLCtxKey).(string)
  946. return str
  947. }
  948. func getProcessingOptions(ctx context.Context) *processingOptions {
  949. return ctx.Value(processingOptionsCtxKey).(*processingOptions)
  950. }