processing_options.go 24 KB

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