processing_options.go 28 KB

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