1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2025-01-08 10:45:04 +02:00
imgproxy/options/processing_options.go

1021 lines
24 KiB
Go
Raw Normal View History

2021-04-26 13:52:50 +02:00
package options
2018-09-07 15:37:25 +02:00
import (
"errors"
"fmt"
"net/http"
2018-09-07 15:37:25 +02:00
"strconv"
"strings"
2019-10-10 16:35:17 +02:00
"sync"
"time"
2019-10-10 16:35:17 +02:00
2021-04-26 13:52:50 +02:00
log "github.com/sirupsen/logrus"
2021-09-30 16:23:30 +02:00
"github.com/imgproxy/imgproxy/v3/config"
"github.com/imgproxy/imgproxy/v3/ierrors"
"github.com/imgproxy/imgproxy/v3/imagetype"
2021-10-11 13:50:12 +02:00
"github.com/imgproxy/imgproxy/v3/imath"
2021-09-30 16:23:30 +02:00
"github.com/imgproxy/imgproxy/v3/structdiff"
"github.com/imgproxy/imgproxy/v3/vips"
)
2021-04-26 13:52:50 +02:00
const maxClientHintDPR = 8
2018-10-02 14:20:23 +02:00
2021-04-26 13:52:50 +02:00
var errExpiredURL = errors.New("Expired URL")
2021-04-26 13:52:50 +02:00
type ExtendOptions struct {
2019-12-25 12:50:02 +02:00
Enabled bool
2021-04-26 13:52:50 +02:00
Gravity GravityOptions
2019-12-25 12:50:02 +02:00
}
2021-04-26 13:52:50 +02:00
type CropOptions struct {
2021-01-18 17:14:15 +02:00
Width float64
Height float64
2021-04-26 13:52:50 +02:00
Gravity GravityOptions
}
2021-04-26 13:52:50 +02:00
type PaddingOptions struct {
Enabled bool
Top int
Right int
Bottom int
Left int
}
2021-04-26 13:52:50 +02:00
type TrimOptions struct {
2020-01-17 11:54:50 +02:00
Enabled bool
Threshold float64
Smart bool
2021-04-26 13:52:50 +02:00
Color vips.Color
EqualHor bool
EqualVer bool
2020-01-17 11:54:50 +02:00
}
2021-04-26 13:52:50 +02:00
type WatermarkOptions struct {
2018-10-19 11:47:11 +02:00
Enabled bool
Opacity float64
Replicate bool
2021-04-26 13:52:50 +02:00
Gravity GravityOptions
2018-10-28 14:05:57 +02:00
Scale float64
2018-10-19 11:47:11 +02:00
}
2021-04-26 13:52:50 +02:00
type ProcessingOptions struct {
ResizingType ResizeType
2021-01-12 16:43:09 +02:00
Width int
Height int
MinWidth int
MinHeight int
2021-01-12 16:43:09 +02:00
Dpr float64
2021-04-26 13:52:50 +02:00
Gravity GravityOptions
2021-01-12 16:43:09 +02:00
Enlarge bool
2021-04-26 13:52:50 +02:00
Extend ExtendOptions
Crop CropOptions
Padding PaddingOptions
Trim TrimOptions
2021-01-13 16:51:19 +02:00
Rotate int
2021-04-26 13:52:50 +02:00
Format imagetype.Type
2021-01-12 16:43:09 +02:00
Quality int
2021-09-29 15:49:18 +02:00
FormatQuality map[imagetype.Type]int
2021-01-12 16:43:09 +02:00
MaxBytes int
Flatten bool
2021-04-26 13:52:50 +02:00
Background vips.Color
2021-01-12 16:43:09 +02:00
Blur float32
Sharpen float32
2021-04-27 12:23:22 +02:00
Pixelate int
2021-01-12 16:43:09 +02:00
StripMetadata bool
StripColorProfile bool
AutoRotate bool
2018-10-19 11:47:11 +02:00
2021-04-26 13:52:50 +02:00
SkipProcessingFormats []imagetype.Type
2018-10-29 10:54:30 +02:00
CacheBuster string
2021-04-26 13:52:50 +02:00
Watermark WatermarkOptions
2018-10-19 11:47:11 +02:00
PreferWebP bool
EnforceWebP bool
PreferAvif bool
EnforceAvif bool
Filename string
2018-10-04 14:02:24 +02:00
UsedPresets []string
2021-09-29 15:49:18 +02:00
defaultQuality int
2018-09-07 15:37:25 +02:00
}
2019-10-10 16:35:17 +02:00
var (
2021-04-26 13:52:50 +02:00
_newProcessingOptions ProcessingOptions
2019-10-10 16:35:17 +02:00
newProcessingOptionsOnce sync.Once
)
2021-04-26 13:52:50 +02:00
func NewProcessingOptions() *ProcessingOptions {
2019-10-10 16:35:17 +02:00
newProcessingOptionsOnce.Do(func() {
2021-04-26 13:52:50 +02:00
_newProcessingOptions = ProcessingOptions{
ResizingType: ResizeFit,
2021-01-12 16:43:09 +02:00
Width: 0,
Height: 0,
2021-04-26 13:52:50 +02:00
Gravity: GravityOptions{Type: GravityCenter},
2021-01-12 16:43:09 +02:00
Enlarge: false,
2021-04-26 13:52:50 +02:00
Extend: ExtendOptions{Enabled: false, Gravity: GravityOptions{Type: GravityCenter}},
Padding: PaddingOptions{Enabled: false},
Trim: TrimOptions{Enabled: false, Threshold: 10, Smart: true},
2021-01-13 16:51:19 +02:00
Rotate: 0,
2021-01-13 13:58:36 +02:00
Quality: 0,
2021-01-12 16:43:09 +02:00
MaxBytes: 0,
2021-04-26 13:52:50 +02:00
Format: imagetype.Unknown,
Background: vips.Color{R: 255, G: 255, B: 255},
2021-01-12 16:43:09 +02:00
Blur: 0,
Sharpen: 0,
Dpr: 1,
2021-04-26 13:52:50 +02:00
Watermark: WatermarkOptions{Opacity: 1, Replicate: false, Gravity: GravityOptions{Type: GravityCenter}},
StripMetadata: config.StripMetadata,
StripColorProfile: config.StripColorProfile,
AutoRotate: config.AutoRotate,
2021-09-29 15:49:18 +02:00
// Basically, we need this to update ETag when `IMGPROXY_QUALITY` is changed
defaultQuality: config.Quality,
2019-10-10 16:35:17 +02:00
}
})
po := _newProcessingOptions
2021-04-26 13:52:50 +02:00
po.SkipProcessingFormats = append([]imagetype.Type(nil), config.SkipProcessingFormats...)
po.UsedPresets = make([]string, 0, len(config.Presets))
2019-10-10 16:35:17 +02:00
2021-09-29 15:49:18 +02:00
po.FormatQuality = make(map[imagetype.Type]int)
for k, v := range config.FormatQuality {
po.FormatQuality[k] = v
}
2019-10-10 16:35:17 +02:00
return &po
2019-09-11 12:40:07 +02:00
}
2021-04-26 13:52:50 +02:00
func (po *ProcessingOptions) GetQuality() int {
2021-01-13 13:58:36 +02:00
q := po.Quality
if q == 0 {
2021-09-29 15:49:18 +02:00
q = po.FormatQuality[po.Format]
2021-01-13 13:58:36 +02:00
}
if q == 0 {
2021-09-29 15:49:18 +02:00
q = po.defaultQuality
2021-01-13 13:58:36 +02:00
}
return q
}
2021-04-26 13:52:50 +02:00
func (po *ProcessingOptions) isPresetUsed(name string) bool {
2018-11-02 17:35:21 +02:00
for _, usedName := range po.UsedPresets {
if usedName == name {
return true
}
}
return false
}
2021-04-26 13:52:50 +02:00
func (po *ProcessingOptions) Diff() structdiff.Entries {
return structdiff.Diff(NewProcessingOptions(), po)
2018-11-02 17:35:21 +02:00
}
2021-04-26 13:52:50 +02:00
func (po *ProcessingOptions) String() string {
2019-10-10 16:35:17 +02:00
return po.Diff().String()
}
2021-04-26 13:52:50 +02:00
func (po *ProcessingOptions) MarshalJSON() ([]byte, error) {
2019-10-10 16:35:17 +02:00
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 fmt.Errorf("Invalid %s: %s", name, arg)
2018-09-07 15:37:25 +02:00
}
return nil
}
func parseBoolOption(str string) bool {
b, err := strconv.ParseBool(str)
if err != nil {
2021-04-26 13:52:50 +02:00
log.Warningf("`%s` is not a valid boolean value. Treated as false", str)
}
return b
}
2021-04-26 13:52:50 +02:00
func isGravityOffcetValid(gravity GravityType, offset float64) bool {
2022-01-17 14:39:59 +02:00
return gravity != GravityFocusPoint || (offset >= 0 && offset <= 1)
2019-06-20 14:49:25 +02:00
}
2021-04-26 13:52:50 +02:00
func parseGravity(g *GravityOptions, args []string) error {
2019-06-20 14:49:25 +02:00
nArgs := len(args)
if nArgs > 3 {
return fmt.Errorf("Invalid gravity arguments: %v", args)
}
if t, ok := gravityTypes[args[0]]; ok {
g.Type = t
2018-09-07 15:37:25 +02:00
} else {
return fmt.Errorf("Invalid gravity: %s", args[0])
}
2021-04-26 13:52:50 +02:00
if g.Type == GravitySmart && nArgs > 1 {
2019-06-20 14:49:25 +02:00
return fmt.Errorf("Invalid gravity arguments: %v", args)
2021-04-26 13:52:50 +02:00
} else if g.Type == GravityFocusPoint && nArgs != 3 {
2019-06-20 14:49:25 +02:00
return fmt.Errorf("Invalid gravity arguments: %v", args)
}
2019-06-20 14:49:25 +02:00
if nArgs > 1 {
if x, err := strconv.ParseFloat(args[1], 64); err == nil && isGravityOffcetValid(g.Type, x) {
g.X = x
} else {
return fmt.Errorf("Invalid gravity X: %s", args[1])
}
2019-06-20 14:49:25 +02:00
}
2019-06-20 14:49:25 +02:00
if nArgs > 2 {
if y, err := strconv.ParseFloat(args[2], 64); err == nil && isGravityOffcetValid(g.Type, y) {
g.Y = y
} else {
return fmt.Errorf("Invalid gravity Y: %s", args[2])
}
2018-09-07 15:37:25 +02:00
}
return nil
}
2021-04-26 13:52:50 +02:00
func applyWidthOption(po *ProcessingOptions, args []string) error {
2018-09-07 15:37:25 +02:00
if len(args) > 1 {
return fmt.Errorf("Invalid width arguments: %v", args)
2018-09-07 15:37:25 +02:00
}
return parseDimension(&po.Width, "width", args[0])
}
2021-04-26 13:52:50 +02:00
func applyHeightOption(po *ProcessingOptions, args []string) error {
if len(args) > 1 {
return fmt.Errorf("Invalid height arguments: %v", args)
2018-09-07 15:37:25 +02:00
}
return parseDimension(&po.Height, "height", args[0])
2018-09-07 15:37:25 +02:00
}
2021-04-26 13:52:50 +02:00
func applyMinWidthOption(po *ProcessingOptions, args []string) error {
if len(args) > 1 {
return fmt.Errorf("Invalid min width arguments: %v", args)
}
return parseDimension(&po.MinWidth, "min width", args[0])
}
2021-04-26 13:52:50 +02:00
func applyMinHeightOption(po *ProcessingOptions, args []string) error {
if len(args) > 1 {
return fmt.Errorf("Invalid min height arguments: %v", args)
}
return parseDimension(&po.MinHeight, " min height", args[0])
}
2021-04-26 13:52:50 +02:00
func applyEnlargeOption(po *ProcessingOptions, args []string) error {
2018-09-07 15:37:25 +02:00
if len(args) > 1 {
return fmt.Errorf("Invalid enlarge arguments: %v", args)
}
po.Enlarge = parseBoolOption(args[0])
2018-09-07 15:37:25 +02:00
return nil
}
2021-04-26 13:52:50 +02:00
func applyExtendOption(po *ProcessingOptions, args []string) error {
2019-12-25 12:50:02 +02:00
if len(args) > 4 {
2019-06-17 12:15:10 +02:00
return fmt.Errorf("Invalid extend arguments: %v", args)
2019-02-21 17:55:20 +02:00
}
2019-12-25 12:50:02 +02:00
po.Extend.Enabled = parseBoolOption(args[0])
if len(args) > 1 {
if err := parseGravity(&po.Extend.Gravity, args[1:]); err != nil {
return err
}
2021-04-26 13:52:50 +02:00
if po.Extend.Gravity.Type == GravitySmart {
2019-12-25 12:50:02 +02:00
return errors.New("extend doesn't support smart gravity")
}
}
2019-02-21 17:55:20 +02:00
return nil
}
2021-04-26 13:52:50 +02:00
func applySizeOption(po *ProcessingOptions, args []string) (err error) {
2019-12-25 12:50:02 +02:00
if len(args) > 7 {
2018-09-07 15:37:25 +02:00
return fmt.Errorf("Invalid size arguments: %v", args)
}
2018-10-03 17:33:22 +02:00
if len(args) >= 1 && len(args[0]) > 0 {
2018-09-07 15:37:25 +02:00
if err = applyWidthOption(po, args[0:1]); err != nil {
return
}
}
2018-10-03 17:33:22 +02:00
if len(args) >= 2 && len(args[1]) > 0 {
2018-09-07 15:37:25 +02:00
if err = applyHeightOption(po, args[1:2]); err != nil {
return
}
}
2019-02-21 17:55:20 +02:00
if len(args) >= 3 && len(args[2]) > 0 {
2018-09-07 15:37:25 +02:00
if err = applyEnlargeOption(po, args[2:3]); err != nil {
return
}
}
2019-12-25 12:50:02 +02:00
if len(args) >= 4 && len(args[3]) > 0 {
if err = applyExtendOption(po, args[3:]); err != nil {
2019-02-21 17:55:20 +02:00
return
}
}
2018-09-07 15:37:25 +02:00
return nil
}
2021-04-26 13:52:50 +02:00
func applyResizingTypeOption(po *ProcessingOptions, args []string) error {
2018-10-03 17:34:13 +02:00
if len(args) > 1 {
return fmt.Errorf("Invalid resizing type arguments: %v", args)
2018-09-07 15:37:25 +02:00
}
if r, ok := resizeTypes[args[0]]; ok {
2019-10-11 13:05:20 +02:00
po.ResizingType = r
2018-09-07 15:37:25 +02:00
} else {
return fmt.Errorf("Invalid resize type: %s", args[0])
}
2018-10-03 17:34:13 +02:00
return nil
}
2021-04-26 13:52:50 +02:00
func applyResizeOption(po *ProcessingOptions, args []string) error {
2019-12-25 12:50:02 +02:00
if len(args) > 8 {
2018-10-03 17:34:13 +02:00
return fmt.Errorf("Invalid resize arguments: %v", args)
}
if len(args[0]) > 0 {
if err := applyResizingTypeOption(po, args[0:1]); err != nil {
return err
}
}
2018-09-07 15:37:25 +02:00
if len(args) > 1 {
if err := applySizeOption(po, args[1:]); err != nil {
return err
}
}
return nil
}
2021-04-26 13:52:50 +02:00
func applyDprOption(po *ProcessingOptions, args []string) error {
2018-11-15 13:19:27 +02:00
if len(args) > 1 {
return fmt.Errorf("Invalid dpr arguments: %v", args)
}
2019-06-19 12:14:41 +02:00
if d, err := strconv.ParseFloat(args[0], 64); err == nil && d > 0 {
2018-11-15 13:19:27 +02:00
po.Dpr = d
} else {
return fmt.Errorf("Invalid dpr: %s", args[0])
}
return nil
}
2021-04-26 13:52:50 +02:00
func applyGravityOption(po *ProcessingOptions, args []string) error {
return parseGravity(&po.Gravity, args)
}
2021-04-26 13:52:50 +02:00
func applyCropOption(po *ProcessingOptions, args []string) error {
if len(args) > 5 {
return fmt.Errorf("Invalid crop arguments: %v", args)
2018-09-07 15:37:25 +02:00
}
2021-01-18 17:14:15 +02:00
if w, err := strconv.ParseFloat(args[0], 64); err == nil && w >= 0 {
po.Crop.Width = w
} else {
return fmt.Errorf("Invalid crop width: %s", args[0])
}
2018-09-11 13:23:28 +02:00
if len(args) > 1 {
2021-01-18 17:14:15 +02:00
if h, err := strconv.ParseFloat(args[1], 64); err == nil && h >= 0 {
po.Crop.Height = h
} else {
return fmt.Errorf("Invalid crop height: %s", args[1])
2018-09-11 13:23:28 +02:00
}
}
2018-09-11 13:23:28 +02:00
if len(args) > 2 {
return parseGravity(&po.Crop.Gravity, args[2:])
2018-09-11 13:23:28 +02:00
}
2018-09-07 15:37:25 +02:00
return nil
}
2021-04-26 13:52:50 +02:00
func applyPaddingOption(po *ProcessingOptions, args []string) error {
nArgs := len(args)
if nArgs < 1 || nArgs > 4 {
return fmt.Errorf("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
}
2021-04-26 13:52:50 +02:00
func applyTrimOption(po *ProcessingOptions, args []string) error {
nArgs := len(args)
if nArgs > 4 {
return fmt.Errorf("Invalid trim arguments: %v", args)
2020-01-17 11:54:50 +02:00
}
if t, err := strconv.ParseFloat(args[0], 64); err == nil && t >= 0 {
po.Trim.Enabled = true
po.Trim.Threshold = t
} else {
return fmt.Errorf("Invalid trim threshold: %s", args[0])
}
if nArgs > 1 && len(args[1]) > 0 {
2021-04-26 13:52:50 +02:00
if c, err := vips.ColorFromHex(args[1]); err == nil {
po.Trim.Color = c
po.Trim.Smart = false
} else {
return fmt.Errorf("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])
2020-01-17 11:54:50 +02:00
}
return nil
}
2021-04-26 13:52:50 +02:00
func applyRotateOption(po *ProcessingOptions, args []string) error {
2021-01-13 16:51:19 +02:00
if len(args) > 1 {
return fmt.Errorf("Invalid rotate arguments: %v", args)
}
if r, err := strconv.Atoi(args[0]); err == nil && r%90 == 0 {
po.Rotate = r
} else {
return fmt.Errorf("Invalid rotation angle: %s", args[0])
}
return nil
}
2021-04-26 13:52:50 +02:00
func applyQualityOption(po *ProcessingOptions, args []string) error {
2018-10-30 14:20:02 +02:00
if len(args) > 1 {
return fmt.Errorf("Invalid quality arguments: %v", args)
}
2021-01-13 13:58:36 +02:00
if q, err := strconv.Atoi(args[0]); err == nil && q >= 0 && q <= 100 {
2018-10-30 14:20:02 +02:00
po.Quality = q
} else {
return fmt.Errorf("Invalid quality: %s", args[0])
}
return nil
}
2021-09-29 15:49:18 +02:00
func applyFormatQualityOption(po *ProcessingOptions, args []string) error {
argsLen := len(args)
if len(args)%2 != 0 {
return fmt.Errorf("Missing quality for: %s", args[argsLen-1])
}
for i := 0; i < argsLen; i += 2 {
f, ok := imagetype.Types[args[i]]
if !ok {
return fmt.Errorf("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 fmt.Errorf("Invalid quality for %s: %s", args[i], args[i+1])
}
}
return nil
}
2021-04-26 13:52:50 +02:00
func applyMaxBytesOption(po *ProcessingOptions, args []string) error {
if len(args) > 1 {
return fmt.Errorf("Invalid max_bytes arguments: %v", args)
}
if max, err := strconv.Atoi(args[0]); err == nil && max >= 0 {
po.MaxBytes = max
} else {
return fmt.Errorf("Invalid max_bytes: %s", args[0])
}
return nil
}
2021-04-26 13:52:50 +02:00
func applyBackgroundOption(po *ProcessingOptions, args []string) error {
switch len(args) {
case 1:
if len(args[0]) == 0 {
po.Flatten = false
2021-04-26 13:52:50 +02:00
} else if c, err := vips.ColorFromHex(args[0]); err == nil {
po.Flatten = true
po.Background = c
} else {
return fmt.Errorf("Invalid background argument: %s", err)
}
2018-10-02 14:20:23 +02:00
case 3:
po.Flatten = true
2018-10-02 14:20:23 +02:00
if r, err := strconv.ParseUint(args[0], 10, 8); err == nil && r <= 255 {
po.Background.R = uint8(r)
} else {
return fmt.Errorf("Invalid background red channel: %s", args[0])
}
2018-10-02 14:20:23 +02:00
if g, err := strconv.ParseUint(args[1], 10, 8); err == nil && g <= 255 {
po.Background.G = uint8(g)
} else {
return fmt.Errorf("Invalid background green channel: %s", args[1])
}
2018-10-02 14:20:23 +02:00
if b, err := strconv.ParseUint(args[2], 10, 8); err == nil && b <= 255 {
po.Background.B = uint8(b)
} else {
return fmt.Errorf("Invalid background blue channel: %s", args[2])
}
default:
return fmt.Errorf("Invalid background arguments: %v", args)
}
2018-10-02 14:20:23 +02:00
return nil
}
2021-04-26 13:52:50 +02:00
func applyBlurOption(po *ProcessingOptions, args []string) error {
2018-09-07 16:46:16 +02:00
if len(args) > 1 {
return fmt.Errorf("Invalid blur arguments: %v", args)
}
2019-05-28 13:39:20 +02:00
if b, err := strconv.ParseFloat(args[0], 32); err == nil && b >= 0 {
2018-09-07 16:46:16 +02:00
po.Blur = float32(b)
} else {
return fmt.Errorf("Invalid blur: %s", args[0])
}
return nil
}
2021-04-26 13:52:50 +02:00
func applySharpenOption(po *ProcessingOptions, args []string) error {
2018-09-07 16:46:16 +02:00
if len(args) > 1 {
return fmt.Errorf("Invalid sharpen arguments: %v", args)
}
2019-05-28 13:39:20 +02:00
if s, err := strconv.ParseFloat(args[0], 32); err == nil && s >= 0 {
2018-09-07 16:46:16 +02:00
po.Sharpen = float32(s)
} else {
return fmt.Errorf("Invalid sharpen: %s", args[0])
}
return nil
}
func applyPixelateOption(po *ProcessingOptions, args []string) error {
2021-04-27 12:23:22 +02:00
if len(args) > 1 {
return fmt.Errorf("Invalid pixelate arguments: %v", args)
}
if p, err := strconv.Atoi(args[0]); err == nil && p >= 0 {
po.Pixelate = p
} else {
return fmt.Errorf("Invalid pixelate: %s", args[0])
}
return nil
}
2021-04-26 13:52:50 +02:00
func applyPresetOption(po *ProcessingOptions, args []string) error {
2018-09-07 19:41:06 +02:00
for _, preset := range args {
2021-04-26 13:52:50 +02:00
if p, ok := presets[preset]; ok {
2018-10-04 14:02:24 +02:00
if po.isPresetUsed(preset) {
2021-04-26 13:52:50 +02:00
log.Warningf("Recursive preset usage is detected: %s", preset)
continue
2018-10-04 14:02:24 +02:00
}
2021-04-26 13:52:50 +02:00
po.UsedPresets = append(po.UsedPresets, preset)
2018-10-04 14:02:24 +02:00
2021-04-26 13:52:50 +02:00
if err := applyURLOptions(po, p); err != nil {
return err
2018-09-07 19:41:06 +02:00
}
} else {
2019-08-28 15:01:02 +02:00
return fmt.Errorf("Unknown preset: %s", preset)
2018-09-07 19:41:06 +02:00
}
}
return nil
}
2021-04-26 13:52:50 +02:00
func applyWatermarkOption(po *ProcessingOptions, args []string) error {
2018-10-28 14:05:57 +02:00
if len(args) > 7 {
return fmt.Errorf("Invalid watermark arguments: %v", args)
}
2018-10-19 11:47:11 +02:00
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 fmt.Errorf("Invalid watermark opacity: %s", args[0])
}
2018-10-28 14:05:57 +02:00
if len(args) > 1 && len(args[1]) > 0 {
2018-10-19 11:47:11 +02:00
if args[1] == "re" {
po.Watermark.Replicate = true
2021-04-26 13:52:50 +02:00
} else if g, ok := gravityTypes[args[1]]; ok && g != GravityFocusPoint && g != GravitySmart {
2019-12-25 12:50:02 +02:00
po.Watermark.Gravity.Type = g
2018-10-19 11:47:11 +02:00
} else {
return fmt.Errorf("Invalid watermark position: %s", args[1])
}
}
2018-10-28 14:05:57 +02:00
if len(args) > 2 && len(args[2]) > 0 {
2018-10-19 11:47:11 +02:00
if x, err := strconv.Atoi(args[2]); err == nil {
2019-12-25 12:50:02 +02:00
po.Watermark.Gravity.X = float64(x)
2018-10-19 11:47:11 +02:00
} else {
return fmt.Errorf("Invalid watermark X offset: %s", args[2])
}
}
2018-10-28 14:05:57 +02:00
if len(args) > 3 && len(args[3]) > 0 {
2018-10-19 11:47:11 +02:00
if y, err := strconv.Atoi(args[3]); err == nil {
2019-12-25 12:50:02 +02:00
po.Watermark.Gravity.Y = float64(y)
2018-10-19 11:47:11 +02:00
} else {
return fmt.Errorf("Invalid watermark Y offset: %s", args[3])
}
}
2018-10-28 14:05:57 +02:00
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 fmt.Errorf("Invalid watermark scale: %s", args[4])
}
}
2018-10-19 11:47:11 +02:00
return nil
}
2021-04-26 13:52:50 +02:00
func applyFormatOption(po *ProcessingOptions, args []string) error {
2018-09-11 14:26:27 +02:00
if len(args) > 1 {
return fmt.Errorf("Invalid format arguments: %v", args)
}
2021-04-26 13:52:50 +02:00
if f, ok := imagetype.Types[args[0]]; ok {
2018-09-11 14:26:27 +02:00
po.Format = f
} else {
return fmt.Errorf("Invalid image format: %s", args[0])
}
2018-09-07 15:37:25 +02:00
return nil
}
2021-04-26 13:52:50 +02:00
func applyCacheBusterOption(po *ProcessingOptions, args []string) error {
2018-10-29 10:54:30 +02:00
if len(args) > 1 {
return fmt.Errorf("Invalid cache buster arguments: %v", args)
}
po.CacheBuster = args[0]
return nil
}
2021-04-26 13:52:50 +02:00
func applySkipProcessingFormatsOption(po *ProcessingOptions, args []string) error {
for _, format := range args {
2021-04-26 13:52:50 +02:00
if f, ok := imagetype.Types[format]; ok {
po.SkipProcessingFormats = append(po.SkipProcessingFormats, f)
} else {
return fmt.Errorf("Invalid image format in skip processing: %s", format)
}
}
return nil
}
2021-04-26 13:52:50 +02:00
func applyFilenameOption(po *ProcessingOptions, args []string) error {
if len(args) > 1 {
return fmt.Errorf("Invalid filename arguments: %v", args)
}
po.Filename = args[0]
return nil
}
2021-04-26 13:52:50 +02:00
func applyExpiresOption(po *ProcessingOptions, args []string) error {
if len(args) > 1 {
return fmt.Errorf("Invalid expires arguments: %v", args)
}
timestamp, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return fmt.Errorf("Invalid expires argument: %v", args[0])
}
if timestamp > 0 && timestamp < time.Now().Unix() {
2021-04-26 13:52:50 +02:00
return errExpiredURL
}
return nil
}
2021-04-26 13:52:50 +02:00
func applyStripMetadataOption(po *ProcessingOptions, args []string) error {
if len(args) > 1 {
return fmt.Errorf("Invalid strip metadata arguments: %v", args)
}
po.StripMetadata = parseBoolOption(args[0])
return nil
}
2021-04-26 13:52:50 +02:00
func applyStripColorProfileOption(po *ProcessingOptions, args []string) error {
2021-01-12 16:43:09 +02:00
if len(args) > 1 {
return fmt.Errorf("Invalid strip color profile arguments: %v", args)
}
po.StripColorProfile = parseBoolOption(args[0])
return nil
}
2021-04-26 13:52:50 +02:00
func applyAutoRotateOption(po *ProcessingOptions, args []string) error {
if len(args) > 1 {
return fmt.Errorf("Invalid auto rotate arguments: %v", args)
}
po.AutoRotate = parseBoolOption(args[0])
return nil
}
2021-04-26 13:52:50 +02:00
func applyURLOption(po *ProcessingOptions, name string, args []string) error {
2018-09-07 15:37:25 +02:00
switch name {
2018-10-03 17:36:52 +02:00
case "resize", "rs":
2019-08-13 13:56:14 +02:00
return applyResizeOption(po, args)
2018-10-03 17:36:52 +02:00
case "size", "s":
2019-08-13 13:56:14 +02:00
return applySizeOption(po, args)
case "resizing_type", "rt":
return applyResizingTypeOption(po, args)
2018-10-03 17:36:52 +02:00
case "width", "w":
2019-08-13 13:56:14 +02:00
return applyWidthOption(po, args)
2018-10-03 17:36:52 +02:00
case "height", "h":
2019-08-13 13:56:14 +02:00
return applyHeightOption(po, args)
case "min-width", "mw":
return applyMinWidthOption(po, args)
case "min-height", "mh":
return applyMinHeightOption(po, args)
case "dpr":
return applyDprOption(po, args)
2018-10-03 17:36:52 +02:00
case "enlarge", "el":
2019-08-13 13:56:14 +02:00
return applyEnlargeOption(po, args)
2019-02-21 17:55:20 +02:00
case "extend", "ex":
2019-08-13 13:56:14 +02:00
return applyExtendOption(po, args)
2018-10-03 17:36:52 +02:00
case "gravity", "g":
2019-08-13 13:56:14 +02:00
return applyGravityOption(po, args)
case "crop", "c":
2019-08-13 13:56:14 +02:00
return applyCropOption(po, args)
2020-01-17 11:54:50 +02:00
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)
2018-10-03 17:36:52 +02:00
case "background", "bg":
2019-08-13 13:56:14 +02:00
return applyBackgroundOption(po, args)
2018-10-03 17:36:52 +02:00
case "blur", "bl":
2019-08-13 13:56:14 +02:00
return applyBlurOption(po, args)
2018-10-03 17:36:52 +02:00
case "sharpen", "sh":
2019-08-13 13:56:14 +02:00
return applySharpenOption(po, args)
2021-04-27 12:23:22 +02:00
case "pixelate", "pix":
return applyPixelateOption(po, args)
2018-10-19 11:47:11 +02:00
case "watermark", "wm":
2019-08-13 13:56:14 +02:00
return applyWatermarkOption(po, args)
case "strip_metadata", "sm":
return applyStripMetadataOption(po, args)
2021-01-12 16:43:09 +02:00
case "strip_color_profile", "scp":
return applyStripColorProfileOption(po, args)
// Saving options
case "quality", "q":
return applyQualityOption(po, args)
2021-09-29 15:49:18 +02:00
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 "cachebuster", "cb":
return applyCacheBusterOption(po, args)
case "expires", "exp":
return applyExpiresOption(po, args)
case "filename", "fn":
return applyFilenameOption(po, args)
// Presets
case "preset", "pr":
return applyPresetOption(po, args)
}
2019-08-13 13:56:14 +02:00
return fmt.Errorf("Unknown processing option: %s", name)
}
2021-04-26 13:52:50 +02:00
func applyURLOptions(po *ProcessingOptions, options urlOptions) error {
2019-07-22 13:27:18 +02:00
for _, opt := range options {
2021-04-26 13:52:50 +02:00
if err := applyURLOption(po, opt.Name, opt.Args); err != nil {
return err
}
2018-09-07 15:37:25 +02:00
}
return nil
}
2021-04-26 13:52:50 +02:00
func defaultProcessingOptions(headers http.Header) (*ProcessingOptions, error) {
po := NewProcessingOptions()
2021-04-26 13:52:50 +02:00
headerAccept := headers.Get("Accept")
2018-09-07 15:37:25 +02:00
2021-04-26 13:52:50 +02:00
if strings.Contains(headerAccept, "image/webp") {
po.PreferWebP = config.EnableWebpDetection || config.EnforceWebp
po.EnforceWebP = config.EnforceWebp
2018-09-07 19:41:06 +02:00
}
2021-04-26 13:52:50 +02:00
if strings.Contains(headerAccept, "image/avif") {
po.PreferAvif = config.EnableAvifDetection || config.EnforceAvif
po.EnforceAvif = config.EnforceAvif
2018-09-07 19:41:06 +02:00
}
2021-04-26 13:52:50 +02:00
if config.EnableClientHints {
2021-10-11 13:50:12 +02:00
if headerDPR := headers.Get("DPR"); len(headerDPR) > 0 {
if dpr, err := strconv.ParseFloat(headerDPR, 64); err == nil && (dpr > 0 && dpr <= maxClientHintDPR) {
po.Dpr = dpr
}
}
2021-04-26 13:52:50 +02:00
if headerViewportWidth := headers.Get("Viewport-Width"); len(headerViewportWidth) > 0 {
if vw, err := strconv.Atoi(headerViewportWidth); err == nil {
po.Width = vw
}
}
2021-04-26 13:52:50 +02:00
if headerWidth := headers.Get("Width"); len(headerWidth) > 0 {
if w, err := strconv.Atoi(headerWidth); err == nil {
2021-10-11 13:50:12 +02:00
po.Width = imath.Scale(w, 1/po.Dpr)
2021-04-26 13:52:50 +02:00
}
}
}
2021-04-26 13:52:50 +02:00
if _, ok := presets["default"]; ok {
2019-09-11 12:40:07 +02:00
if err := applyPresetOption(po, []string{"default"}); err != nil {
return po, err
}
2018-09-11 12:33:36 +02:00
}
2019-09-11 12:40:07 +02:00
return po, nil
2018-09-11 12:33:36 +02:00
}
2021-04-26 13:52:50 +02:00
func parsePathOptions(parts []string, headers http.Header) (*ProcessingOptions, string, error) {
2021-12-07 11:16:59 +02:00
if _, ok := resizeTypes[parts[0]]; ok {
return nil, "", ierrors.New(
404,
"It looks like you're using the deprecated basic URL format",
"Invalid URL",
)
}
po, err := defaultProcessingOptions(headers)
2018-09-11 12:33:36 +02:00
if err != nil {
2021-04-26 13:52:50 +02:00
return nil, "", err
2018-09-11 12:33:36 +02:00
}
2018-09-07 19:41:06 +02:00
options, urlParts := parseURLOptions(parts)
2021-04-26 13:52:50 +02:00
if err = applyURLOptions(po, options); err != nil {
return nil, "", err
2018-09-07 15:37:25 +02:00
}
2021-04-26 13:52:50 +02:00
url, extension, err := DecodeURL(urlParts)
2018-09-07 15:37:25 +02:00
if err != nil {
2021-04-26 13:52:50 +02:00
return nil, "", err
2018-09-07 15:37:25 +02:00
}
2018-09-11 14:26:27 +02:00
if len(extension) > 0 {
if err = applyFormatOption(po, []string{extension}); err != nil {
2021-04-26 13:52:50 +02:00
return nil, "", err
2018-09-11 14:26:27 +02:00
}
2018-09-07 15:37:25 +02:00
}
2021-04-26 13:52:50 +02:00
return po, url, nil
2018-09-07 15:37:25 +02:00
}
2021-04-26 13:52:50 +02:00
func parsePathPresets(parts []string, headers http.Header) (*ProcessingOptions, string, error) {
po, err := defaultProcessingOptions(headers)
if err != nil {
2021-04-26 13:52:50 +02:00
return nil, "", err
}
presets := strings.Split(parts[0], ":")
urlParts := parts[1:]
if err = applyPresetOption(po, presets); err != nil {
2021-04-26 13:52:50 +02:00
return nil, "", err
}
2021-04-26 13:52:50 +02:00
url, extension, err := DecodeURL(urlParts)
if err != nil {
2021-04-26 13:52:50 +02:00
return nil, "", err
}
if len(extension) > 0 {
if err = applyFormatOption(po, []string{extension}); err != nil {
2021-04-26 13:52:50 +02:00
return nil, "", err
}
}
2021-04-26 13:52:50 +02:00
return po, url, nil
}
2021-04-26 13:52:50 +02:00
func ParsePath(path string, headers http.Header) (*ProcessingOptions, string, error) {
if path == "" || path == "/" {
return nil, "", ierrors.New(404, fmt.Sprintf("Invalid path: %s", path), "Invalid URL")
2020-04-07 13:54:00 +02:00
}
2021-04-26 13:52:50 +02:00
parts := strings.Split(strings.TrimPrefix(path, "/"), "/")
2021-04-26 13:52:50 +02:00
var (
imageURL string
po *ProcessingOptions
err error
)
2018-09-07 15:37:25 +02:00
2021-04-26 13:52:50 +02:00
if config.OnlyPresets {
po, imageURL, err = parsePathPresets(parts, headers)
2018-10-05 17:17:36 +02:00
} else {
2021-04-26 13:52:50 +02:00
po, imageURL, err = parsePathOptions(parts, headers)
2018-10-05 17:17:36 +02:00
}
if err != nil {
2021-04-26 13:52:50 +02:00
return nil, "", ierrors.New(404, err.Error(), "Invalid URL")
}
2021-04-26 13:52:50 +02:00
return po, imageURL, nil
2018-09-07 15:37:25 +02:00
}