1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2024-11-24 08:12:38 +02:00
imgproxy/processing_options.go

1152 lines
25 KiB
Go
Raw Normal View History

2018-09-07 15:37:25 +02:00
package main
import (
2018-10-05 17:17:36 +02:00
"context"
2018-09-07 15:37:25 +02:00
"encoding/base64"
"errors"
"fmt"
"net/http"
2018-11-02 17:35:21 +02:00
"net/url"
"regexp"
2018-09-07 15:37:25 +02:00
"strconv"
"strings"
2019-10-10 16:35:17 +02:00
"sync"
2020-02-27 18:30:31 +02:00
"github.com/imgproxy/imgproxy/v2/structdiff"
2018-09-07 15:37:25 +02:00
)
2019-07-22 13:27:18 +02:00
type urlOption struct {
Name string
Args []string
}
type urlOptions []urlOption
2018-09-07 19:41:06 +02:00
type processingHeaders struct {
Accept string
Width string
ViewportWidth string
DPR string
}
type gravityType int
const (
gravityUnknown gravityType = iota
gravityCenter
gravityNorth
gravityEast
gravitySouth
gravityWest
2018-10-19 11:47:11 +02:00
gravityNorthWest
gravityNorthEast
gravitySouthWest
gravitySouthEast
gravitySmart
2018-09-11 13:23:28 +02:00
gravityFocusPoint
)
var gravityTypes = map[string]gravityType{
2018-10-19 11:47:11 +02:00
"ce": gravityCenter,
"no": gravityNorth,
"ea": gravityEast,
"so": gravitySouth,
"we": gravityWest,
"nowe": gravityNorthWest,
"noea": gravityNorthEast,
"sowe": gravitySouthWest,
"soea": gravitySouthEast,
"sm": gravitySmart,
"fp": gravityFocusPoint,
2018-09-11 13:23:28 +02:00
}
type resizeType int
const (
resizeFit resizeType = iota
resizeFill
resizeCrop
2019-06-24 14:50:17 +02:00
resizeAuto
)
2018-09-07 15:37:25 +02:00
var resizeTypes = map[string]resizeType{
"fit": resizeFit,
"fill": resizeFill,
"crop": resizeCrop,
2019-06-24 14:50:17 +02:00
"auto": resizeAuto,
2018-09-07 15:37:25 +02:00
}
type rgbColor struct{ R, G, B uint8 }
2018-10-02 14:20:23 +02:00
var hexColorRegex = regexp.MustCompile("^([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$")
const (
hexColorLongFormat = "%02x%02x%02x"
hexColorShortFormat = "%1x%1x%1x"
)
2019-06-20 14:49:25 +02:00
type gravityOptions struct {
Type gravityType
X, Y float64
}
2019-12-25 12:50:02 +02:00
type extendOptions struct {
Enabled bool
Gravity gravityOptions
}
type cropOptions struct {
Width int
Height int
Gravity gravityOptions
}
type paddingOptions struct {
Enabled bool
Top int
Right int
Bottom int
Left int
}
2020-01-17 11:54:50 +02:00
type trimOptions struct {
Enabled bool
Threshold float64
Smart bool
Color rgbColor
EqualHor bool
EqualVer bool
2020-01-17 11:54:50 +02:00
}
2018-10-19 11:47:11 +02:00
type watermarkOptions struct {
Enabled bool
Opacity float64
Replicate bool
2019-12-25 12:50:02 +02:00
Gravity gravityOptions
2018-10-28 14:05:57 +02:00
Scale float64
2018-10-19 11:47:11 +02:00
}
2018-09-07 15:37:25 +02:00
type processingOptions struct {
ResizingType resizeType
Width int
Height int
Dpr float64
Gravity gravityOptions
Enlarge bool
Extend extendOptions
Crop cropOptions
Padding paddingOptions
Trim trimOptions
Format imageType
Quality int
MaxBytes int
Flatten bool
Background rgbColor
Blur float32
Sharpen float32
StripMetadata bool
2018-10-19 11:47:11 +02:00
2018-10-29 10:54:30 +02:00
CacheBuster string
2018-10-19 11:47:11 +02:00
Watermark watermarkOptions
PreferWebP bool
EnforceWebP bool
Filename string
2018-10-04 14:02:24 +02:00
UsedPresets []string
2018-09-07 15:37:25 +02:00
}
2018-10-05 17:17:36 +02:00
const (
imageURLCtxKey = ctxKey("imageUrl")
processingOptionsCtxKey = ctxKey("processingOptions")
2018-11-02 17:35:21 +02:00
urlTokenPlain = "plain"
2018-11-15 13:19:27 +02:00
maxClientHintDPR = 8
2018-11-20 14:53:44 +02:00
msgForbidden = "Forbidden"
msgInvalidURL = "Invalid URL"
msgInvalidSource = "Invalid Source"
2018-10-05 17:17:36 +02:00
)
2018-09-11 13:48:16 +02:00
func (gt gravityType) String() string {
for k, v := range gravityTypes {
if v == gt {
return k
}
}
return ""
}
func (gt gravityType) MarshalJSON() ([]byte, error) {
for k, v := range gravityTypes {
if v == gt {
return []byte(fmt.Sprintf("%q", k)), nil
}
}
return []byte("null"), nil
}
2018-09-11 13:48:16 +02:00
func (rt resizeType) String() string {
for k, v := range resizeTypes {
if v == rt {
return k
}
}
return ""
}
func (rt resizeType) MarshalJSON() ([]byte, error) {
for k, v := range resizeTypes {
if v == rt {
return []byte(fmt.Sprintf("%q", k)), nil
}
}
return []byte("null"), nil
}
2019-10-10 16:35:17 +02:00
var (
_newProcessingOptions processingOptions
newProcessingOptionsOnce sync.Once
)
2019-09-11 12:40:07 +02:00
func newProcessingOptions() *processingOptions {
2019-10-10 16:35:17 +02:00
newProcessingOptionsOnce.Do(func() {
_newProcessingOptions = processingOptions{
ResizingType: resizeFit,
Width: 0,
Height: 0,
Gravity: gravityOptions{Type: gravityCenter},
Enlarge: false,
Extend: extendOptions{Enabled: false, Gravity: gravityOptions{Type: gravityCenter}},
Padding: paddingOptions{Enabled: false},
Trim: trimOptions{Enabled: false, Threshold: 10, Smart: true},
Quality: conf.Quality,
MaxBytes: 0,
Format: imageTypeUnknown,
Background: rgbColor{255, 255, 255},
Blur: 0,
Sharpen: 0,
Dpr: 1,
Watermark: watermarkOptions{Opacity: 1, Replicate: false, Gravity: gravityOptions{Type: gravityCenter}},
StripMetadata: conf.StripMetadata,
2019-10-10 16:35:17 +02:00
}
})
po := _newProcessingOptions
po.UsedPresets = make([]string, 0, len(conf.Presets))
return &po
2019-09-11 12:40:07 +02:00
}
2018-11-02 17:35:21 +02:00
func (po *processingOptions) isPresetUsed(name string) bool {
for _, usedName := range po.UsedPresets {
if usedName == name {
return true
}
}
return false
}
func (po *processingOptions) presetUsed(name string) {
po.UsedPresets = append(po.UsedPresets, name)
}
2019-10-10 16:35:17 +02:00
func (po *processingOptions) Diff() structdiff.Entries {
return structdiff.Diff(newProcessingOptions(), po)
}
func (po *processingOptions) String() string {
2019-10-10 16:35:17 +02:00
return po.Diff().String()
}
func (po *processingOptions) MarshalJSON() ([]byte, error) {
return po.Diff().MarshalJSON()
}
func colorFromHex(hexcolor string) (rgbColor, error) {
c := rgbColor{}
if !hexColorRegex.MatchString(hexcolor) {
return c, fmt.Errorf("Invalid hex color: %s", hexcolor)
}
if len(hexcolor) == 3 {
fmt.Sscanf(hexcolor, hexColorShortFormat, &c.R, &c.G, &c.B)
c.R *= 17
c.G *= 17
c.B *= 17
} else {
fmt.Sscanf(hexcolor, hexColorLongFormat, &c.R, &c.G, &c.B)
}
return c, nil
}
2018-11-02 17:35:21 +02:00
func decodeBase64URL(parts []string) (string, string, error) {
var format string
2019-08-28 16:36:11 +02:00
encoded := strings.Join(parts, "")
urlParts := strings.Split(encoded, ".")
if len(urlParts[0]) == 0 {
return "", "", errors.New("Image URL is empty")
}
2018-11-02 17:35:21 +02:00
if len(urlParts) > 2 {
2019-08-28 16:36:11 +02:00
return "", "", fmt.Errorf("Multiple formats are specified: %s", encoded)
2018-10-04 14:02:24 +02:00
}
2018-11-02 17:35:21 +02:00
if len(urlParts) == 2 && len(urlParts[1]) > 0 {
format = urlParts[1]
}
2019-01-14 12:09:14 +02:00
imageURL, err := base64.RawURLEncoding.DecodeString(strings.TrimRight(urlParts[0], "="))
2018-11-02 17:35:21 +02:00
if err != nil {
2019-08-28 16:36:11 +02:00
return "", "", fmt.Errorf("Invalid url encoding: %s", encoded)
2018-11-02 17:35:21 +02:00
}
fullURL := fmt.Sprintf("%s%s", conf.BaseURL, string(imageURL))
2018-11-02 17:35:21 +02:00
return fullURL, format, nil
2018-10-04 14:02:24 +02:00
}
2018-11-02 17:35:21 +02:00
func decodePlainURL(parts []string) (string, string, error) {
var format string
2018-09-07 15:37:25 +02:00
2019-08-28 16:36:11 +02:00
encoded := strings.Join(parts, "/")
urlParts := strings.Split(encoded, "@")
if len(urlParts[0]) == 0 {
return "", "", errors.New("Image URL is empty")
}
2018-09-07 15:37:25 +02:00
if len(urlParts) > 2 {
2019-08-28 16:36:11 +02:00
return "", "", fmt.Errorf("Multiple formats are specified: %s", encoded)
2018-09-07 15:37:25 +02:00
}
2018-11-02 17:35:21 +02:00
if len(urlParts) == 2 && len(urlParts[1]) > 0 {
format = urlParts[1]
2018-09-07 15:37:25 +02:00
}
2019-08-28 16:36:11 +02:00
unescaped, err := url.PathUnescape(urlParts[0])
if err != nil {
return "", "", fmt.Errorf("Invalid url encoding: %s", encoded)
}
fullURL := fmt.Sprintf("%s%s", conf.BaseURL, unescaped)
return fullURL, format, nil
2018-11-02 17:35:21 +02:00
}
func decodeURL(parts []string) (string, string, error) {
if len(parts) == 0 {
2019-08-28 16:36:11 +02:00
return "", "", errors.New("Image URL is empty")
2018-09-07 15:37:25 +02:00
}
2018-11-02 17:35:21 +02:00
if parts[0] == urlTokenPlain && len(parts) > 1 {
return decodePlainURL(parts[1:])
}
return decodeBase64URL(parts)
2018-09-07 15:37:25 +02:00
}
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 {
logWarning("`%s` is not a valid boolean value. Treated as false", str)
}
return b
}
2019-06-20 14:49:25 +02:00
func isGravityOffcetValid(gravity gravityType, offset float64) bool {
if gravity == gravityCenter {
return true
}
return offset >= 0 && (gravity != gravityFocusPoint || offset <= 1)
}
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])
}
2019-06-20 14:49:25 +02:00
if g.Type == gravitySmart && nArgs > 1 {
return fmt.Errorf("Invalid gravity arguments: %v", args)
} else if g.Type == gravityFocusPoint && nArgs != 3 {
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
}
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])
}
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
}
func applyEnlargeOption(po *processingOptions, args []string) error {
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
}
2019-02-21 17:55:20 +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
}
if po.Extend.Gravity.Type == gravitySmart {
return errors.New("extend doesn't support smart gravity")
}
}
2019-02-21 17:55:20 +02:00
return nil
}
2018-09-07 15:37:25 +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
}
2018-10-03 17:34:13 +02:00
func applyResizingTypeOption(po *processingOptions, args []string) error {
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
}
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
}
2018-11-15 13:19:27 +02:00
func applyDprOption(po *processingOptions, args []string) error {
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
}
2018-09-07 15:37:25 +02:00
func applyGravityOption(po *processingOptions, args []string) error {
return parseGravity(&po.Gravity, args)
}
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
}
if err := parseDimension(&po.Crop.Width, "crop width", args[0]); err != nil {
return err
}
2018-09-11 13:23:28 +02:00
if len(args) > 1 {
if err := parseDimension(&po.Crop.Height, "crop height", args[1]); err != nil {
return err
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
}
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
}
2020-01-17 11:54: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 {
if c, err := 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
}
2018-10-30 14:20:02 +02:00
func applyQualityOption(po *processingOptions, args []string) error {
if len(args) > 1 {
return fmt.Errorf("Invalid quality arguments: %v", args)
}
if q, err := strconv.Atoi(args[0]); err == nil && q > 0 && q <= 100 {
po.Quality = q
} else {
return fmt.Errorf("Invalid quality: %s", args[0])
}
return nil
}
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
}
2018-10-02 14:20:23 +02:00
func applyBackgroundOption(po *processingOptions, args []string) error {
switch len(args) {
case 1:
if len(args[0]) == 0 {
po.Flatten = false
} else if c, err := 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
}
2018-09-07 16:46:16 +02:00
func applyBlurOption(po *processingOptions, args []string) error {
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
}
func applySharpenOption(po *processingOptions, args []string) error {
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
}
2018-09-07 19:41:06 +02:00
func applyPresetOption(po *processingOptions, args []string) error {
for _, preset := range args {
if p, ok := conf.Presets[preset]; ok {
2018-10-04 14:02:24 +02:00
if po.isPresetUsed(preset) {
logWarning("Recursive preset usage is detected: %s", preset)
continue
2018-10-04 14:02:24 +02:00
}
po.presetUsed(preset)
if err := applyProcessingOptions(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
}
2018-10-19 11:47:11 +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
2018-10-19 13:31:09 +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
}
2018-09-11 14:26:27 +02:00
func applyFormatOption(po *processingOptions, args []string) error {
if len(args) > 1 {
return fmt.Errorf("Invalid format arguments: %v", args)
}
if f, ok := imageTypes[args[0]]; ok {
po.Format = f
} else {
return fmt.Errorf("Invalid image format: %s", args[0])
}
2019-10-01 14:08:30 +02:00
if !imageTypeSaveSupport(po.Format) {
2019-08-28 16:36:11 +02:00
return fmt.Errorf("Resulting image format is not supported: %s", po.Format)
2018-09-11 14:26:27 +02:00
}
2018-09-07 15:37:25 +02:00
return nil
}
2018-10-29 10:54:30 +02:00
func applyCacheBusterOption(po *processingOptions, args []string) error {
if len(args) > 1 {
return fmt.Errorf("Invalid cache buster arguments: %v", args)
}
po.CacheBuster = args[0]
return nil
}
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
}
func applyStripMetadataOption(po *processingOptions, args []string) error {
if len(args[0]) > 0 {
po.StripMetadata = parseBoolOption(args[0])
}
return nil
}
2018-09-07 15:37:25 +02:00
func applyProcessingOption(po *processingOptions, name string, args []string) error {
switch name {
2018-10-03 17:36:52 +02:00
case "format", "f", "ext":
2019-08-13 13:56:14 +02:00
return applyFormatOption(po, args)
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 "resizing_type", "rt":
2019-08-13 13:56:14 +02:00
return applyResizingTypeOption(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)
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)
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-11-15 13:19:27 +02:00
case "dpr":
2019-08-13 13:56:14 +02:00
return applyDprOption(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)
2018-10-30 14:20:02 +02:00
case "quality", "q":
2019-08-13 13:56:14 +02:00
return applyQualityOption(po, args)
case "max_bytes", "mb":
return applyMaxBytesOption(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)
2018-10-19 11:47:11 +02:00
case "watermark", "wm":
2019-08-13 13:56:14 +02:00
return applyWatermarkOption(po, args)
2018-10-03 17:36:52 +02:00
case "preset", "pr":
2019-08-13 13:56:14 +02:00
return applyPresetOption(po, args)
2018-10-29 10:54:30 +02:00
case "cachebuster", "cb":
2019-08-13 13:56:14 +02:00
return applyCacheBusterOption(po, args)
case "strip_metadata", "sm":
return applyStripMetadataOption(po, args)
case "filename", "fn":
2019-08-13 13:56:14 +02:00
return applyFilenameOption(po, args)
}
2019-08-13 13:56:14 +02:00
return fmt.Errorf("Unknown processing option: %s", name)
}
func applyProcessingOptions(po *processingOptions, options urlOptions) error {
2019-07-22 13:27:18 +02:00
for _, opt := range options {
if err := applyProcessingOption(po, opt.Name, opt.Args); err != nil {
return err
}
2018-09-07 15:37:25 +02:00
}
return nil
}
func isAllowedSource(imageURL string) bool {
if len(conf.AllowedSources) == 0 {
return true
}
for _, val := range conf.AllowedSources {
if strings.HasPrefix(imageURL, string(val)) {
return true
}
}
return false
}
2018-09-07 19:41:06 +02:00
func parseURLOptions(opts []string) (urlOptions, []string) {
2019-07-22 13:27:18 +02:00
parsed := make(urlOptions, 0, len(opts))
2018-09-07 19:41:06 +02:00
urlStart := len(opts) + 1
2018-09-07 15:37:25 +02:00
2018-09-07 19:41:06 +02:00
for i, opt := range opts {
args := strings.Split(opt, ":")
2018-09-07 15:37:25 +02:00
if len(args) == 1 {
urlStart = i
break
}
2019-07-22 13:27:18 +02:00
parsed = append(parsed, urlOption{Name: args[0], Args: args[1:]})
2018-09-07 19:41:06 +02:00
}
var rest []string
if urlStart < len(opts) {
rest = opts[urlStart:]
} else {
rest = []string{}
}
return parsed, rest
}
func defaultProcessingOptions(headers *processingHeaders) (*processingOptions, error) {
2019-09-11 12:40:07 +02:00
po := newProcessingOptions()
2018-09-11 12:33:36 +02:00
if strings.Contains(headers.Accept, "image/webp") {
po.PreferWebP = conf.EnableWebpDetection || conf.EnforceWebp
po.EnforceWebP = conf.EnforceWebp
2018-09-11 14:26:27 +02:00
}
if conf.EnableClientHints && len(headers.ViewportWidth) > 0 {
if vw, err := strconv.Atoi(headers.ViewportWidth); err == nil {
po.Width = vw
}
}
if conf.EnableClientHints && len(headers.Width) > 0 {
if w, err := strconv.Atoi(headers.Width); err == nil {
po.Width = w
}
}
if conf.EnableClientHints && len(headers.DPR) > 0 {
2019-05-28 13:39:20 +02:00
if dpr, err := strconv.ParseFloat(headers.DPR, 64); err == nil && (dpr > 0 && dpr <= maxClientHintDPR) {
2018-11-15 13:19:27 +02:00
po.Dpr = dpr
}
}
2018-09-11 12:33:36 +02:00
if _, ok := conf.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
}
func parsePathAdvanced(parts []string, headers *processingHeaders) (string, *processingOptions, error) {
po, err := defaultProcessingOptions(headers)
2018-09-11 12:33:36 +02:00
if err != nil {
return "", po, err
}
2018-09-07 19:41:06 +02:00
options, urlParts := parseURLOptions(parts)
if err = applyProcessingOptions(po, options); err != nil {
return "", po, err
2018-09-07 15:37:25 +02:00
}
2018-09-11 14:26:27 +02:00
url, extension, err := decodeURL(urlParts)
2018-09-07 15:37:25 +02:00
if err != nil {
return "", po, err
}
2018-09-11 14:26:27 +02:00
if len(extension) > 0 {
if err = applyFormatOption(po, []string{extension}); err != nil {
2018-10-05 17:17:36 +02:00
return "", po, err
2018-09-11 14:26:27 +02:00
}
2018-09-07 15:37:25 +02:00
}
2018-11-02 17:35:21 +02:00
return url, po, nil
2018-09-07 15:37:25 +02:00
}
func parsePathPresets(parts []string, headers *processingHeaders) (string, *processingOptions, error) {
po, err := defaultProcessingOptions(headers)
if err != nil {
return "", po, err
}
presets := strings.Split(parts[0], ":")
urlParts := parts[1:]
if err = applyPresetOption(po, presets); err != nil {
return "", nil, err
}
url, extension, err := decodeURL(urlParts)
if err != nil {
return "", po, err
}
if len(extension) > 0 {
if err = applyFormatOption(po, []string{extension}); err != nil {
return "", po, err
}
}
return url, po, nil
}
2018-11-06 13:19:34 +02:00
func parsePathBasic(parts []string, headers *processingHeaders) (string, *processingOptions, error) {
2018-09-07 15:37:25 +02:00
if len(parts) < 6 {
2019-08-28 16:36:11 +02:00
return "", nil, fmt.Errorf("Invalid basic URL format arguments: %s", strings.Join(parts, "/"))
2018-09-11 12:33:36 +02:00
}
po, err := defaultProcessingOptions(headers)
2018-09-11 12:33:36 +02:00
if err != nil {
return "", po, err
2018-09-07 15:37:25 +02:00
}
2019-10-11 13:05:20 +02:00
po.ResizingType = resizeTypes[parts[0]]
2018-09-07 15:37:25 +02:00
2018-10-09 15:03:10 +02:00
if err = applyWidthOption(po, parts[1:2]); err != nil {
2018-09-07 15:37:25 +02:00
return "", po, err
}
2018-10-09 15:03:10 +02:00
if err = applyHeightOption(po, parts[2:3]); err != nil {
2018-09-07 15:37:25 +02:00
return "", po, err
}
2018-10-09 15:03:10 +02:00
if err = applyGravityOption(po, strings.Split(parts[3], ":")); err != nil {
2018-09-07 15:37:25 +02:00
return "", po, err
}
2018-10-09 15:03:10 +02:00
if err = applyEnlargeOption(po, parts[4:5]); err != nil {
2018-09-07 15:37:25 +02:00
return "", po, err
}
2018-09-11 14:26:27 +02:00
url, extension, err := decodeURL(parts[5:])
2018-09-07 15:37:25 +02:00
if err != nil {
return "", po, err
}
2018-09-11 14:26:27 +02:00
if len(extension) > 0 {
2018-10-09 15:03:10 +02:00
if err := applyFormatOption(po, []string{extension}); err != nil {
2018-10-05 17:17:36 +02:00
return "", po, err
2018-09-11 14:26:27 +02:00
}
2018-09-07 15:37:25 +02:00
}
2018-11-02 17:35:21 +02:00
return url, po, nil
2018-09-07 15:37:25 +02:00
}
func parsePath(ctx context.Context, r *http.Request) (context.Context, error) {
2020-04-07 13:54:00 +02:00
var err error
path := trimAfter(r.RequestURI, '?')
2020-04-07 13:54:00 +02:00
if len(conf.PathPrefix) > 0 {
path = strings.TrimPrefix(path, conf.PathPrefix)
}
path = strings.TrimPrefix(path, "/")
parts := strings.Split(path, "/")
2018-09-07 15:37:25 +02:00
if len(parts) < 2 {
2019-08-28 16:36:11 +02:00
return ctx, newError(404, fmt.Sprintf("Invalid path: %s", path), msgInvalidURL)
2018-09-07 15:37:25 +02:00
}
if !conf.AllowInsecure {
if err = validatePath(parts[0], strings.TrimPrefix(path, parts[0])); err != nil {
2018-11-20 14:53:44 +02:00
return ctx, newError(403, err.Error(), msgForbidden)
}
}
2018-11-02 18:39:38 +02:00
headers := &processingHeaders{
Accept: r.Header.Get("Accept"),
Width: r.Header.Get("Width"),
ViewportWidth: r.Header.Get("Viewport-Width"),
DPR: r.Header.Get("DPR"),
2018-11-02 18:39:38 +02:00
}
2018-09-07 15:37:25 +02:00
2018-10-05 17:17:36 +02:00
var imageURL string
2018-10-09 15:03:10 +02:00
var po *processingOptions
2018-10-05 17:17:36 +02:00
if conf.OnlyPresets {
imageURL, po, err = parsePathPresets(parts[1:], headers)
} else if _, ok := resizeTypes[parts[1]]; ok {
2018-11-06 13:19:34 +02:00
imageURL, po, err = parsePathBasic(parts[1:], headers)
2018-10-05 17:17:36 +02:00
} else {
imageURL, po, err = parsePathAdvanced(parts[1:], headers)
2018-10-05 17:17:36 +02:00
}
if err != nil {
2018-11-20 14:53:44 +02:00
return ctx, newError(404, err.Error(), msgInvalidURL)
}
if !isAllowedSource(imageURL) {
2020-07-31 16:07:39 +02:00
return ctx, newError(404, "Invalid source", msgInvalidSource)
}
2018-10-05 17:17:36 +02:00
ctx = context.WithValue(ctx, imageURLCtxKey, imageURL)
2018-10-09 15:03:10 +02:00
ctx = context.WithValue(ctx, processingOptionsCtxKey, po)
2018-10-05 17:17:36 +02:00
2018-11-20 14:53:44 +02:00
return ctx, nil
2018-10-05 17:17:36 +02:00
}
func getImageURL(ctx context.Context) string {
str, _ := ctx.Value(imageURLCtxKey).(string)
return str
2018-10-05 17:17:36 +02:00
}
2018-10-05 18:20:29 +02:00
func getProcessingOptions(ctx context.Context) *processingOptions {
2018-10-05 17:17:36 +02:00
return ctx.Value(processingOptionsCtxKey).(*processingOptions)
2018-09-07 15:37:25 +02:00
}