processing_options.go 22 KB

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