processing_options.go 29 KB

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