mirror of
https://github.com/imgproxy/imgproxy.git
synced 2025-12-23 22:11:10 +02:00
289 lines
7.6 KiB
Go
289 lines
7.6 KiB
Go
package optionsparser
|
|
|
|
import (
|
|
"net/http"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/imgproxy/imgproxy/v3/ierrors"
|
|
"github.com/imgproxy/imgproxy/v3/imath"
|
|
"github.com/imgproxy/imgproxy/v3/options"
|
|
"github.com/imgproxy/imgproxy/v3/options/keys"
|
|
"github.com/imgproxy/imgproxy/v3/processing"
|
|
)
|
|
|
|
const maxClientHintDPR = 8
|
|
|
|
func (p *Parser) applyURLOption(
|
|
o *options.Options,
|
|
name string,
|
|
args []string,
|
|
usedPresets ...string,
|
|
) error {
|
|
switch name {
|
|
case "resize", "rs":
|
|
return p.applyResizeOption(o, args)
|
|
case "size", "s":
|
|
return p.applySizeOption(o, args)
|
|
case "resizing_type", "rt":
|
|
return p.applyResizingTypeOption(o, args)
|
|
case "width", "w":
|
|
return p.applyWidthOption(o, args)
|
|
case "height", "h":
|
|
return p.applyHeightOption(o, args)
|
|
case "min-width", "mw":
|
|
return p.applyMinWidthOption(o, args)
|
|
case "min-height", "mh":
|
|
return p.applyMinHeightOption(o, args)
|
|
case "zoom", "z":
|
|
return p.applyZoomOption(o, args)
|
|
case "dpr":
|
|
return p.applyDprOption(o, args)
|
|
case "enlarge", "el":
|
|
return p.applyEnlargeOption(o, args)
|
|
case "extend", "ex":
|
|
return p.applyExtendOption(o, args)
|
|
case "extend_aspect_ratio", "extend_ar", "exar":
|
|
return p.applyExtendAspectRatioOption(o, args)
|
|
case "gravity", "g":
|
|
return p.applyGravityOption(o, args)
|
|
case "crop", "c":
|
|
return p.applyCropOption(o, args)
|
|
case "trim", "t":
|
|
return p.applyTrimOption(o, args)
|
|
case "padding", "pd":
|
|
return p.applyPaddingOption(o, args)
|
|
case "auto_rotate", "ar":
|
|
return p.applyAutoRotateOption(o, args)
|
|
case "rotate", "rot":
|
|
return p.applyRotateOption(o, args)
|
|
case "background", "bg":
|
|
return p.applyBackgroundOption(o, args)
|
|
case "blur", "bl":
|
|
return p.applyBlurOption(o, args)
|
|
case "sharpen", "sh":
|
|
return p.applySharpenOption(o, args)
|
|
case "pixelate", "pix":
|
|
return p.applyPixelateOption(o, args)
|
|
case "watermark", "wm":
|
|
return p.applyWatermarkOption(o, args)
|
|
case "strip_metadata", "sm":
|
|
return p.applyStripMetadataOption(o.Main(), args)
|
|
case "keep_copyright", "kcr":
|
|
return p.applyKeepCopyrightOption(o.Main(), args)
|
|
case "strip_color_profile", "scp":
|
|
return p.applyStripColorProfileOption(o.Main(), args)
|
|
case "enforce_thumbnail", "eth":
|
|
return p.applyEnforceThumbnailOption(o.Main(), args)
|
|
// Saving options
|
|
case "quality", "q":
|
|
return p.applyQualityOption(o.Main(), args)
|
|
case "format_quality", "fq":
|
|
return p.applyFormatQualityOption(o.Main(), args)
|
|
case "max_bytes", "mb":
|
|
return p.applyMaxBytesOption(o.Main(), args)
|
|
case "format", "f", "ext":
|
|
return p.applyFormatOption(o.Main(), args)
|
|
// Handling options
|
|
case "skip_processing", "skp":
|
|
return p.applySkipProcessingFormatsOption(o.Main(), args)
|
|
case "raw":
|
|
return p.applyRawOption(o.Main(), args)
|
|
case "cachebuster", "cb":
|
|
return p.applyCacheBusterOption(o.Main(), args)
|
|
case "expires", "exp":
|
|
return p.applyExpiresOption(o.Main(), args)
|
|
case "filename", "fn":
|
|
return p.applyFilenameOption(o.Main(), args)
|
|
case "return_attachment", "att":
|
|
return p.applyReturnAttachmentOption(o.Main(), args)
|
|
// Presets
|
|
case "preset", "pr":
|
|
return p.applyPresetOption(o, args, usedPresets...)
|
|
// Security
|
|
case "max_src_resolution", "msr":
|
|
return p.applyMaxSrcResolutionOption(o, args)
|
|
case "max_src_file_size", "msfs":
|
|
return p.applyMaxSrcFileSizeOption(o, args)
|
|
case "max_animation_frames", "maf":
|
|
return p.applyMaxAnimationFramesOption(o.Main(), args)
|
|
case "max_animation_frame_resolution", "mafr":
|
|
return p.applyMaxAnimationFrameResolutionOption(o.Main(), args)
|
|
case "max_result_dimension", "mrd":
|
|
return p.applyMaxResultDimensionOption(o.Main(), args)
|
|
}
|
|
|
|
return newUnknownOptionError("processing", name)
|
|
}
|
|
|
|
func (p *Parser) applyURLOptions(
|
|
o *options.Options,
|
|
options urlOptions,
|
|
allowAll bool,
|
|
usedPresets ...string,
|
|
) error {
|
|
allowAll = allowAll || len(p.config.AllowedProcessingOptions) == 0
|
|
|
|
for _, opt := range options {
|
|
if !allowAll && !slices.Contains(p.config.AllowedProcessingOptions, opt.Name) {
|
|
return newForbiddenOptionError("processing", opt.Name)
|
|
}
|
|
|
|
if err := p.applyURLOption(o, opt.Name, opt.Args, usedPresets...); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) defaultProcessingOptions(headers http.Header) (*options.Options, error) {
|
|
o := options.New()
|
|
|
|
headerAccept := headers.Get("Accept")
|
|
|
|
if (p.config.AutoWebp || p.config.EnforceWebp) && strings.Contains(headerAccept, "image/webp") {
|
|
o.Set(keys.PreferWebP, true)
|
|
|
|
if p.config.EnforceWebp {
|
|
o.Set(keys.EnforceWebP, true)
|
|
}
|
|
}
|
|
|
|
if (p.config.AutoAvif || p.config.EnforceAvif) && strings.Contains(headerAccept, "image/avif") {
|
|
o.Set(keys.PreferAvif, true)
|
|
|
|
if p.config.EnforceAvif {
|
|
o.Set(keys.EnforceAvif, true)
|
|
}
|
|
}
|
|
|
|
if (p.config.AutoJxl || p.config.EnforceJxl) && strings.Contains(headerAccept, "image/jxl") {
|
|
o.Set(keys.PreferJxl, true)
|
|
|
|
if p.config.EnforceJxl {
|
|
o.Set(keys.EnforceJxl, true)
|
|
}
|
|
}
|
|
|
|
if p.config.EnableClientHints {
|
|
dpr := 1.0
|
|
|
|
headerDPR := headers.Get("Sec-CH-DPR")
|
|
if len(headerDPR) == 0 {
|
|
headerDPR = headers.Get("DPR")
|
|
}
|
|
if len(headerDPR) > 0 {
|
|
if d, err := strconv.ParseFloat(headerDPR, 64); err == nil && (d > 0 && d <= maxClientHintDPR) {
|
|
dpr = d
|
|
o.Set(keys.Dpr, dpr)
|
|
}
|
|
}
|
|
|
|
headerWidth := headers.Get("Sec-CH-Width")
|
|
if len(headerWidth) == 0 {
|
|
headerWidth = headers.Get("Width")
|
|
}
|
|
if len(headerWidth) > 0 {
|
|
if w, err := strconv.Atoi(headerWidth); err == nil {
|
|
o.Set(keys.Width, imath.Shrink(w, dpr))
|
|
}
|
|
}
|
|
}
|
|
|
|
if _, ok := p.presets["default"]; ok {
|
|
if err := p.applyPresetOption(o, []string{"default"}); err != nil {
|
|
return o, err
|
|
}
|
|
}
|
|
|
|
return o, nil
|
|
}
|
|
|
|
// ParsePath parses the given request path and returns the processing options and image URL
|
|
func (p *Parser) ParsePath(
|
|
path string,
|
|
headers http.Header,
|
|
) (o *options.Options, imageURL string, err error) {
|
|
if path == "" || path == "/" {
|
|
return nil, "", newInvalidURLError("invalid path: %s", path)
|
|
}
|
|
|
|
parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
|
|
|
|
if p.config.OnlyPresets {
|
|
o, imageURL, err = p.parsePathPresets(parts, headers)
|
|
} else {
|
|
o, imageURL, err = p.parsePathOptions(parts, headers)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, "", ierrors.Wrap(err, 0)
|
|
}
|
|
|
|
return o, imageURL, nil
|
|
}
|
|
|
|
// parsePathOptions parses processing options from the URL path
|
|
func (p *Parser) parsePathOptions(
|
|
parts []string,
|
|
headers http.Header,
|
|
) (*options.Options, string, error) {
|
|
if _, ok := processing.ResizeTypes[parts[0]]; ok {
|
|
return nil, "", newInvalidURLError("It looks like you're using the deprecated basic URL format")
|
|
}
|
|
|
|
o, err := p.defaultProcessingOptions(headers)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
urlOpts, urlParts := p.parseURLOptions(parts)
|
|
|
|
if err = p.applyURLOptions(o, urlOpts, false); err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
url, extension, err := p.DecodeURL(urlParts)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
if !options.Get(o, keys.Raw, false) && len(extension) > 0 {
|
|
if err = p.applyFormatOption(o, []string{extension}); err != nil {
|
|
return nil, "", err
|
|
}
|
|
}
|
|
|
|
return o, url, nil
|
|
}
|
|
|
|
// parsePathPresets parses presets from the URL path
|
|
func (p *Parser) parsePathPresets(parts []string, headers http.Header) (*options.Options, string, error) {
|
|
o, err := p.defaultProcessingOptions(headers)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
presets := strings.Split(parts[0], p.config.ArgumentsSeparator)
|
|
urlParts := parts[1:]
|
|
|
|
if err = p.applyPresetOption(o, presets); err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
url, extension, err := p.DecodeURL(urlParts)
|
|
if err != nil {
|
|
return nil, "", err
|
|
}
|
|
|
|
if !options.Get(o, keys.Raw, false) && len(extension) > 0 {
|
|
if err = p.applyFormatOption(o, []string{extension}); err != nil {
|
|
return nil, "", err
|
|
}
|
|
}
|
|
|
|
return o, url, nil
|
|
}
|