processing_options.go 28 KB

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