processing_options.go 29 KB

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