processing_options.go 26 KB

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