You've already forked imgproxy
mirror of
https://github.com/imgproxy/imgproxy.git
synced 2026-06-19 23:24:30 +02:00
566 lines
15 KiB
Go
566 lines
15 KiB
Go
package optionsparser
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log/slog"
|
|
"slices"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/imgproxy/imgproxy/v4/imagetype"
|
|
"github.com/imgproxy/imgproxy/v4/options"
|
|
"github.com/imgproxy/imgproxy/v4/options/keys"
|
|
"github.com/imgproxy/imgproxy/v4/processing"
|
|
"github.com/imgproxy/imgproxy/v4/vips/color"
|
|
)
|
|
|
|
func (p *Parser) applyWidthOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parsePositiveInt(ctx, o, keys.Width, args...)
|
|
}
|
|
|
|
func (p *Parser) applyHeightOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parsePositiveInt(ctx, o, keys.Height, args...)
|
|
}
|
|
|
|
func (p *Parser) applyMinWidthOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parsePositiveInt(ctx, o, keys.MinWidth, args...)
|
|
}
|
|
|
|
func (p *Parser) applyMinHeightOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parsePositiveInt(ctx, o, keys.MinHeight, args...)
|
|
}
|
|
|
|
func (p *Parser) applyEnlargeOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parseBool(ctx, o, keys.Enlarge, args...)
|
|
}
|
|
|
|
func (p *Parser) applyExtendOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parseExtend(ctx, o, keys.PrefixExtend, args)
|
|
}
|
|
|
|
func (p *Parser) applyExtendAspectRatioOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parseExtend(ctx, o, keys.PrefixExtendAspectRatio, args)
|
|
}
|
|
|
|
func (p *Parser) applySizeOption(ctx context.Context, o *options.Options, args []string) (err error) {
|
|
if err = p.ensureMaxArgs(ctx, "size", args, 7); err != nil {
|
|
return
|
|
}
|
|
|
|
if len(args) >= 1 && len(args[0]) > 0 {
|
|
if err = p.applyWidthOption(ctx, o, args[0:1]); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
if len(args) >= 2 && len(args[1]) > 0 {
|
|
if err = p.applyHeightOption(ctx, o, args[1:2]); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
if len(args) >= 3 && len(args[2]) > 0 {
|
|
if err = p.applyEnlargeOption(ctx, o, args[2:3]); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
if len(args) >= 4 && len(args[3]) > 0 {
|
|
if err = p.applyExtendOption(ctx, o, args[3:]); err != nil {
|
|
return
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) applyResizingTypeOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return parseFromMap(ctx, p, o, keys.ResizingType, processing.ResizeTypes, args...)
|
|
}
|
|
|
|
func (p *Parser) applyResizeOption(ctx context.Context, o *options.Options, args []string) error {
|
|
if err := p.ensureMaxArgs(ctx, "resize", args, 8); err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(args[0]) > 0 {
|
|
if err := p.applyResizingTypeOption(ctx, o, args[0:1]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(args) > 1 {
|
|
if err := p.applySizeOption(ctx, o, args[1:]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) applyZoomOption(ctx context.Context, o *options.Options, args []string) error {
|
|
if err := p.ensureMaxArgs(ctx, "zoom", args, 2); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := p.parsePositiveNonZeroFloat(ctx, o, keys.ZoomWidth, args[0]); err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(args) < 2 {
|
|
o.CopyValue(keys.ZoomWidth, keys.ZoomHeight)
|
|
return nil
|
|
}
|
|
|
|
if err := p.parsePositiveNonZeroFloat(ctx, o, keys.ZoomHeight, args[1]); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) applyDprOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parsePositiveNonZeroFloat(ctx, o, keys.Dpr, args...)
|
|
}
|
|
|
|
func (p *Parser) applyGravityOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parseGravity(ctx, o, keys.Gravity, processing.CropGravityTypes, args...)
|
|
}
|
|
|
|
func (p *Parser) applyCropOption(ctx context.Context, o *options.Options, args []string) error {
|
|
if err := p.parsePositiveFloat(ctx, o, keys.CropWidth, args[0]); err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(args) > 1 {
|
|
if err := p.parsePositiveFloat(ctx, o, keys.CropHeight, args[1]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(args) > 2 {
|
|
return p.parseGravity(ctx, o, keys.CropGravity, processing.CropGravityTypes, args[2:]...)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) applyPaddingOption(ctx context.Context, o *options.Options, args []string) error {
|
|
if err := p.ensureMaxArgs(ctx, "padding", args, 4); err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(args) > 0 && len(args[0]) > 0 {
|
|
if err := p.parsePositiveInt(ctx, o, keys.PaddingTop, args[0]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(args) > 1 && len(args[1]) > 0 {
|
|
if err := p.parsePositiveInt(ctx, 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 := p.parsePositiveInt(ctx, 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 := p.parsePositiveInt(ctx, o, keys.PaddingLeft, args[3]); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
o.CopyValue(keys.PaddingRight, keys.PaddingLeft)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) applyTrimOption(ctx context.Context, o *options.Options, args []string) error {
|
|
if err := p.ensureMaxArgs(ctx, "trim", args, 4); err != nil {
|
|
return err
|
|
}
|
|
|
|
nArgs := len(args)
|
|
|
|
if len(args[0]) > 0 {
|
|
if err := p.parseFloat(ctx, o, keys.TrimThreshold, args[0]); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
o.Delete(keys.TrimThreshold)
|
|
}
|
|
|
|
if nArgs > 1 && len(args[1]) > 0 {
|
|
if err := p.parseHexRGBColor(ctx, o, keys.TrimColor, args[1]); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
o.Delete(keys.TrimColor)
|
|
}
|
|
|
|
if nArgs > 2 && len(args[2]) > 0 {
|
|
if err := p.parseBool(ctx, o, keys.TrimEqualHor, args[2]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if nArgs > 3 && len(args[3]) > 0 {
|
|
if err := p.parseBool(ctx, o, keys.TrimEqualVer, args[3]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) applyRotateOption(ctx context.Context, o *options.Options, args []string) error {
|
|
if err := p.parseInt(ctx, o, keys.Rotate, args...); err != nil {
|
|
return err
|
|
}
|
|
|
|
if options.Get(o, keys.Rotate, 0)%90 != 0 {
|
|
return newOptionArgumentError(ctx, keys.Rotate, "Rotation angle must be a multiple of 90")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) applyFlipOption(ctx context.Context, o *options.Options, args []string) error {
|
|
if err := p.ensureMaxArgs(ctx, "flip", args, 2); err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(args[0]) > 0 {
|
|
if err := p.parseBool(ctx, o, keys.FlipHorizontal, args[0]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(args) > 1 && len(args[1]) > 0 {
|
|
if err := p.parseBool(ctx, o, keys.FlipVertical, args[1]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) applyQualityOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parseQualityInt(ctx, o, keys.Quality, 0, args...)
|
|
}
|
|
|
|
func (p *Parser) applyFormatQualityOption(ctx context.Context, o *options.Options, args []string) error {
|
|
argsLen := len(args)
|
|
if len(args)%2 != 0 {
|
|
return newOptionArgumentError(
|
|
ctx,
|
|
keys.PrefixFormatQuality,
|
|
"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(ctx, keys.PrefixFormatQuality, "Invalid image format: %s", args[i])
|
|
}
|
|
|
|
if err := p.parseQualityInt(ctx, o, keys.FormatQuality(f), 0, args[i+1]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) applyMaxBytesOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parsePositiveInt(ctx, o, keys.MaxBytes, args...)
|
|
}
|
|
|
|
func (p *Parser) applyBackgroundOption(ctx context.Context, o *options.Options, args []string) error {
|
|
switch len(args) {
|
|
case 1:
|
|
if len(args[0]) == 0 {
|
|
o.Delete(keys.Background)
|
|
return nil
|
|
}
|
|
|
|
if err := p.parseHexRGBColor(ctx, o, keys.Background, args[0]); err != nil {
|
|
return 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 newInvalidArgumentError(ctx, keys.Background+".R", args[0], "number in range 0-255")
|
|
}
|
|
|
|
if g, err := strconv.ParseUint(args[1], 10, 8); err == nil && g <= 255 {
|
|
c.G = uint8(g)
|
|
} else {
|
|
return newInvalidArgumentError(ctx, keys.Background+".G", args[1], "number in range 0-255")
|
|
}
|
|
|
|
if b, err := strconv.ParseUint(args[2], 10, 8); err == nil && b <= 255 {
|
|
c.B = uint8(b)
|
|
} else {
|
|
return newInvalidArgumentError(ctx, keys.Background+".B", args[2], "number in range 0-255")
|
|
}
|
|
|
|
o.Set(keys.Background, c)
|
|
|
|
default:
|
|
return newInvalidArgsError(ctx, keys.Background, args)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) applyBlurOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parsePositiveFloat(ctx, o, keys.Blur, args...)
|
|
}
|
|
|
|
func (p *Parser) applySharpenOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parsePositiveFloat(ctx, o, keys.Sharpen, args...)
|
|
}
|
|
|
|
func (p *Parser) applyPixelateOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parsePositiveInt(ctx, o, keys.Pixelate, args...)
|
|
}
|
|
|
|
func (p *Parser) applyWatermarkOption(ctx context.Context, o *options.Options, args []string) error {
|
|
if err := p.ensureMaxArgs(ctx, "watermark", args, 7); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := p.parseOpacityFloat(ctx, o, keys.WatermarkOpacity, args[0]); err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(args) > 1 && len(args[1]) > 0 {
|
|
if _, err := p.parseGravityType(
|
|
ctx, o, keys.WatermarkPosition, processing.WatermarkGravityTypes, args[1],
|
|
); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(args) > 2 && len(args[2]) > 0 {
|
|
if err := p.parseFloat(ctx, o, keys.WatermarkXOffset, args[2]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(args) > 3 && len(args[3]) > 0 {
|
|
if err := p.parseFloat(ctx, o, keys.WatermarkYOffset, args[3]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if len(args) > 4 && len(args[4]) > 0 {
|
|
if err := p.parsePositiveNonZeroFloat(ctx, o, keys.WatermarkScale, args[4]); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) applyFormatOption(ctx context.Context, o *options.Options, args []string) error {
|
|
if err := p.ensureMaxArgs(ctx, keys.Format, args, 1); err != nil {
|
|
return err
|
|
}
|
|
|
|
if f, ok := imagetype.GetTypeByName(args[0]); ok {
|
|
o.Set(keys.Format, f)
|
|
} else {
|
|
return newInvalidArgumentError(ctx, keys.Format, args[0], "supported image format")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) applyCacheBusterOption(ctx context.Context, o *options.Options, args []string) error {
|
|
if err := p.ensureMaxArgs(ctx, keys.CacheBuster, args, 1); err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(args[0]) == 0 {
|
|
o.Delete(keys.CacheBuster)
|
|
return nil
|
|
}
|
|
|
|
o.Set(keys.CacheBuster, args[0])
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) applySkipProcessingFormatsOption(ctx context.Context, o *options.Options, args []string) error {
|
|
for _, format := range args {
|
|
if f, ok := imagetype.GetTypeByName(format); ok {
|
|
options.AppendToSlice(o, keys.SkipProcessing, f)
|
|
} else {
|
|
return newOptionArgumentError(
|
|
ctx,
|
|
keys.SkipProcessing,
|
|
"Invalid image format in %s: %s",
|
|
keys.SkipProcessing,
|
|
format,
|
|
)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) applyRawOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parseBool(ctx, o, keys.Raw, args...)
|
|
}
|
|
|
|
func (p *Parser) applyFilenameOption(ctx context.Context, o *options.Options, args []string) error {
|
|
if err := p.ensureMaxArgs(ctx, keys.Filename, args, 2); err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(args) > 1 && len(args[1]) > 0 {
|
|
if encoded, _ := strconv.ParseBool(args[1]); encoded {
|
|
return p.parseBase64String(ctx, o, keys.Filename, args[0])
|
|
}
|
|
}
|
|
|
|
o.Set(keys.Filename, args[0])
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) applyExpiresOption(ctx context.Context, o *options.Options, args []string) error {
|
|
if err := p.ensureMaxArgs(ctx, keys.Expires, args, 1); err != nil {
|
|
return err
|
|
}
|
|
|
|
timestamp, err := strconv.ParseInt(args[0], 10, 64)
|
|
if err != nil {
|
|
return newInvalidArgumentError(ctx, keys.Expires, args[0], "unix timestamp")
|
|
}
|
|
|
|
if timestamp > 0 && timestamp < time.Now().Unix() {
|
|
return newOptionArgumentError(ctx, keys.Expires, "Expired URL")
|
|
}
|
|
|
|
o.Set(keys.Expires, time.Unix(timestamp, 0))
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *Parser) applyStripMetadataOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parseBool(ctx, o, keys.StripMetadata, args...)
|
|
}
|
|
|
|
func (p *Parser) applyKeepCopyrightOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parseBool(ctx, o, keys.KeepCopyright, args...)
|
|
}
|
|
|
|
func (p *Parser) applyStripColorProfileOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parseBool(ctx, o, keys.StripColorProfile, args...)
|
|
}
|
|
|
|
func (p *Parser) applyPreserveHDROption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parseBool(ctx, o, keys.PreserveHDR, args...)
|
|
}
|
|
|
|
func (p *Parser) applyAutoRotateOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parseBool(ctx, o, keys.AutoRotate, args...)
|
|
}
|
|
|
|
func (p *Parser) applyEnforceThumbnailOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parseBool(ctx, o, keys.EnforceThumbnail, args...)
|
|
}
|
|
|
|
func (p *Parser) applyReturnAttachmentOption(ctx context.Context, o *options.Options, args []string) error {
|
|
return p.parseBool(ctx, o, keys.ReturnAttachment, args...)
|
|
}
|
|
|
|
func (p *Parser) applyMaxSrcResolutionOption(ctx context.Context, o *options.Options, args []string) error {
|
|
if err := p.IsSecurityOptionsAllowed(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
return p.parseResolution(ctx, o, keys.MaxSrcResolution, args...)
|
|
}
|
|
|
|
func (p *Parser) applyMaxSrcFileSizeOption(ctx context.Context, o *options.Options, args []string) error {
|
|
if err := p.IsSecurityOptionsAllowed(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
return p.parseInt(ctx, o, keys.MaxSrcFileSize, args...)
|
|
}
|
|
|
|
func (p *Parser) applyMaxAnimationFramesOption(ctx context.Context, o *options.Options, args []string) error {
|
|
if err := p.IsSecurityOptionsAllowed(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
return p.parsePositiveNonZeroInt(ctx, o, keys.MaxAnimationFrames, args...)
|
|
}
|
|
|
|
func (p *Parser) applyMaxAnimationFrameResolutionOption(ctx context.Context, o *options.Options, args []string) error {
|
|
if err := p.IsSecurityOptionsAllowed(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
return p.parseResolution(ctx, o, keys.MaxAnimationFrameResolution, args...)
|
|
}
|
|
|
|
func (p *Parser) applyMaxResultDimensionOption(
|
|
ctx context.Context,
|
|
o *options.Options,
|
|
args []string,
|
|
) error {
|
|
if err := p.IsSecurityOptionsAllowed(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
return p.parseInt(ctx, o, keys.MaxResultDimension, args...)
|
|
}
|
|
|
|
func (p *Parser) applyPresetOption(
|
|
ctx context.Context,
|
|
o *options.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
|
|
}
|
|
|
|
options.AppendToSlice(o, keys.UsedPresets, preset)
|
|
|
|
if err := p.applyURLOptions(ctx, o, pr, true, append(usedPresets, preset)...); err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
return newOptionArgumentError(ctx, "preset", "Unknown preset: %s", preset)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|