1
0

processing_options.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. package optionsparser
  2. import (
  3. "net/http"
  4. "slices"
  5. "strconv"
  6. "strings"
  7. "github.com/imgproxy/imgproxy/v3/ierrors"
  8. "github.com/imgproxy/imgproxy/v3/imath"
  9. "github.com/imgproxy/imgproxy/v3/options"
  10. "github.com/imgproxy/imgproxy/v3/options/keys"
  11. "github.com/imgproxy/imgproxy/v3/processing"
  12. )
  13. const maxClientHintDPR = 8
  14. func (p *Parser) applyURLOption(
  15. o *options.Options,
  16. name string,
  17. args []string,
  18. usedPresets ...string,
  19. ) error {
  20. switch name {
  21. case "resize", "rs":
  22. return applyResizeOption(o, args)
  23. case "size", "s":
  24. return applySizeOption(o, args)
  25. case "resizing_type", "rt":
  26. return applyResizingTypeOption(o, args)
  27. case "width", "w":
  28. return applyWidthOption(o, args)
  29. case "height", "h":
  30. return applyHeightOption(o, args)
  31. case "min-width", "mw":
  32. return applyMinWidthOption(o, args)
  33. case "min-height", "mh":
  34. return applyMinHeightOption(o, args)
  35. case "zoom", "z":
  36. return applyZoomOption(o, args)
  37. case "dpr":
  38. return applyDprOption(o, args)
  39. case "enlarge", "el":
  40. return applyEnlargeOption(o, args)
  41. case "extend", "ex":
  42. return applyExtendOption(o, args)
  43. case "extend_aspect_ratio", "extend_ar", "exar":
  44. return applyExtendAspectRatioOption(o, args)
  45. case "gravity", "g":
  46. return applyGravityOption(o, args)
  47. case "crop", "c":
  48. return applyCropOption(o, args)
  49. case "trim", "t":
  50. return applyTrimOption(o, args)
  51. case "padding", "pd":
  52. return applyPaddingOption(o, args)
  53. case "auto_rotate", "ar":
  54. return applyAutoRotateOption(o, args)
  55. case "rotate", "rot":
  56. return applyRotateOption(o, args)
  57. case "background", "bg":
  58. return applyBackgroundOption(o, args)
  59. case "blur", "bl":
  60. return applyBlurOption(o, args)
  61. case "sharpen", "sh":
  62. return applySharpenOption(o, args)
  63. case "pixelate", "pix":
  64. return applyPixelateOption(o, args)
  65. case "watermark", "wm":
  66. return applyWatermarkOption(o, args)
  67. case "strip_metadata", "sm":
  68. return applyStripMetadataOption(o.Main(), args)
  69. case "keep_copyright", "kcr":
  70. return applyKeepCopyrightOption(o.Main(), args)
  71. case "strip_color_profile", "scp":
  72. return applyStripColorProfileOption(o.Main(), args)
  73. case "enforce_thumbnail", "eth":
  74. return applyEnforceThumbnailOption(o.Main(), args)
  75. // Saving options
  76. case "quality", "q":
  77. return applyQualityOption(o.Main(), args)
  78. case "format_quality", "fq":
  79. return applyFormatQualityOption(o.Main(), args)
  80. case "max_bytes", "mb":
  81. return applyMaxBytesOption(o.Main(), args)
  82. case "format", "f", "ext":
  83. return applyFormatOption(o.Main(), args)
  84. // Handling options
  85. case "skip_processing", "skp":
  86. return applySkipProcessingFormatsOption(o.Main(), args)
  87. case "raw":
  88. return applyRawOption(o.Main(), args)
  89. case "cachebuster", "cb":
  90. return applyCacheBusterOption(o.Main(), args)
  91. case "expires", "exp":
  92. return applyExpiresOption(o.Main(), args)
  93. case "filename", "fn":
  94. return applyFilenameOption(o.Main(), args)
  95. case "return_attachment", "att":
  96. return applyReturnAttachmentOption(o.Main(), args)
  97. // Presets
  98. case "preset", "pr":
  99. return applyPresetOption(p, o, args, usedPresets...)
  100. // Security
  101. case "max_src_resolution", "msr":
  102. return applyMaxSrcResolutionOption(p, o.Main(), args)
  103. case "max_src_file_size", "msfs":
  104. return applyMaxSrcFileSizeOption(p, o.Main(), args)
  105. case "max_animation_frames", "maf":
  106. return applyMaxAnimationFramesOption(p, o.Main(), args)
  107. case "max_animation_frame_resolution", "mafr":
  108. return applyMaxAnimationFrameResolutionOption(p, o.Main(), args)
  109. case "max_result_dimension", "mrd":
  110. return applyMaxResultDimensionOption(p, o.Main(), args)
  111. }
  112. return newUnknownOptionError("processing", name)
  113. }
  114. func (p *Parser) applyURLOptions(
  115. o *options.Options,
  116. options urlOptions,
  117. allowAll bool,
  118. usedPresets ...string,
  119. ) error {
  120. allowAll = allowAll || len(p.config.AllowedProcessingOptions) == 0
  121. for _, opt := range options {
  122. if !allowAll && !slices.Contains(p.config.AllowedProcessingOptions, opt.Name) {
  123. return newForbiddenOptionError("processing", opt.Name)
  124. }
  125. if err := p.applyURLOption(o, opt.Name, opt.Args, usedPresets...); err != nil {
  126. return err
  127. }
  128. }
  129. return nil
  130. }
  131. func (p *Parser) defaultProcessingOptions(headers http.Header) (*options.Options, error) {
  132. o := options.New()
  133. headerAccept := headers.Get("Accept")
  134. if (p.config.AutoWebp || p.config.EnforceWebp) && strings.Contains(headerAccept, "image/webp") {
  135. o.Set(keys.PreferWebP, true)
  136. if p.config.EnforceWebp {
  137. o.Set(keys.EnforceWebP, true)
  138. }
  139. }
  140. if (p.config.AutoAvif || p.config.EnforceAvif) && strings.Contains(headerAccept, "image/avif") {
  141. o.Set(keys.PreferAvif, true)
  142. if p.config.EnforceAvif {
  143. o.Set(keys.EnforceAvif, true)
  144. }
  145. }
  146. if (p.config.AutoJxl || p.config.EnforceJxl) && strings.Contains(headerAccept, "image/jxl") {
  147. o.Set(keys.PreferJxl, true)
  148. if p.config.EnforceJxl {
  149. o.Set(keys.EnforceJxl, true)
  150. }
  151. }
  152. if p.config.EnableClientHints {
  153. dpr := 1.0
  154. headerDPR := headers.Get("Sec-CH-DPR")
  155. if len(headerDPR) == 0 {
  156. headerDPR = headers.Get("DPR")
  157. }
  158. if len(headerDPR) > 0 {
  159. if d, err := strconv.ParseFloat(headerDPR, 64); err == nil && (d > 0 && d <= maxClientHintDPR) {
  160. dpr = d
  161. o.Set(keys.Dpr, dpr)
  162. }
  163. }
  164. headerWidth := headers.Get("Sec-CH-Width")
  165. if len(headerWidth) == 0 {
  166. headerWidth = headers.Get("Width")
  167. }
  168. if len(headerWidth) > 0 {
  169. if w, err := strconv.Atoi(headerWidth); err == nil {
  170. o.Set(keys.Width, imath.Shrink(w, dpr))
  171. }
  172. }
  173. }
  174. if _, ok := p.presets["default"]; ok {
  175. if err := applyPresetOption(p, o, []string{"default"}); err != nil {
  176. return o, err
  177. }
  178. }
  179. return o, nil
  180. }
  181. // ParsePath parses the given request path and returns the processing options and image URL
  182. func (p *Parser) ParsePath(
  183. path string,
  184. headers http.Header,
  185. ) (o *options.Options, imageURL string, err error) {
  186. if path == "" || path == "/" {
  187. return nil, "", newInvalidURLError("invalid path: %s", path)
  188. }
  189. parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
  190. if p.config.OnlyPresets {
  191. o, imageURL, err = p.parsePathPresets(parts, headers)
  192. } else {
  193. o, imageURL, err = p.parsePathOptions(parts, headers)
  194. }
  195. if err != nil {
  196. return nil, "", ierrors.Wrap(err, 0)
  197. }
  198. return o, imageURL, nil
  199. }
  200. // parsePathOptions parses processing options from the URL path
  201. func (p *Parser) parsePathOptions(
  202. parts []string,
  203. headers http.Header,
  204. ) (*options.Options, string, error) {
  205. if _, ok := processing.ResizeTypes[parts[0]]; ok {
  206. return nil, "", newInvalidURLError("It looks like you're using the deprecated basic URL format")
  207. }
  208. o, err := p.defaultProcessingOptions(headers)
  209. if err != nil {
  210. return nil, "", err
  211. }
  212. urlOpts, urlParts := p.parseURLOptions(parts)
  213. if err = p.applyURLOptions(o, urlOpts, false); err != nil {
  214. return nil, "", err
  215. }
  216. url, extension, err := p.DecodeURL(urlParts)
  217. if err != nil {
  218. return nil, "", err
  219. }
  220. if !options.Get(o, keys.Raw, false) && len(extension) > 0 {
  221. if err = applyFormatOption(o, []string{extension}); err != nil {
  222. return nil, "", err
  223. }
  224. }
  225. return o, url, nil
  226. }
  227. // parsePathPresets parses presets from the URL path
  228. func (p *Parser) parsePathPresets(parts []string, headers http.Header) (*options.Options, string, error) {
  229. o, err := p.defaultProcessingOptions(headers)
  230. if err != nil {
  231. return nil, "", err
  232. }
  233. presets := strings.Split(parts[0], p.config.ArgumentsSeparator)
  234. urlParts := parts[1:]
  235. if err = applyPresetOption(p, o, presets); err != nil {
  236. return nil, "", err
  237. }
  238. url, extension, err := p.DecodeURL(urlParts)
  239. if err != nil {
  240. return nil, "", err
  241. }
  242. if !options.Get(o, keys.Raw, false) && len(extension) > 0 {
  243. if err = applyFormatOption(o, []string{extension}); err != nil {
  244. return nil, "", err
  245. }
  246. }
  247. return o, url, nil
  248. }