processing_options.go 25 KB

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