processing_options.go 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020
  1. package options
  2. import (
  3. "errors"
  4. "fmt"
  5. "net/http"
  6. "strconv"
  7. "strings"
  8. "sync"
  9. "time"
  10. log "github.com/sirupsen/logrus"
  11. "github.com/imgproxy/imgproxy/v2/config"
  12. "github.com/imgproxy/imgproxy/v2/ierrors"
  13. "github.com/imgproxy/imgproxy/v2/imagetype"
  14. "github.com/imgproxy/imgproxy/v2/structdiff"
  15. "github.com/imgproxy/imgproxy/v2/vips"
  16. )
  17. const maxClientHintDPR = 8
  18. var errExpiredURL = errors.New("Expired URL")
  19. type GravityOptions struct {
  20. Type GravityType
  21. X, Y float64
  22. }
  23. type ExtendOptions struct {
  24. Enabled bool
  25. Gravity GravityOptions
  26. }
  27. type CropOptions struct {
  28. Width float64
  29. Height float64
  30. Gravity GravityOptions
  31. }
  32. type PaddingOptions struct {
  33. Enabled bool
  34. Top int
  35. Right int
  36. Bottom int
  37. Left int
  38. }
  39. type TrimOptions struct {
  40. Enabled bool
  41. Threshold float64
  42. Smart bool
  43. Color vips.Color
  44. EqualHor bool
  45. EqualVer bool
  46. }
  47. type WatermarkOptions struct {
  48. Enabled bool
  49. Opacity float64
  50. Replicate bool
  51. Gravity GravityOptions
  52. Scale float64
  53. }
  54. type ProcessingOptions struct {
  55. ResizingType ResizeType
  56. Width int
  57. Height int
  58. MinWidth int
  59. MinHeight int
  60. Dpr float64
  61. Gravity GravityOptions
  62. Enlarge bool
  63. Extend ExtendOptions
  64. Crop CropOptions
  65. Padding PaddingOptions
  66. Trim TrimOptions
  67. Rotate int
  68. Format imagetype.Type
  69. Quality int
  70. FormatQuality map[imagetype.Type]int
  71. MaxBytes int
  72. Flatten bool
  73. Background vips.Color
  74. Blur float32
  75. Sharpen float32
  76. Pixelate int
  77. StripMetadata bool
  78. StripColorProfile bool
  79. AutoRotate bool
  80. SkipProcessingFormats []imagetype.Type
  81. CacheBuster string
  82. Watermark WatermarkOptions
  83. PreferWebP bool
  84. EnforceWebP bool
  85. PreferAvif bool
  86. EnforceAvif bool
  87. Filename string
  88. UsedPresets []string
  89. defaultQuality int
  90. }
  91. var (
  92. _newProcessingOptions ProcessingOptions
  93. newProcessingOptionsOnce sync.Once
  94. )
  95. func NewProcessingOptions() *ProcessingOptions {
  96. newProcessingOptionsOnce.Do(func() {
  97. _newProcessingOptions = ProcessingOptions{
  98. ResizingType: ResizeFit,
  99. Width: 0,
  100. Height: 0,
  101. Gravity: GravityOptions{Type: GravityCenter},
  102. Enlarge: false,
  103. Extend: ExtendOptions{Enabled: false, Gravity: GravityOptions{Type: GravityCenter}},
  104. Padding: PaddingOptions{Enabled: false},
  105. Trim: TrimOptions{Enabled: false, Threshold: 10, Smart: true},
  106. Rotate: 0,
  107. Quality: 0,
  108. MaxBytes: 0,
  109. Format: imagetype.Unknown,
  110. Background: vips.Color{R: 255, G: 255, B: 255},
  111. Blur: 0,
  112. Sharpen: 0,
  113. Dpr: 1,
  114. Watermark: WatermarkOptions{Opacity: 1, Replicate: false, Gravity: GravityOptions{Type: GravityCenter}},
  115. StripMetadata: config.StripMetadata,
  116. StripColorProfile: config.StripColorProfile,
  117. AutoRotate: config.AutoRotate,
  118. // Basically, we need this to update ETag when `IMGPROXY_QUALITY` is changed
  119. defaultQuality: config.Quality,
  120. }
  121. })
  122. po := _newProcessingOptions
  123. po.SkipProcessingFormats = append([]imagetype.Type(nil), config.SkipProcessingFormats...)
  124. po.UsedPresets = make([]string, 0, len(config.Presets))
  125. po.FormatQuality = make(map[imagetype.Type]int)
  126. for k, v := range config.FormatQuality {
  127. po.FormatQuality[k] = v
  128. }
  129. return &po
  130. }
  131. func (po *ProcessingOptions) GetQuality() int {
  132. q := po.Quality
  133. if q == 0 {
  134. q = po.FormatQuality[po.Format]
  135. }
  136. if q == 0 {
  137. q = po.defaultQuality
  138. }
  139. return q
  140. }
  141. func (po *ProcessingOptions) isPresetUsed(name string) bool {
  142. for _, usedName := range po.UsedPresets {
  143. if usedName == name {
  144. return true
  145. }
  146. }
  147. return false
  148. }
  149. func (po *ProcessingOptions) Diff() structdiff.Entries {
  150. return structdiff.Diff(NewProcessingOptions(), po)
  151. }
  152. func (po *ProcessingOptions) String() string {
  153. return po.Diff().String()
  154. }
  155. func (po *ProcessingOptions) MarshalJSON() ([]byte, error) {
  156. return po.Diff().MarshalJSON()
  157. }
  158. func parseDimension(d *int, name, arg string) error {
  159. if v, err := strconv.Atoi(arg); err == nil && v >= 0 {
  160. *d = v
  161. } else {
  162. return fmt.Errorf("Invalid %s: %s", name, arg)
  163. }
  164. return nil
  165. }
  166. func parseBoolOption(str string) bool {
  167. b, err := strconv.ParseBool(str)
  168. if err != nil {
  169. log.Warningf("`%s` is not a valid boolean value. Treated as false", str)
  170. }
  171. return b
  172. }
  173. func isGravityOffcetValid(gravity GravityType, offset float64) bool {
  174. if gravity == GravityCenter {
  175. return true
  176. }
  177. return offset >= 0 && (gravity != GravityFocusPoint || offset <= 1)
  178. }
  179. func parseGravity(g *GravityOptions, args []string) error {
  180. nArgs := len(args)
  181. if nArgs > 3 {
  182. return fmt.Errorf("Invalid gravity arguments: %v", args)
  183. }
  184. if t, ok := gravityTypes[args[0]]; ok {
  185. g.Type = t
  186. } else {
  187. return fmt.Errorf("Invalid gravity: %s", args[0])
  188. }
  189. if g.Type == GravitySmart && nArgs > 1 {
  190. return fmt.Errorf("Invalid gravity arguments: %v", args)
  191. } else if g.Type == GravityFocusPoint && nArgs != 3 {
  192. return fmt.Errorf("Invalid gravity arguments: %v", args)
  193. }
  194. if nArgs > 1 {
  195. if x, err := strconv.ParseFloat(args[1], 64); err == nil && isGravityOffcetValid(g.Type, x) {
  196. g.X = x
  197. } else {
  198. return fmt.Errorf("Invalid gravity X: %s", args[1])
  199. }
  200. }
  201. if nArgs > 2 {
  202. if y, err := strconv.ParseFloat(args[2], 64); err == nil && isGravityOffcetValid(g.Type, y) {
  203. g.Y = y
  204. } else {
  205. return fmt.Errorf("Invalid gravity Y: %s", args[2])
  206. }
  207. }
  208. return nil
  209. }
  210. func applyWidthOption(po *ProcessingOptions, args []string) error {
  211. if len(args) > 1 {
  212. return fmt.Errorf("Invalid width arguments: %v", args)
  213. }
  214. return parseDimension(&po.Width, "width", args[0])
  215. }
  216. func applyHeightOption(po *ProcessingOptions, args []string) error {
  217. if len(args) > 1 {
  218. return fmt.Errorf("Invalid height arguments: %v", args)
  219. }
  220. return parseDimension(&po.Height, "height", args[0])
  221. }
  222. func applyMinWidthOption(po *ProcessingOptions, args []string) error {
  223. if len(args) > 1 {
  224. return fmt.Errorf("Invalid min width arguments: %v", args)
  225. }
  226. return parseDimension(&po.MinWidth, "min width", args[0])
  227. }
  228. func applyMinHeightOption(po *ProcessingOptions, args []string) error {
  229. if len(args) > 1 {
  230. return fmt.Errorf("Invalid min height arguments: %v", args)
  231. }
  232. return parseDimension(&po.MinHeight, " min height", args[0])
  233. }
  234. func applyEnlargeOption(po *ProcessingOptions, args []string) error {
  235. if len(args) > 1 {
  236. return fmt.Errorf("Invalid enlarge arguments: %v", args)
  237. }
  238. po.Enlarge = parseBoolOption(args[0])
  239. return nil
  240. }
  241. func applyExtendOption(po *ProcessingOptions, args []string) error {
  242. if len(args) > 4 {
  243. return fmt.Errorf("Invalid extend arguments: %v", args)
  244. }
  245. po.Extend.Enabled = parseBoolOption(args[0])
  246. if len(args) > 1 {
  247. if err := parseGravity(&po.Extend.Gravity, args[1:]); err != nil {
  248. return err
  249. }
  250. if po.Extend.Gravity.Type == GravitySmart {
  251. return errors.New("extend doesn't support smart gravity")
  252. }
  253. }
  254. return nil
  255. }
  256. func applySizeOption(po *ProcessingOptions, args []string) (err error) {
  257. if len(args) > 7 {
  258. return fmt.Errorf("Invalid size arguments: %v", args)
  259. }
  260. if len(args) >= 1 && len(args[0]) > 0 {
  261. if err = applyWidthOption(po, args[0:1]); err != nil {
  262. return
  263. }
  264. }
  265. if len(args) >= 2 && len(args[1]) > 0 {
  266. if err = applyHeightOption(po, args[1:2]); err != nil {
  267. return
  268. }
  269. }
  270. if len(args) >= 3 && len(args[2]) > 0 {
  271. if err = applyEnlargeOption(po, args[2:3]); err != nil {
  272. return
  273. }
  274. }
  275. if len(args) >= 4 && len(args[3]) > 0 {
  276. if err = applyExtendOption(po, args[3:]); err != nil {
  277. return
  278. }
  279. }
  280. return nil
  281. }
  282. func applyResizingTypeOption(po *ProcessingOptions, args []string) error {
  283. if len(args) > 1 {
  284. return fmt.Errorf("Invalid resizing type arguments: %v", args)
  285. }
  286. if r, ok := resizeTypes[args[0]]; ok {
  287. po.ResizingType = r
  288. } else {
  289. return fmt.Errorf("Invalid resize type: %s", args[0])
  290. }
  291. return nil
  292. }
  293. func applyResizeOption(po *ProcessingOptions, args []string) error {
  294. if len(args) > 8 {
  295. return fmt.Errorf("Invalid resize arguments: %v", args)
  296. }
  297. if len(args[0]) > 0 {
  298. if err := applyResizingTypeOption(po, args[0:1]); err != nil {
  299. return err
  300. }
  301. }
  302. if len(args) > 1 {
  303. if err := applySizeOption(po, args[1:]); err != nil {
  304. return err
  305. }
  306. }
  307. return nil
  308. }
  309. func applyDprOption(po *ProcessingOptions, args []string) error {
  310. if len(args) > 1 {
  311. return fmt.Errorf("Invalid dpr arguments: %v", args)
  312. }
  313. if d, err := strconv.ParseFloat(args[0], 64); err == nil && d > 0 {
  314. po.Dpr = d
  315. } else {
  316. return fmt.Errorf("Invalid dpr: %s", args[0])
  317. }
  318. return nil
  319. }
  320. func applyGravityOption(po *ProcessingOptions, args []string) error {
  321. return parseGravity(&po.Gravity, args)
  322. }
  323. func applyCropOption(po *ProcessingOptions, args []string) error {
  324. if len(args) > 5 {
  325. return fmt.Errorf("Invalid crop arguments: %v", args)
  326. }
  327. if w, err := strconv.ParseFloat(args[0], 64); err == nil && w >= 0 {
  328. po.Crop.Width = w
  329. } else {
  330. return fmt.Errorf("Invalid crop width: %s", args[0])
  331. }
  332. if len(args) > 1 {
  333. if h, err := strconv.ParseFloat(args[1], 64); err == nil && h >= 0 {
  334. po.Crop.Height = h
  335. } else {
  336. return fmt.Errorf("Invalid crop height: %s", args[1])
  337. }
  338. }
  339. if len(args) > 2 {
  340. return parseGravity(&po.Crop.Gravity, args[2:])
  341. }
  342. return nil
  343. }
  344. func applyPaddingOption(po *ProcessingOptions, args []string) error {
  345. nArgs := len(args)
  346. if nArgs < 1 || nArgs > 4 {
  347. return fmt.Errorf("Invalid padding arguments: %v", args)
  348. }
  349. po.Padding.Enabled = true
  350. if nArgs > 0 && len(args[0]) > 0 {
  351. if err := parseDimension(&po.Padding.Top, "padding top (+all)", args[0]); err != nil {
  352. return err
  353. }
  354. po.Padding.Right = po.Padding.Top
  355. po.Padding.Bottom = po.Padding.Top
  356. po.Padding.Left = po.Padding.Top
  357. }
  358. if nArgs > 1 && len(args[1]) > 0 {
  359. if err := parseDimension(&po.Padding.Right, "padding right (+left)", args[1]); err != nil {
  360. return err
  361. }
  362. po.Padding.Left = po.Padding.Right
  363. }
  364. if nArgs > 2 && len(args[2]) > 0 {
  365. if err := parseDimension(&po.Padding.Bottom, "padding bottom", args[2]); err != nil {
  366. return err
  367. }
  368. }
  369. if nArgs > 3 && len(args[3]) > 0 {
  370. if err := parseDimension(&po.Padding.Left, "padding left", args[3]); err != nil {
  371. return err
  372. }
  373. }
  374. if po.Padding.Top == 0 && po.Padding.Right == 0 && po.Padding.Bottom == 0 && po.Padding.Left == 0 {
  375. po.Padding.Enabled = false
  376. }
  377. return nil
  378. }
  379. func applyTrimOption(po *ProcessingOptions, args []string) error {
  380. nArgs := len(args)
  381. if nArgs > 4 {
  382. return fmt.Errorf("Invalid trim arguments: %v", args)
  383. }
  384. if t, err := strconv.ParseFloat(args[0], 64); err == nil && t >= 0 {
  385. po.Trim.Enabled = true
  386. po.Trim.Threshold = t
  387. } else {
  388. return fmt.Errorf("Invalid trim threshold: %s", args[0])
  389. }
  390. if nArgs > 1 && len(args[1]) > 0 {
  391. if c, err := vips.ColorFromHex(args[1]); err == nil {
  392. po.Trim.Color = c
  393. po.Trim.Smart = false
  394. } else {
  395. return fmt.Errorf("Invalid trim color: %s", args[1])
  396. }
  397. }
  398. if nArgs > 2 && len(args[2]) > 0 {
  399. po.Trim.EqualHor = parseBoolOption(args[2])
  400. }
  401. if nArgs > 3 && len(args[3]) > 0 {
  402. po.Trim.EqualVer = parseBoolOption(args[3])
  403. }
  404. return nil
  405. }
  406. func applyRotateOption(po *ProcessingOptions, args []string) error {
  407. if len(args) > 1 {
  408. return fmt.Errorf("Invalid rotate arguments: %v", args)
  409. }
  410. if r, err := strconv.Atoi(args[0]); err == nil && r%90 == 0 {
  411. po.Rotate = r
  412. } else {
  413. return fmt.Errorf("Invalid rotation angle: %s", args[0])
  414. }
  415. return nil
  416. }
  417. func applyQualityOption(po *ProcessingOptions, args []string) error {
  418. if len(args) > 1 {
  419. return fmt.Errorf("Invalid quality arguments: %v", args)
  420. }
  421. if q, err := strconv.Atoi(args[0]); err == nil && q >= 0 && q <= 100 {
  422. po.Quality = q
  423. } else {
  424. return fmt.Errorf("Invalid quality: %s", args[0])
  425. }
  426. return nil
  427. }
  428. func applyFormatQualityOption(po *ProcessingOptions, args []string) error {
  429. argsLen := len(args)
  430. if len(args)%2 != 0 {
  431. return fmt.Errorf("Missing quality for: %s", args[argsLen-1])
  432. }
  433. for i := 0; i < argsLen; i += 2 {
  434. f, ok := imagetype.Types[args[i]]
  435. if !ok {
  436. return fmt.Errorf("Invalid image format: %s", args[i])
  437. }
  438. if q, err := strconv.Atoi(args[i+1]); err == nil && q >= 0 && q <= 100 {
  439. po.FormatQuality[f] = q
  440. } else {
  441. return fmt.Errorf("Invalid quality for %s: %s", args[i], args[i+1])
  442. }
  443. }
  444. return nil
  445. }
  446. func applyMaxBytesOption(po *ProcessingOptions, args []string) error {
  447. if len(args) > 1 {
  448. return fmt.Errorf("Invalid max_bytes arguments: %v", args)
  449. }
  450. if max, err := strconv.Atoi(args[0]); err == nil && max >= 0 {
  451. po.MaxBytes = max
  452. } else {
  453. return fmt.Errorf("Invalid max_bytes: %s", args[0])
  454. }
  455. return nil
  456. }
  457. func applyBackgroundOption(po *ProcessingOptions, args []string) error {
  458. switch len(args) {
  459. case 1:
  460. if len(args[0]) == 0 {
  461. po.Flatten = false
  462. } else if c, err := vips.ColorFromHex(args[0]); err == nil {
  463. po.Flatten = true
  464. po.Background = c
  465. } else {
  466. return fmt.Errorf("Invalid background argument: %s", err)
  467. }
  468. case 3:
  469. po.Flatten = true
  470. if r, err := strconv.ParseUint(args[0], 10, 8); err == nil && r <= 255 {
  471. po.Background.R = uint8(r)
  472. } else {
  473. return fmt.Errorf("Invalid background red channel: %s", args[0])
  474. }
  475. if g, err := strconv.ParseUint(args[1], 10, 8); err == nil && g <= 255 {
  476. po.Background.G = uint8(g)
  477. } else {
  478. return fmt.Errorf("Invalid background green channel: %s", args[1])
  479. }
  480. if b, err := strconv.ParseUint(args[2], 10, 8); err == nil && b <= 255 {
  481. po.Background.B = uint8(b)
  482. } else {
  483. return fmt.Errorf("Invalid background blue channel: %s", args[2])
  484. }
  485. default:
  486. return fmt.Errorf("Invalid background arguments: %v", args)
  487. }
  488. return nil
  489. }
  490. func applyBlurOption(po *ProcessingOptions, args []string) error {
  491. if len(args) > 1 {
  492. return fmt.Errorf("Invalid blur arguments: %v", args)
  493. }
  494. if b, err := strconv.ParseFloat(args[0], 32); err == nil && b >= 0 {
  495. po.Blur = float32(b)
  496. } else {
  497. return fmt.Errorf("Invalid blur: %s", args[0])
  498. }
  499. return nil
  500. }
  501. func applySharpenOption(po *ProcessingOptions, args []string) error {
  502. if len(args) > 1 {
  503. return fmt.Errorf("Invalid sharpen arguments: %v", args)
  504. }
  505. if s, err := strconv.ParseFloat(args[0], 32); err == nil && s >= 0 {
  506. po.Sharpen = float32(s)
  507. } else {
  508. return fmt.Errorf("Invalid sharpen: %s", args[0])
  509. }
  510. return nil
  511. }
  512. func applyPixelateOption(po *ProcessingOptions, args []string) error {
  513. if len(args) > 1 {
  514. return fmt.Errorf("Invalid pixelate arguments: %v", args)
  515. }
  516. if p, err := strconv.Atoi(args[0]); err == nil && p >= 0 {
  517. po.Pixelate = p
  518. } else {
  519. return fmt.Errorf("Invalid pixelate: %s", args[0])
  520. }
  521. return nil
  522. }
  523. func applyPresetOption(po *ProcessingOptions, args []string) error {
  524. for _, preset := range args {
  525. if p, ok := presets[preset]; ok {
  526. if po.isPresetUsed(preset) {
  527. log.Warningf("Recursive preset usage is detected: %s", preset)
  528. continue
  529. }
  530. po.UsedPresets = append(po.UsedPresets, preset)
  531. if err := applyURLOptions(po, p); err != nil {
  532. return err
  533. }
  534. } else {
  535. return fmt.Errorf("Unknown preset: %s", preset)
  536. }
  537. }
  538. return nil
  539. }
  540. func applyWatermarkOption(po *ProcessingOptions, args []string) error {
  541. if len(args) > 7 {
  542. return fmt.Errorf("Invalid watermark arguments: %v", args)
  543. }
  544. if o, err := strconv.ParseFloat(args[0], 64); err == nil && o >= 0 && o <= 1 {
  545. po.Watermark.Enabled = o > 0
  546. po.Watermark.Opacity = o
  547. } else {
  548. return fmt.Errorf("Invalid watermark opacity: %s", args[0])
  549. }
  550. if len(args) > 1 && len(args[1]) > 0 {
  551. if args[1] == "re" {
  552. po.Watermark.Replicate = true
  553. } else if g, ok := gravityTypes[args[1]]; ok && g != GravityFocusPoint && g != GravitySmart {
  554. po.Watermark.Gravity.Type = g
  555. } else {
  556. return fmt.Errorf("Invalid watermark position: %s", args[1])
  557. }
  558. }
  559. if len(args) > 2 && len(args[2]) > 0 {
  560. if x, err := strconv.Atoi(args[2]); err == nil {
  561. po.Watermark.Gravity.X = float64(x)
  562. } else {
  563. return fmt.Errorf("Invalid watermark X offset: %s", args[2])
  564. }
  565. }
  566. if len(args) > 3 && len(args[3]) > 0 {
  567. if y, err := strconv.Atoi(args[3]); err == nil {
  568. po.Watermark.Gravity.Y = float64(y)
  569. } else {
  570. return fmt.Errorf("Invalid watermark Y offset: %s", args[3])
  571. }
  572. }
  573. if len(args) > 4 && len(args[4]) > 0 {
  574. if s, err := strconv.ParseFloat(args[4], 64); err == nil && s >= 0 {
  575. po.Watermark.Scale = s
  576. } else {
  577. return fmt.Errorf("Invalid watermark scale: %s", args[4])
  578. }
  579. }
  580. return nil
  581. }
  582. func applyFormatOption(po *ProcessingOptions, args []string) error {
  583. if len(args) > 1 {
  584. return fmt.Errorf("Invalid format arguments: %v", args)
  585. }
  586. if f, ok := imagetype.Types[args[0]]; ok {
  587. po.Format = f
  588. } else {
  589. return fmt.Errorf("Invalid image format: %s", args[0])
  590. }
  591. return nil
  592. }
  593. func applyCacheBusterOption(po *ProcessingOptions, args []string) error {
  594. if len(args) > 1 {
  595. return fmt.Errorf("Invalid cache buster arguments: %v", args)
  596. }
  597. po.CacheBuster = args[0]
  598. return nil
  599. }
  600. func applySkipProcessingFormatsOption(po *ProcessingOptions, args []string) error {
  601. for _, format := range args {
  602. if f, ok := imagetype.Types[format]; ok {
  603. po.SkipProcessingFormats = append(po.SkipProcessingFormats, f)
  604. } else {
  605. return fmt.Errorf("Invalid image format in skip processing: %s", format)
  606. }
  607. }
  608. return nil
  609. }
  610. func applyFilenameOption(po *ProcessingOptions, args []string) error {
  611. if len(args) > 1 {
  612. return fmt.Errorf("Invalid filename arguments: %v", args)
  613. }
  614. po.Filename = args[0]
  615. return nil
  616. }
  617. func applyExpiresOption(po *ProcessingOptions, args []string) error {
  618. if len(args) > 1 {
  619. return fmt.Errorf("Invalid expires arguments: %v", args)
  620. }
  621. timestamp, err := strconv.ParseInt(args[0], 10, 64)
  622. if err != nil {
  623. return fmt.Errorf("Invalid expires argument: %v", args[0])
  624. }
  625. if timestamp > 0 && timestamp < time.Now().Unix() {
  626. return errExpiredURL
  627. }
  628. return nil
  629. }
  630. func applyStripMetadataOption(po *ProcessingOptions, args []string) error {
  631. if len(args) > 1 {
  632. return fmt.Errorf("Invalid strip metadata arguments: %v", args)
  633. }
  634. po.StripMetadata = parseBoolOption(args[0])
  635. return nil
  636. }
  637. func applyStripColorProfileOption(po *ProcessingOptions, args []string) error {
  638. if len(args) > 1 {
  639. return fmt.Errorf("Invalid strip color profile arguments: %v", args)
  640. }
  641. po.StripColorProfile = parseBoolOption(args[0])
  642. return nil
  643. }
  644. func applyAutoRotateOption(po *ProcessingOptions, args []string) error {
  645. if len(args) > 1 {
  646. return fmt.Errorf("Invalid auto rotate arguments: %v", args)
  647. }
  648. po.AutoRotate = parseBoolOption(args[0])
  649. return nil
  650. }
  651. func applyURLOption(po *ProcessingOptions, name string, args []string) error {
  652. switch name {
  653. case "resize", "rs":
  654. return applyResizeOption(po, args)
  655. case "size", "s":
  656. return applySizeOption(po, args)
  657. case "resizing_type", "rt":
  658. return applyResizingTypeOption(po, args)
  659. case "width", "w":
  660. return applyWidthOption(po, args)
  661. case "height", "h":
  662. return applyHeightOption(po, args)
  663. case "min-width", "mw":
  664. return applyMinWidthOption(po, args)
  665. case "min-height", "mh":
  666. return applyMinHeightOption(po, args)
  667. case "dpr":
  668. return applyDprOption(po, args)
  669. case "enlarge", "el":
  670. return applyEnlargeOption(po, args)
  671. case "extend", "ex":
  672. return applyExtendOption(po, args)
  673. case "gravity", "g":
  674. return applyGravityOption(po, args)
  675. case "crop", "c":
  676. return applyCropOption(po, args)
  677. case "trim", "t":
  678. return applyTrimOption(po, args)
  679. case "padding", "pd":
  680. return applyPaddingOption(po, args)
  681. case "auto_rotate", "ar":
  682. return applyAutoRotateOption(po, args)
  683. case "rotate", "rot":
  684. return applyRotateOption(po, args)
  685. case "background", "bg":
  686. return applyBackgroundOption(po, args)
  687. case "blur", "bl":
  688. return applyBlurOption(po, args)
  689. case "sharpen", "sh":
  690. return applySharpenOption(po, args)
  691. case "pixelate", "pix":
  692. return applyPixelateOption(po, args)
  693. case "watermark", "wm":
  694. return applyWatermarkOption(po, args)
  695. case "strip_metadata", "sm":
  696. return applyStripMetadataOption(po, args)
  697. case "strip_color_profile", "scp":
  698. return applyStripColorProfileOption(po, args)
  699. // Saving options
  700. case "quality", "q":
  701. return applyQualityOption(po, args)
  702. case "format_quality", "fq":
  703. return applyFormatQualityOption(po, args)
  704. case "max_bytes", "mb":
  705. return applyMaxBytesOption(po, args)
  706. case "format", "f", "ext":
  707. return applyFormatOption(po, args)
  708. // Handling options
  709. case "skip_processing", "skp":
  710. return applySkipProcessingFormatsOption(po, args)
  711. case "cachebuster", "cb":
  712. return applyCacheBusterOption(po, args)
  713. case "expires", "exp":
  714. return applyExpiresOption(po, args)
  715. case "filename", "fn":
  716. return applyFilenameOption(po, args)
  717. // Presets
  718. case "preset", "pr":
  719. return applyPresetOption(po, args)
  720. }
  721. return fmt.Errorf("Unknown processing option: %s", name)
  722. }
  723. func applyURLOptions(po *ProcessingOptions, options urlOptions) error {
  724. for _, opt := range options {
  725. if err := applyURLOption(po, opt.Name, opt.Args); err != nil {
  726. return err
  727. }
  728. }
  729. return nil
  730. }
  731. func defaultProcessingOptions(headers http.Header) (*ProcessingOptions, error) {
  732. po := NewProcessingOptions()
  733. headerAccept := headers.Get("Accept")
  734. if strings.Contains(headerAccept, "image/webp") {
  735. po.PreferWebP = config.EnableWebpDetection || config.EnforceWebp
  736. po.EnforceWebP = config.EnforceWebp
  737. }
  738. if strings.Contains(headerAccept, "image/avif") {
  739. po.PreferAvif = config.EnableAvifDetection || config.EnforceAvif
  740. po.EnforceAvif = config.EnforceAvif
  741. }
  742. if config.EnableClientHints {
  743. if headerViewportWidth := headers.Get("Viewport-Width"); len(headerViewportWidth) > 0 {
  744. if vw, err := strconv.Atoi(headerViewportWidth); err == nil {
  745. po.Width = vw
  746. }
  747. }
  748. if headerWidth := headers.Get("Width"); len(headerWidth) > 0 {
  749. if w, err := strconv.Atoi(headerWidth); err == nil {
  750. po.Width = w
  751. }
  752. }
  753. if headerDPR := headers.Get("DPR"); len(headerDPR) > 0 {
  754. if dpr, err := strconv.ParseFloat(headerDPR, 64); err == nil && (dpr > 0 && dpr <= maxClientHintDPR) {
  755. po.Dpr = dpr
  756. }
  757. }
  758. }
  759. if _, ok := presets["default"]; ok {
  760. if err := applyPresetOption(po, []string{"default"}); err != nil {
  761. return po, err
  762. }
  763. }
  764. return po, nil
  765. }
  766. func parsePathOptions(parts []string, headers http.Header) (*ProcessingOptions, string, error) {
  767. po, err := defaultProcessingOptions(headers)
  768. if err != nil {
  769. return nil, "", err
  770. }
  771. options, urlParts := parseURLOptions(parts)
  772. if err = applyURLOptions(po, options); err != nil {
  773. return nil, "", err
  774. }
  775. url, extension, err := DecodeURL(urlParts)
  776. if err != nil {
  777. return nil, "", err
  778. }
  779. if len(extension) > 0 {
  780. if err = applyFormatOption(po, []string{extension}); err != nil {
  781. return nil, "", err
  782. }
  783. }
  784. return po, url, nil
  785. }
  786. func parsePathPresets(parts []string, headers http.Header) (*ProcessingOptions, string, error) {
  787. po, err := defaultProcessingOptions(headers)
  788. if err != nil {
  789. return nil, "", err
  790. }
  791. presets := strings.Split(parts[0], ":")
  792. urlParts := parts[1:]
  793. if err = applyPresetOption(po, presets); err != nil {
  794. return nil, "", err
  795. }
  796. url, extension, err := DecodeURL(urlParts)
  797. if err != nil {
  798. return nil, "", err
  799. }
  800. if len(extension) > 0 {
  801. if err = applyFormatOption(po, []string{extension}); err != nil {
  802. return nil, "", err
  803. }
  804. }
  805. return po, url, nil
  806. }
  807. func ParsePath(path string, headers http.Header) (*ProcessingOptions, string, error) {
  808. if path == "" || path == "/" {
  809. return nil, "", ierrors.New(404, fmt.Sprintf("Invalid path: %s", path), "Invalid URL")
  810. }
  811. parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
  812. var (
  813. imageURL string
  814. po *ProcessingOptions
  815. err error
  816. )
  817. if config.OnlyPresets {
  818. po, imageURL, err = parsePathPresets(parts, headers)
  819. } else {
  820. po, imageURL, err = parsePathOptions(parts, headers)
  821. }
  822. if err != nil {
  823. return nil, "", ierrors.New(404, err.Error(), "Invalid URL")
  824. }
  825. return po, imageURL, nil
  826. }