processing_options.go 23 KB

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