package options import ( "encoding/base64" "fmt" "log/slog" "slices" "strconv" "time" "github.com/imgproxy/imgproxy/v3/imagetype" "github.com/imgproxy/imgproxy/v3/options/keys" "github.com/imgproxy/imgproxy/v3/vips/color" ) func applyWidthOption(o *Options, args []string) error { return parsePositiveInt(o, keys.Width, args...) } func applyHeightOption(o *Options, args []string) error { return parsePositiveInt(o, keys.Height, args...) } func applyMinWidthOption(o *Options, args []string) error { return parsePositiveInt(o, keys.MinWidth, args...) } func applyMinHeightOption(o *Options, args []string) error { return parsePositiveInt(o, keys.MinHeight, args...) } func applyEnlargeOption(o *Options, args []string) error { return parseBool(o, keys.Enlarge, args...) } func applyExtendOption(o *Options, args []string) error { return parseExtend(o, keys.PrefixExtend, args) } func applyExtendAspectRatioOption(o *Options, args []string) error { return parseExtend(o, keys.PrefixExtendAspectRatio, args) } func applySizeOption(o *Options, args []string) (err error) { if err = ensureMaxArgs("size", args, 7); err != nil { return } if len(args) >= 1 && len(args[0]) > 0 { if err = applyWidthOption(o, args[0:1]); err != nil { return } } if len(args) >= 2 && len(args[1]) > 0 { if err = applyHeightOption(o, args[1:2]); err != nil { return } } if len(args) >= 3 && len(args[2]) > 0 { if err = applyEnlargeOption(o, args[2:3]); err != nil { return } } if len(args) >= 4 && len(args[3]) > 0 { if err = applyExtendOption(o, args[3:]); err != nil { return } } return nil } func applyResizingTypeOption(o *Options, args []string) error { if err := ensureMaxArgs(keys.ResizingType, args, 1); err != nil { return err } if r, ok := resizeTypes[args[0]]; ok { o.Set(keys.ResizingType, r) } else { return newOptionArgumentError("Invalid %s: %s", keys.ResizingType, args[0]) } return nil } func applyResizeOption(o *Options, args []string) error { if err := ensureMaxArgs("resize", args, 8); err != nil { return err } if len(args[0]) > 0 { if err := applyResizingTypeOption(o, args[0:1]); err != nil { return err } } if len(args) > 1 { if err := applySizeOption(o, args[1:]); err != nil { return err } } return nil } func applyZoomOption(o *Options, args []string) error { if err := ensureMaxArgs("zoom", args, 2); err != nil { return err } if err := parsePositiveNonZeroFloat(o, keys.ZoomWidth, args[0]); err != nil { return err } if len(args) < 2 { o.CopyValue(keys.ZoomWidth, keys.ZoomHeight) return nil } if err := parsePositiveNonZeroFloat(o, keys.ZoomHeight, args[1]); err != nil { return err } return nil } func applyDprOption(o *Options, args []string) error { return parsePositiveNonZeroFloat(o, keys.Dpr, args...) } func applyGravityOption(o *Options, args []string) error { return parseGravity(o, keys.Gravity, args, cropGravityTypes) } func applyCropOption(o *Options, args []string) error { if err := parsePositiveFloat(o, keys.CropWidth, args[0]); err != nil { return err } if len(args) > 1 { if err := parsePositiveFloat(o, keys.CropHeight, args[1]); err != nil { return err } } if len(args) > 2 { return parseGravity(o, keys.CropGravity, args[2:], cropGravityTypes) } return nil } func applyPaddingOption(o *Options, args []string) error { if err := ensureMaxArgs("padding", args, 4); err != nil { return err } if len(args) > 0 && len(args[0]) > 0 { if err := parsePositiveInt(o, keys.PaddingTop, args[0]); err != nil { return err } } if len(args) > 1 && len(args[1]) > 0 { if err := parsePositiveInt(o, keys.PaddingRight, args[1]); err != nil { return err } } else { o.CopyValue(keys.PaddingTop, keys.PaddingRight) } if len(args) > 2 && len(args[2]) > 0 { if err := parsePositiveInt(o, keys.PaddingBottom, args[2]); err != nil { return err } } else { o.CopyValue(keys.PaddingTop, keys.PaddingBottom) } if len(args) > 3 && len(args[3]) > 0 { if err := parsePositiveInt(o, keys.PaddingLeft, args[3]); err != nil { return err } } else { o.CopyValue(keys.PaddingRight, keys.PaddingLeft) } return nil } func applyTrimOption(o *Options, args []string) error { if err := ensureMaxArgs("trim", args, 4); err != nil { return err } nArgs := len(args) if len(args[0]) > 0 { if err := parseFloat(o, keys.TrimThreshold, args[0]); err != nil { return err } } else { o.Delete(keys.TrimThreshold) } if nArgs > 1 && len(args[1]) > 0 { if c, err := color.RGBFromHex(args[1]); err == nil { o.Set(keys.TrimColor, c) } else { return newOptionArgumentError("Invalid %s: %s", keys.TrimColor, args[1]) } } else { o.Delete(keys.TrimColor) } if nArgs > 2 && len(args[2]) > 0 { if err := parseBool(o, keys.TrimEqualHor, args[2]); err != nil { return err } } if nArgs > 3 && len(args[3]) > 0 { if err := parseBool(o, keys.TrimEqualVer, args[3]); err != nil { return err } } return nil } func applyRotateOption(o *Options, args []string) error { if err := parseInt(o, keys.Rotate, args...); err != nil { return err } if Get(o, keys.Rotate, 0)%90 != 0 { return newOptionArgumentError("Rotation angle must be a multiple of 90") } return nil } func applyQualityOption(o *Options, args []string) error { return parseQualityInt(o, keys.Quality, args...) } func applyFormatQualityOption(o *Options, args []string) error { argsLen := len(args) if len(args)%2 != 0 { return newOptionArgumentError("Missing %s for: %s", keys.PrefixFormatQuality, args[argsLen-1]) } for i := 0; i < argsLen; i += 2 { f, ok := imagetype.GetTypeByName(args[i]) if !ok { return newOptionArgumentError("Invalid image format: %s", args[i]) } if err := parseQualityInt(o, keys.FormatQuality(f), args[i+1]); err != nil { return err } } return nil } func applyMaxBytesOption(o *Options, args []string) error { return parsePositiveInt(o, keys.MaxBytes, args...) } func applyBackgroundOption(o *Options, args []string) error { switch len(args) { case 1: if len(args[0]) == 0 { o.Delete(keys.Background) return nil } if c, err := color.RGBFromHex(args[0]); err == nil { o.Set(keys.Background, c) } else { return newOptionArgumentError("Invalid %s argument: %s", keys.Background, err) } case 3: var c color.RGB if r, err := strconv.ParseUint(args[0], 10, 8); err == nil && r <= 255 { c.R = uint8(r) } else { return newOptionArgumentError("Invalid %s red channel: %s", keys.Background, args[0]) } if g, err := strconv.ParseUint(args[1], 10, 8); err == nil && g <= 255 { c.G = uint8(g) } else { return newOptionArgumentError("Invalid %s green channel: %s", keys.Background, args[1]) } if b, err := strconv.ParseUint(args[2], 10, 8); err == nil && b <= 255 { c.B = uint8(b) } else { return newOptionArgumentError("Invalid %s blue channel: %s", keys.Background, args[2]) } o.Set(keys.Background, c) default: return newOptionArgumentError("Invalid %s arguments: %v", keys.Background, args) } return nil } func applyBlurOption(o *Options, args []string) error { return parsePositiveNonZeroFloat(o, keys.Blur, args...) } func applySharpenOption(o *Options, args []string) error { return parsePositiveNonZeroFloat(o, keys.Sharpen, args...) } func applyPixelateOption(o *Options, args []string) error { return parsePositiveInt(o, keys.Pixelate, args...) } func applyWatermarkOption(o *Options, args []string) error { if err := ensureMaxArgs("watermark", args, 7); err != nil { return err } if wo, err := strconv.ParseFloat(args[0], 64); err == nil && wo >= 0 && wo <= 1 { o.Set(keys.WatermarkOpacity, wo) } else { return newOptionArgumentError("Invalid %s: %s", keys.WatermarkOpacity, args[0]) } if len(args) > 1 && len(args[1]) > 0 { if pos, ok := gravityTypes[args[1]]; ok && slices.Contains(watermarkGravityTypes, pos) { o.Set(keys.WatermarkPosition, pos) } else { return newOptionArgumentError("Invalid %s: %s", keys.WatermarkPosition, args[1]) } } if len(args) > 2 && len(args[2]) > 0 { if err := parseFloat(o, keys.WatermarkXOffset, args[2]); err != nil { return err } } if len(args) > 3 && len(args[3]) > 0 { if err := parseFloat(o, keys.WatermarkYOffset, args[3]); err != nil { return err } } if len(args) > 4 && len(args[4]) > 0 { if err := parsePositiveNonZeroFloat(o, keys.WatermarkScale, args[4]); err == nil { return err } } return nil } func applyFormatOption(o *Options, args []string) error { if err := ensureMaxArgs(keys.Format, args, 1); err != nil { return err } if f, ok := imagetype.GetTypeByName(args[0]); ok { o.Set(keys.Format, f) } else { return newOptionArgumentError("Invalid image format: %s", args[0]) } return nil } func applyCacheBusterOption(o *Options, args []string) error { if err := ensureMaxArgs(keys.CacheBuster, args, 1); err != nil { return err } o.Set(keys.CacheBuster, args[0]) return nil } func applySkipProcessingFormatsOption(o *Options, args []string) error { for _, format := range args { if f, ok := imagetype.GetTypeByName(format); ok { AppendToSlice(o, keys.SkipProcessing, f) } else { return newOptionArgumentError("Invalid image format in %s: %s", keys.SkipProcessing, format) } } return nil } func applyRawOption(o *Options, args []string) error { return parseBool(o, keys.Raw, args...) } func applyFilenameOption(o *Options, args []string) error { if err := ensureMaxArgs(keys.Filename, args, 2); err != nil { return err } filename := args[0] if len(args) > 1 && len(args[1]) > 0 { if encoded, _ := strconv.ParseBool(args[1]); encoded { if decoded, err := base64.RawURLEncoding.DecodeString(filename); err == nil { filename = string(decoded) } else { return newOptionArgumentError("Invalid %s encoding: %s", keys.Filename, err) } } } o.Set(keys.Filename, filename) return nil } func applyExpiresOption(o *Options, args []string) error { if err := ensureMaxArgs(keys.Expires, args, 1); err != nil { return err } timestamp, err := strconv.ParseInt(args[0], 10, 64) if err != nil { return newOptionArgumentError("Invalid %s argument: %v", keys.Expires, args[0]) } if timestamp > 0 && timestamp < time.Now().Unix() { return newOptionArgumentError("Expired URL") } o.Set(keys.Expires, time.Unix(timestamp, 0)) return nil } func applyStripMetadataOption(o *Options, args []string) error { return parseBool(o, keys.StripMetadata, args...) } func applyKeepCopyrightOption(o *Options, args []string) error { return parseBool(o, keys.KeepCopyright, args...) } func applyStripColorProfileOption(o *Options, args []string) error { return parseBool(o, keys.StripColorProfile, args...) } func applyAutoRotateOption(o *Options, args []string) error { return parseBool(o, keys.AutoRotate, args...) } func applyEnforceThumbnailOption(o *Options, args []string) error { return parseBool(o, keys.EnforceThumbnail, args...) } func applyReturnAttachmentOption(o *Options, args []string) error { return parseBool(o, keys.ReturnAttachment, args...) } func applyMaxSrcResolutionOption(p *Parser, o *Options, args []string) error { if err := p.IsSecurityOptionsAllowed(); err != nil { return err } return parseResolution(o, keys.MaxSrcResolution, args...) } func applyMaxSrcFileSizeOption(p *Parser, o *Options, args []string) error { if err := p.IsSecurityOptionsAllowed(); err != nil { return err } return parseInt(o, keys.MaxSrcFileSize, args...) } func applyMaxAnimationFramesOption(p *Parser, o *Options, args []string) error { if err := p.IsSecurityOptionsAllowed(); err != nil { return err } return parsePositiveNonZeroInt(o, keys.MaxAnimationFrames, args...) } func applyMaxAnimationFrameResolutionOption(p *Parser, o *Options, args []string) error { if err := p.IsSecurityOptionsAllowed(); err != nil { return err } return parseResolution(o, keys.MaxAnimationFrameResolution, args...) } func applyMaxResultDimensionOption(p *Parser, o *Options, args []string) error { if err := p.IsSecurityOptionsAllowed(); err != nil { return err } return parseInt(o, keys.MaxResultDimension, args...) } func applyPresetOption(p *Parser, o *Options, args []string, usedPresets ...string) error { for _, preset := range args { if pr, ok := p.presets[preset]; ok { if slices.Contains(usedPresets, preset) { slog.Warn(fmt.Sprintf("Recursive preset usage is detected: %s", preset)) continue } AppendToSlice(o, keys.UsedPresets, preset) if err := p.applyURLOptions(o, pr, true, append(usedPresets, preset)...); err != nil { return err } } else { return newOptionArgumentError("Unknown preset: %s", preset) } } return nil }