123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206 |
- package options
- import (
- "encoding/base64"
- "net/http"
- "slices"
- "strconv"
- "strings"
- "time"
- log "github.com/sirupsen/logrus"
- "github.com/imgproxy/imgproxy/v3/config"
- "github.com/imgproxy/imgproxy/v3/ierrors"
- "github.com/imgproxy/imgproxy/v3/imagetype"
- "github.com/imgproxy/imgproxy/v3/imath"
- "github.com/imgproxy/imgproxy/v3/security"
- "github.com/imgproxy/imgproxy/v3/structdiff"
- "github.com/imgproxy/imgproxy/v3/vips"
- )
- const maxClientHintDPR = 8
- type ExtendOptions struct {
- Enabled bool
- Gravity GravityOptions
- }
- type CropOptions struct {
- Width float64
- Height float64
- Gravity GravityOptions
- }
- type PaddingOptions struct {
- Enabled bool
- Top int
- Right int
- Bottom int
- Left int
- }
- type TrimOptions struct {
- Enabled bool
- Threshold float64
- Smart bool
- Color vips.Color
- EqualHor bool
- EqualVer bool
- }
- type WatermarkOptions struct {
- Enabled bool
- Opacity float64
- Position GravityOptions
- Scale float64
- }
- func (wo WatermarkOptions) ShouldReplicate() bool {
- return wo.Position.Type == GravityReplicate
- }
- type ProcessingOptions struct {
- ResizingType ResizeType
- Width int
- Height int
- MinWidth int
- MinHeight int
- ZoomWidth float64
- ZoomHeight float64
- Dpr float64
- Gravity GravityOptions
- Enlarge bool
- Extend ExtendOptions
- ExtendAspectRatio ExtendOptions
- Crop CropOptions
- Padding PaddingOptions
- Trim TrimOptions
- Rotate int
- Format imagetype.Type
- Quality int
- FormatQuality map[imagetype.Type]int
- MaxBytes int
- Flatten bool
- Background vips.Color
- Blur float32
- Sharpen float32
- Pixelate int
- StripMetadata bool
- KeepCopyright bool
- StripColorProfile bool
- AutoRotate bool
- EnforceThumbnail bool
- SkipProcessingFormats []imagetype.Type
- CacheBuster string
- Expires *time.Time
- Watermark WatermarkOptions
- PreferWebP bool
- EnforceWebP bool
- PreferAvif bool
- EnforceAvif bool
- PreferJxl bool
- EnforceJxl bool
- Filename string
- ReturnAttachment bool
- Raw bool
- UsedPresets []string
- SecurityOptions security.Options
- defaultQuality int
- }
- func NewProcessingOptions() *ProcessingOptions {
- po := ProcessingOptions{
- ResizingType: ResizeFit,
- Width: 0,
- Height: 0,
- ZoomWidth: 1,
- ZoomHeight: 1,
- Gravity: GravityOptions{Type: GravityCenter},
- Enlarge: false,
- Extend: ExtendOptions{Enabled: false, Gravity: GravityOptions{Type: GravityCenter}},
- ExtendAspectRatio: ExtendOptions{Enabled: false, Gravity: GravityOptions{Type: GravityCenter}},
- Padding: PaddingOptions{Enabled: false},
- Trim: TrimOptions{Enabled: false, Threshold: 10, Smart: true},
- Rotate: 0,
- Quality: 0,
- MaxBytes: 0,
- Format: imagetype.Unknown,
- Background: vips.Color{R: 255, G: 255, B: 255},
- Blur: 0,
- Sharpen: 0,
- Dpr: 1,
- Watermark: WatermarkOptions{Opacity: 1, Position: GravityOptions{Type: GravityCenter}},
- StripMetadata: config.StripMetadata,
- KeepCopyright: config.KeepCopyright,
- StripColorProfile: config.StripColorProfile,
- AutoRotate: config.AutoRotate,
- EnforceThumbnail: config.EnforceThumbnail,
- ReturnAttachment: config.ReturnAttachment,
- SkipProcessingFormats: append([]imagetype.Type(nil), config.SkipProcessingFormats...),
- UsedPresets: make([]string, 0, len(config.Presets)),
- SecurityOptions: security.DefaultOptions(),
- // Basically, we need this to update ETag when `IMGPROXY_QUALITY` is changed
- defaultQuality: config.Quality,
- }
- po.FormatQuality = make(map[imagetype.Type]int, len(config.FormatQuality))
- for k, v := range config.FormatQuality {
- po.FormatQuality[k] = v
- }
- return &po
- }
- func (po *ProcessingOptions) GetQuality() int {
- q := po.Quality
- if q == 0 {
- q = po.FormatQuality[po.Format]
- }
- if q == 0 {
- q = po.defaultQuality
- }
- return q
- }
- func (po *ProcessingOptions) Diff() structdiff.Entries {
- return structdiff.Diff(NewProcessingOptions(), po)
- }
- func (po *ProcessingOptions) String() string {
- return po.Diff().String()
- }
- func (po *ProcessingOptions) MarshalJSON() ([]byte, error) {
- return po.Diff().MarshalJSON()
- }
- func parseDimension(d *int, name, arg string) error {
- if v, err := strconv.Atoi(arg); err == nil && v >= 0 {
- *d = v
- } else {
- return newOptionArgumentError("Invalid %s: %s", name, arg)
- }
- return nil
- }
- func parseBoolOption(str string) bool {
- b, err := strconv.ParseBool(str)
- if err != nil {
- log.Warningf("`%s` is not a valid boolean value. Treated as false", str)
- }
- return b
- }
- func isGravityOffcetValid(gravity GravityType, offset float64) bool {
- return gravity != GravityFocusPoint || (offset >= 0 && offset <= 1)
- }
- func parseGravity(g *GravityOptions, name string, args []string, allowedTypes []GravityType) error {
- nArgs := len(args)
- if t, ok := gravityTypes[args[0]]; ok && slices.Contains(allowedTypes, t) {
- g.Type = t
- } else {
- return newOptionArgumentError("Invalid %s: %s", name, args[0])
- }
- switch g.Type {
- case GravitySmart:
- if nArgs > 1 {
- return newOptionArgumentError("Invalid %s arguments: %v", name, args)
- }
- g.X, g.Y = 0.0, 0.0
- case GravityFocusPoint:
- if nArgs != 3 {
- return newOptionArgumentError("Invalid %s arguments: %v", name, args)
- }
- fallthrough
- default:
- if nArgs > 3 {
- return newOptionArgumentError("Invalid %s arguments: %v", name, args)
- }
- if nArgs > 1 {
- if x, err := strconv.ParseFloat(args[1], 64); err == nil && isGravityOffcetValid(g.Type, x) {
- g.X = x
- } else {
- return newOptionArgumentError("Invalid %s X: %s", name, args[1])
- }
- }
- if nArgs > 2 {
- if y, err := strconv.ParseFloat(args[2], 64); err == nil && isGravityOffcetValid(g.Type, y) {
- g.Y = y
- } else {
- return newOptionArgumentError("Invalid %s Y: %s", name, args[2])
- }
- }
- }
- return nil
- }
- func parseExtend(opts *ExtendOptions, name string, args []string) error {
- if len(args) > 4 {
- return newOptionArgumentError("Invalid %s arguments: %v", name, args)
- }
- opts.Enabled = parseBoolOption(args[0])
- if len(args) > 1 {
- return parseGravity(&opts.Gravity, name+" gravity", args[1:], extendGravityTypes)
- }
- return nil
- }
- func applyWidthOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid width arguments: %v", args)
- }
- return parseDimension(&po.Width, "width", args[0])
- }
- func applyHeightOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid height arguments: %v", args)
- }
- return parseDimension(&po.Height, "height", args[0])
- }
- func applyMinWidthOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid min width arguments: %v", args)
- }
- return parseDimension(&po.MinWidth, "min width", args[0])
- }
- func applyMinHeightOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid min height arguments: %v", args)
- }
- return parseDimension(&po.MinHeight, " min height", args[0])
- }
- func applyEnlargeOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid enlarge arguments: %v", args)
- }
- po.Enlarge = parseBoolOption(args[0])
- return nil
- }
- func applyExtendOption(po *ProcessingOptions, args []string) error {
- return parseExtend(&po.Extend, "extend", args)
- }
- func applyExtendAspectRatioOption(po *ProcessingOptions, args []string) error {
- return parseExtend(&po.ExtendAspectRatio, "extend_aspect_ratio", args)
- }
- func applySizeOption(po *ProcessingOptions, args []string) (err error) {
- if len(args) > 7 {
- return newOptionArgumentError("Invalid size arguments: %v", args)
- }
- if len(args) >= 1 && len(args[0]) > 0 {
- if err = applyWidthOption(po, args[0:1]); err != nil {
- return
- }
- }
- if len(args) >= 2 && len(args[1]) > 0 {
- if err = applyHeightOption(po, args[1:2]); err != nil {
- return
- }
- }
- if len(args) >= 3 && len(args[2]) > 0 {
- if err = applyEnlargeOption(po, args[2:3]); err != nil {
- return
- }
- }
- if len(args) >= 4 && len(args[3]) > 0 {
- if err = applyExtendOption(po, args[3:]); err != nil {
- return
- }
- }
- return nil
- }
- func applyResizingTypeOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid resizing type arguments: %v", args)
- }
- if r, ok := resizeTypes[args[0]]; ok {
- po.ResizingType = r
- } else {
- return newOptionArgumentError("Invalid resize type: %s", args[0])
- }
- return nil
- }
- func applyResizeOption(po *ProcessingOptions, args []string) error {
- if len(args) > 8 {
- return newOptionArgumentError("Invalid resize arguments: %v", args)
- }
- if len(args[0]) > 0 {
- if err := applyResizingTypeOption(po, args[0:1]); err != nil {
- return err
- }
- }
- if len(args) > 1 {
- if err := applySizeOption(po, args[1:]); err != nil {
- return err
- }
- }
- return nil
- }
- func applyZoomOption(po *ProcessingOptions, args []string) error {
- nArgs := len(args)
- if nArgs > 2 {
- return newOptionArgumentError("Invalid zoom arguments: %v", args)
- }
- if z, err := strconv.ParseFloat(args[0], 64); err == nil && z > 0 {
- po.ZoomWidth = z
- po.ZoomHeight = z
- } else {
- return newOptionArgumentError("Invalid zoom value: %s", args[0])
- }
- if nArgs > 1 {
- if z, err := strconv.ParseFloat(args[1], 64); err == nil && z > 0 {
- po.ZoomHeight = z
- } else {
- return newOptionArgumentError("Invalid zoom value: %s", args[0])
- }
- }
- return nil
- }
- func applyDprOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid dpr arguments: %v", args)
- }
- if d, err := strconv.ParseFloat(args[0], 64); err == nil && d > 0 {
- po.Dpr = d
- } else {
- return newOptionArgumentError("Invalid dpr: %s", args[0])
- }
- return nil
- }
- func applyGravityOption(po *ProcessingOptions, args []string) error {
- return parseGravity(&po.Gravity, "gravity", args, cropGravityTypes)
- }
- func applyCropOption(po *ProcessingOptions, args []string) error {
- if w, err := strconv.ParseFloat(args[0], 64); err == nil && w >= 0 {
- po.Crop.Width = w
- } else {
- return newOptionArgumentError("Invalid crop width: %s", args[0])
- }
- if len(args) > 1 {
- if h, err := strconv.ParseFloat(args[1], 64); err == nil && h >= 0 {
- po.Crop.Height = h
- } else {
- return newOptionArgumentError("Invalid crop height: %s", args[1])
- }
- }
- if len(args) > 2 {
- return parseGravity(&po.Crop.Gravity, "crop gravity", args[2:], cropGravityTypes)
- }
- return nil
- }
- func applyPaddingOption(po *ProcessingOptions, args []string) error {
- nArgs := len(args)
- if nArgs < 1 || nArgs > 4 {
- return newOptionArgumentError("Invalid padding arguments: %v", args)
- }
- po.Padding.Enabled = true
- if nArgs > 0 && len(args[0]) > 0 {
- if err := parseDimension(&po.Padding.Top, "padding top (+all)", args[0]); err != nil {
- return err
- }
- po.Padding.Right = po.Padding.Top
- po.Padding.Bottom = po.Padding.Top
- po.Padding.Left = po.Padding.Top
- }
- if nArgs > 1 && len(args[1]) > 0 {
- if err := parseDimension(&po.Padding.Right, "padding right (+left)", args[1]); err != nil {
- return err
- }
- po.Padding.Left = po.Padding.Right
- }
- if nArgs > 2 && len(args[2]) > 0 {
- if err := parseDimension(&po.Padding.Bottom, "padding bottom", args[2]); err != nil {
- return err
- }
- }
- if nArgs > 3 && len(args[3]) > 0 {
- if err := parseDimension(&po.Padding.Left, "padding left", args[3]); err != nil {
- return err
- }
- }
- if po.Padding.Top == 0 && po.Padding.Right == 0 && po.Padding.Bottom == 0 && po.Padding.Left == 0 {
- po.Padding.Enabled = false
- }
- return nil
- }
- func applyTrimOption(po *ProcessingOptions, args []string) error {
- nArgs := len(args)
- if nArgs > 4 {
- return newOptionArgumentError("Invalid trim arguments: %v", args)
- }
- if t, err := strconv.ParseFloat(args[0], 64); err == nil && t >= 0 {
- po.Trim.Enabled = true
- po.Trim.Threshold = t
- } else {
- return newOptionArgumentError("Invalid trim threshold: %s", args[0])
- }
- if nArgs > 1 && len(args[1]) > 0 {
- if c, err := vips.ColorFromHex(args[1]); err == nil {
- po.Trim.Color = c
- po.Trim.Smart = false
- } else {
- return newOptionArgumentError("Invalid trim color: %s", args[1])
- }
- }
- if nArgs > 2 && len(args[2]) > 0 {
- po.Trim.EqualHor = parseBoolOption(args[2])
- }
- if nArgs > 3 && len(args[3]) > 0 {
- po.Trim.EqualVer = parseBoolOption(args[3])
- }
- return nil
- }
- func applyRotateOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid rotate arguments: %v", args)
- }
- if r, err := strconv.Atoi(args[0]); err == nil && r%90 == 0 {
- po.Rotate = r
- } else {
- return newOptionArgumentError("Invalid rotation angle: %s", args[0])
- }
- return nil
- }
- func applyQualityOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid quality arguments: %v", args)
- }
- if q, err := strconv.Atoi(args[0]); err == nil && q >= 0 && q <= 100 {
- po.Quality = q
- } else {
- return newOptionArgumentError("Invalid quality: %s", args[0])
- }
- return nil
- }
- func applyFormatQualityOption(po *ProcessingOptions, args []string) error {
- argsLen := len(args)
- if len(args)%2 != 0 {
- return newOptionArgumentError("Missing quality for: %s", args[argsLen-1])
- }
- for i := 0; i < argsLen; i += 2 {
- f, ok := imagetype.Types[args[i]]
- if !ok {
- return newOptionArgumentError("Invalid image format: %s", args[i])
- }
- if q, err := strconv.Atoi(args[i+1]); err == nil && q >= 0 && q <= 100 {
- po.FormatQuality[f] = q
- } else {
- return newOptionArgumentError("Invalid quality for %s: %s", args[i], args[i+1])
- }
- }
- return nil
- }
- func applyMaxBytesOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid max_bytes arguments: %v", args)
- }
- if max, err := strconv.Atoi(args[0]); err == nil && max >= 0 {
- po.MaxBytes = max
- } else {
- return newOptionArgumentError("Invalid max_bytes: %s", args[0])
- }
- return nil
- }
- func applyBackgroundOption(po *ProcessingOptions, args []string) error {
- switch len(args) {
- case 1:
- if len(args[0]) == 0 {
- po.Flatten = false
- } else if c, err := vips.ColorFromHex(args[0]); err == nil {
- po.Flatten = true
- po.Background = c
- } else {
- return newOptionArgumentError("Invalid background argument: %s", err)
- }
- case 3:
- po.Flatten = true
- if r, err := strconv.ParseUint(args[0], 10, 8); err == nil && r <= 255 {
- po.Background.R = uint8(r)
- } else {
- return newOptionArgumentError("Invalid background red channel: %s", args[0])
- }
- if g, err := strconv.ParseUint(args[1], 10, 8); err == nil && g <= 255 {
- po.Background.G = uint8(g)
- } else {
- return newOptionArgumentError("Invalid background green channel: %s", args[1])
- }
- if b, err := strconv.ParseUint(args[2], 10, 8); err == nil && b <= 255 {
- po.Background.B = uint8(b)
- } else {
- return newOptionArgumentError("Invalid background blue channel: %s", args[2])
- }
- default:
- return newOptionArgumentError("Invalid background arguments: %v", args)
- }
- return nil
- }
- func applyBlurOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid blur arguments: %v", args)
- }
- if b, err := strconv.ParseFloat(args[0], 32); err == nil && b >= 0 {
- po.Blur = float32(b)
- } else {
- return newOptionArgumentError("Invalid blur: %s", args[0])
- }
- return nil
- }
- func applySharpenOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid sharpen arguments: %v", args)
- }
- if s, err := strconv.ParseFloat(args[0], 32); err == nil && s >= 0 {
- po.Sharpen = float32(s)
- } else {
- return newOptionArgumentError("Invalid sharpen: %s", args[0])
- }
- return nil
- }
- func applyPixelateOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid pixelate arguments: %v", args)
- }
- if p, err := strconv.Atoi(args[0]); err == nil && p >= 0 {
- po.Pixelate = p
- } else {
- return newOptionArgumentError("Invalid pixelate: %s", args[0])
- }
- return nil
- }
- func applyPresetOption(po *ProcessingOptions, args []string, usedPresets ...string) error {
- for _, preset := range args {
- if p, ok := presets[preset]; ok {
- if slices.Contains(usedPresets, preset) {
- log.Warningf("Recursive preset usage is detected: %s", preset)
- continue
- }
- po.UsedPresets = append(po.UsedPresets, preset)
- if err := applyURLOptions(po, p, append(usedPresets, preset)...); err != nil {
- return err
- }
- } else {
- return newOptionArgumentError("Unknown preset: %s", preset)
- }
- }
- return nil
- }
- func applyWatermarkOption(po *ProcessingOptions, args []string) error {
- if len(args) > 7 {
- return newOptionArgumentError("Invalid watermark arguments: %v", args)
- }
- if o, err := strconv.ParseFloat(args[0], 64); err == nil && o >= 0 && o <= 1 {
- po.Watermark.Enabled = o > 0
- po.Watermark.Opacity = o
- } else {
- return newOptionArgumentError("Invalid watermark opacity: %s", args[0])
- }
- if len(args) > 1 && len(args[1]) > 0 {
- if g, ok := gravityTypes[args[1]]; ok && slices.Contains(watermarkGravityTypes, g) {
- po.Watermark.Position.Type = g
- } else {
- return newOptionArgumentError("Invalid watermark position: %s", args[1])
- }
- }
- if len(args) > 2 && len(args[2]) > 0 {
- if x, err := strconv.ParseFloat(args[2], 64); err == nil {
- po.Watermark.Position.X = x
- } else {
- return newOptionArgumentError("Invalid watermark X offset: %s", args[2])
- }
- }
- if len(args) > 3 && len(args[3]) > 0 {
- if y, err := strconv.ParseFloat(args[3], 64); err == nil {
- po.Watermark.Position.Y = y
- } else {
- return newOptionArgumentError("Invalid watermark Y offset: %s", args[3])
- }
- }
- if len(args) > 4 && len(args[4]) > 0 {
- if s, err := strconv.ParseFloat(args[4], 64); err == nil && s >= 0 {
- po.Watermark.Scale = s
- } else {
- return newOptionArgumentError("Invalid watermark scale: %s", args[4])
- }
- }
- return nil
- }
- func applyFormatOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid format arguments: %v", args)
- }
- if f, ok := imagetype.Types[args[0]]; ok {
- po.Format = f
- } else {
- return newOptionArgumentError("Invalid image format: %s", args[0])
- }
- return nil
- }
- func applyCacheBusterOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid cache buster arguments: %v", args)
- }
- po.CacheBuster = args[0]
- return nil
- }
- func applySkipProcessingFormatsOption(po *ProcessingOptions, args []string) error {
- for _, format := range args {
- if f, ok := imagetype.Types[format]; ok {
- po.SkipProcessingFormats = append(po.SkipProcessingFormats, f)
- } else {
- return newOptionArgumentError("Invalid image format in skip processing: %s", format)
- }
- }
- return nil
- }
- func applyRawOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid return_attachment arguments: %v", args)
- }
- po.Raw = parseBoolOption(args[0])
- return nil
- }
- func applyFilenameOption(po *ProcessingOptions, args []string) error {
- if len(args) > 2 {
- return newOptionArgumentError("Invalid filename arguments: %v", args)
- }
- po.Filename = args[0]
- if len(args) > 1 && parseBoolOption(args[1]) {
- decoded, err := base64.RawURLEncoding.DecodeString(po.Filename)
- if err != nil {
- return newOptionArgumentError("Invalid filename encoding: %s", err)
- }
- po.Filename = string(decoded)
- }
- return nil
- }
- func applyExpiresOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid expires arguments: %v", args)
- }
- timestamp, err := strconv.ParseInt(args[0], 10, 64)
- if err != nil {
- return newOptionArgumentError("Invalid expires argument: %v", args[0])
- }
- if timestamp > 0 && timestamp < time.Now().Unix() {
- return newOptionArgumentError("Expired URL")
- }
- expires := time.Unix(timestamp, 0)
- po.Expires = &expires
- return nil
- }
- func applyStripMetadataOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid strip metadata arguments: %v", args)
- }
- po.StripMetadata = parseBoolOption(args[0])
- return nil
- }
- func applyKeepCopyrightOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid keep copyright arguments: %v", args)
- }
- po.KeepCopyright = parseBoolOption(args[0])
- return nil
- }
- func applyStripColorProfileOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid strip color profile arguments: %v", args)
- }
- po.StripColorProfile = parseBoolOption(args[0])
- return nil
- }
- func applyAutoRotateOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid auto rotate arguments: %v", args)
- }
- po.AutoRotate = parseBoolOption(args[0])
- return nil
- }
- func applyEnforceThumbnailOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid enforce thumbnail arguments: %v", args)
- }
- po.EnforceThumbnail = parseBoolOption(args[0])
- return nil
- }
- func applyReturnAttachmentOption(po *ProcessingOptions, args []string) error {
- if len(args) > 1 {
- return newOptionArgumentError("Invalid return_attachment arguments: %v", args)
- }
- po.ReturnAttachment = parseBoolOption(args[0])
- return nil
- }
- func applyMaxSrcResolutionOption(po *ProcessingOptions, args []string) error {
- if err := security.IsSecurityOptionsAllowed(); err != nil {
- return err
- }
- if len(args) > 1 {
- return newOptionArgumentError("Invalid max_src_resolution arguments: %v", args)
- }
- if x, err := strconv.ParseFloat(args[0], 64); err == nil && x > 0 {
- po.SecurityOptions.MaxSrcResolution = int(x * 1000000)
- } else {
- return newOptionArgumentError("Invalid max_src_resolution: %s", args[0])
- }
- return nil
- }
- func applyMaxSrcFileSizeOption(po *ProcessingOptions, args []string) error {
- if err := security.IsSecurityOptionsAllowed(); err != nil {
- return err
- }
- if len(args) > 1 {
- return newOptionArgumentError("Invalid max_src_file_size arguments: %v", args)
- }
- if x, err := strconv.Atoi(args[0]); err == nil {
- po.SecurityOptions.MaxSrcFileSize = x
- } else {
- return newOptionArgumentError("Invalid max_src_file_size: %s", args[0])
- }
- return nil
- }
- func applyMaxAnimationFramesOption(po *ProcessingOptions, args []string) error {
- if err := security.IsSecurityOptionsAllowed(); err != nil {
- return err
- }
- if len(args) > 1 {
- return newOptionArgumentError("Invalid max_animation_frames arguments: %v", args)
- }
- if x, err := strconv.Atoi(args[0]); err == nil && x > 0 {
- po.SecurityOptions.MaxAnimationFrames = x
- } else {
- return newOptionArgumentError("Invalid max_animation_frames: %s", args[0])
- }
- return nil
- }
- func applyMaxAnimationFrameResolutionOption(po *ProcessingOptions, args []string) error {
- if err := security.IsSecurityOptionsAllowed(); err != nil {
- return err
- }
- if len(args) > 1 {
- return newOptionArgumentError("Invalid max_animation_frame_resolution arguments: %v", args)
- }
- if x, err := strconv.ParseFloat(args[0], 64); err == nil {
- po.SecurityOptions.MaxAnimationFrameResolution = int(x * 1000000)
- } else {
- return newOptionArgumentError("Invalid max_animation_frame_resolution: %s", args[0])
- }
- return nil
- }
- func applyURLOption(po *ProcessingOptions, name string, args []string, usedPresets ...string) error {
- switch name {
- case "resize", "rs":
- return applyResizeOption(po, args)
- case "size", "s":
- return applySizeOption(po, args)
- case "resizing_type", "rt":
- return applyResizingTypeOption(po, args)
- case "width", "w":
- return applyWidthOption(po, args)
- case "height", "h":
- return applyHeightOption(po, args)
- case "min-width", "mw":
- return applyMinWidthOption(po, args)
- case "min-height", "mh":
- return applyMinHeightOption(po, args)
- case "zoom", "z":
- return applyZoomOption(po, args)
- case "dpr":
- return applyDprOption(po, args)
- case "enlarge", "el":
- return applyEnlargeOption(po, args)
- case "extend", "ex":
- return applyExtendOption(po, args)
- case "extend_aspect_ratio", "extend_ar", "exar":
- return applyExtendAspectRatioOption(po, args)
- case "gravity", "g":
- return applyGravityOption(po, args)
- case "crop", "c":
- return applyCropOption(po, args)
- case "trim", "t":
- return applyTrimOption(po, args)
- case "padding", "pd":
- return applyPaddingOption(po, args)
- case "auto_rotate", "ar":
- return applyAutoRotateOption(po, args)
- case "rotate", "rot":
- return applyRotateOption(po, args)
- case "background", "bg":
- return applyBackgroundOption(po, args)
- case "blur", "bl":
- return applyBlurOption(po, args)
- case "sharpen", "sh":
- return applySharpenOption(po, args)
- case "pixelate", "pix":
- return applyPixelateOption(po, args)
- case "watermark", "wm":
- return applyWatermarkOption(po, args)
- case "strip_metadata", "sm":
- return applyStripMetadataOption(po, args)
- case "keep_copyright", "kcr":
- return applyKeepCopyrightOption(po, args)
- case "strip_color_profile", "scp":
- return applyStripColorProfileOption(po, args)
- case "enforce_thumbnail", "eth":
- return applyEnforceThumbnailOption(po, args)
- // Saving options
- case "quality", "q":
- return applyQualityOption(po, args)
- case "format_quality", "fq":
- return applyFormatQualityOption(po, args)
- case "max_bytes", "mb":
- return applyMaxBytesOption(po, args)
- case "format", "f", "ext":
- return applyFormatOption(po, args)
- // Handling options
- case "skip_processing", "skp":
- return applySkipProcessingFormatsOption(po, args)
- case "raw":
- return applyRawOption(po, args)
- case "cachebuster", "cb":
- return applyCacheBusterOption(po, args)
- case "expires", "exp":
- return applyExpiresOption(po, args)
- case "filename", "fn":
- return applyFilenameOption(po, args)
- case "return_attachment", "att":
- return applyReturnAttachmentOption(po, args)
- // Presets
- case "preset", "pr":
- return applyPresetOption(po, args, usedPresets...)
- // Security
- case "max_src_resolution", "msr":
- return applyMaxSrcResolutionOption(po, args)
- case "max_src_file_size", "msfs":
- return applyMaxSrcFileSizeOption(po, args)
- case "max_animation_frames", "maf":
- return applyMaxAnimationFramesOption(po, args)
- case "max_animation_frame_resolution", "mafr":
- return applyMaxAnimationFrameResolutionOption(po, args)
- }
- return newUnknownOptionError("processing", name)
- }
- func applyURLOptions(po *ProcessingOptions, options urlOptions, usedPresets ...string) error {
- for _, opt := range options {
- if err := applyURLOption(po, opt.Name, opt.Args, usedPresets...); err != nil {
- return err
- }
- }
- return nil
- }
- func defaultProcessingOptions(headers http.Header) (*ProcessingOptions, error) {
- po := NewProcessingOptions()
- headerAccept := headers.Get("Accept")
- if strings.Contains(headerAccept, "image/webp") {
- po.PreferWebP = config.AutoWebp || config.EnforceWebp
- po.EnforceWebP = config.EnforceWebp
- }
- if strings.Contains(headerAccept, "image/avif") {
- po.PreferAvif = config.AutoAvif || config.EnforceAvif
- po.EnforceAvif = config.EnforceAvif
- }
- if strings.Contains(headerAccept, "image/jxl") {
- po.PreferJxl = config.AutoJxl || config.EnforceJxl
- po.EnforceJxl = config.EnforceJxl
- }
- if config.EnableClientHints {
- headerDPR := headers.Get("Sec-CH-DPR")
- if len(headerDPR) == 0 {
- headerDPR = headers.Get("DPR")
- }
- if len(headerDPR) > 0 {
- if dpr, err := strconv.ParseFloat(headerDPR, 64); err == nil && (dpr > 0 && dpr <= maxClientHintDPR) {
- po.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 {
- po.Width = imath.Shrink(w, po.Dpr)
- }
- }
- }
- if _, ok := presets["default"]; ok {
- if err := applyPresetOption(po, []string{"default"}); err != nil {
- return po, err
- }
- }
- return po, nil
- }
- func parsePathOptions(parts []string, headers http.Header) (*ProcessingOptions, string, error) {
- if _, ok := resizeTypes[parts[0]]; ok {
- return nil, "", newInvalidURLError("It looks like you're using the deprecated basic URL format")
- }
- po, err := defaultProcessingOptions(headers)
- if err != nil {
- return nil, "", err
- }
- options, urlParts := parseURLOptions(parts)
- if err = applyURLOptions(po, options); err != nil {
- return nil, "", err
- }
- url, extension, err := DecodeURL(urlParts)
- if err != nil {
- return nil, "", err
- }
- if !po.Raw && len(extension) > 0 {
- if err = applyFormatOption(po, []string{extension}); err != nil {
- return nil, "", err
- }
- }
- return po, url, nil
- }
- func parsePathPresets(parts []string, headers http.Header) (*ProcessingOptions, string, error) {
- po, err := defaultProcessingOptions(headers)
- if err != nil {
- return nil, "", err
- }
- presets := strings.Split(parts[0], config.ArgumentsSeparator)
- urlParts := parts[1:]
- if err = applyPresetOption(po, presets); err != nil {
- return nil, "", err
- }
- url, extension, err := DecodeURL(urlParts)
- if err != nil {
- return nil, "", err
- }
- if !po.Raw && len(extension) > 0 {
- if err = applyFormatOption(po, []string{extension}); err != nil {
- return nil, "", err
- }
- }
- return po, url, nil
- }
- func ParsePath(path string, headers http.Header) (*ProcessingOptions, string, error) {
- if path == "" || path == "/" {
- return nil, "", newInvalidURLError("Invalid path: %s", path)
- }
- parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
- var (
- imageURL string
- po *ProcessingOptions
- err error
- )
- if config.OnlyPresets {
- po, imageURL, err = parsePathPresets(parts, headers)
- } else {
- po, imageURL, err = parsePathOptions(parts, headers)
- }
- if err != nil {
- return nil, "", ierrors.Wrap(err, 0)
- }
- return po, imageURL, nil
- }
|