processing_options.go 24 KB

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