diff --git a/config.go b/config.go index 60fcb861..f930e2e1 100644 --- a/config.go +++ b/config.go @@ -121,6 +121,9 @@ type config struct { Quality int GZipCompression int + EnableWebpDetection bool + EnforceWebp bool + Key []byte Salt []byte @@ -190,6 +193,9 @@ func init() { intEnvConfig(&conf.Quality, "IMGPROXY_QUALITY") intEnvConfig(&conf.GZipCompression, "IMGPROXY_GZIP_COMPRESSION") + boolEnvConfig(&conf.EnableWebpDetection, "IMGPROXY_ENABLE_WEBP_DETECTION") + boolEnvConfig(&conf.EnforceWebp, "IMGPROXY_ENFORCE_WEBP") + hexEnvConfig(&conf.Key, "IMGPROXY_KEY") hexEnvConfig(&conf.Salt, "IMGPROXY_SALT") diff --git a/processing_options.go b/processing_options.go index 0b122ccd..1bc13385 100644 --- a/processing_options.go +++ b/processing_options.go @@ -114,29 +114,25 @@ func (rt resizeType) String() string { return "" } -func decodeURL(parts []string) (string, imageType, error) { - var imgType imageType = imageTypeJPEG +func decodeURL(parts []string) (string, string, error) { + var extension string urlParts := strings.Split(strings.Join(parts, ""), ".") if len(urlParts) > 2 { - return "", 0, errors.New("Invalid url encoding") + return "", "", errors.New("Invalid url encoding") } if len(urlParts) == 2 { - if f, ok := imageTypes[urlParts[1]]; ok { - imgType = f - } else { - return "", 0, fmt.Errorf("Invalid image format: %s", urlParts[1]) - } + extension = urlParts[1] } url, err := base64.RawURLEncoding.DecodeString(urlParts[0]) if err != nil { - return "", 0, errors.New("Invalid url encoding") + return "", "", errors.New("Invalid url encoding") } - return string(url), imgType, nil + return string(url), extension, nil } func applyWidthOption(po *processingOptions, args []string) error { @@ -297,18 +293,35 @@ func applyPresetOption(po *processingOptions, args []string) error { return nil } -func applyFormatOption(po *processingOptions, imgType imageType) error { - if !vipsTypeSupportSave[imgType] { - return errors.New("Resulting image type not supported") +func applyFormatOption(po *processingOptions, args []string) error { + if len(args) > 1 { + return fmt.Errorf("Invalid format arguments: %v", args) } - po.Format = imgType + if conf.EnforceWebp && po.Format == imageTypeWEBP { + // Webp is enforced and already set as format + return nil + } + + if f, ok := imageTypes[args[0]]; ok { + po.Format = f + } else { + return fmt.Errorf("Invalid image format: %s", args[0]) + } + + if !vipsTypeSupportSave[po.Format] { + return errors.New("Resulting image type not supported") + } return nil } func applyProcessingOption(po *processingOptions, name string, args []string) error { switch name { + case "format": + if err := applyFormatOption(po, args); err != nil { + return err + } case "resize": if err := applyResizeOption(po, args); err != nil { return err @@ -376,7 +389,7 @@ func parseURLOptions(opts []string) (urlOptions, []string) { return parsed, rest } -func defaultProcessingOptions() (processingOptions, error) { +func defaultProcessingOptions(acceptHeader string) (processingOptions, error) { var err error po := processingOptions{ @@ -390,6 +403,10 @@ func defaultProcessingOptions() (processingOptions, error) { Sharpen: 0, } + if (conf.EnableWebpDetection || conf.EnforceWebp) && strings.Contains(acceptHeader, "image/webp") { + po.Format = imageTypeWEBP + } + if _, ok := conf.Presets["default"]; ok { err = applyPresetOption(&po, []string{"default"}) } @@ -397,8 +414,8 @@ func defaultProcessingOptions() (processingOptions, error) { return po, err } -func parsePathAdvanced(parts []string) (string, processingOptions, error) { - po, err := defaultProcessingOptions() +func parsePathAdvanced(parts []string, acceptHeader string) (string, processingOptions, error) { + po, err := defaultProcessingOptions(acceptHeader) if err != nil { return "", po, err } @@ -411,26 +428,28 @@ func parsePathAdvanced(parts []string) (string, processingOptions, error) { } } - url, imgType, err := decodeURL(urlParts) + url, extension, err := decodeURL(urlParts) if err != nil { return "", po, err } - if err := applyFormatOption(&po, imgType); err != nil { - return "", po, errors.New("Resulting image type not supported") + if len(extension) > 0 { + if err := applyFormatOption(&po, []string{extension}); err != nil { + return "", po, errors.New("Resulting image type not supported") + } } return string(url), po, nil } -func parsePathSimple(parts []string) (string, processingOptions, error) { +func parsePathSimple(parts []string, acceptHeader string) (string, processingOptions, error) { var err error if len(parts) < 6 { return "", processingOptions{}, errors.New("Invalid path") } - po, err := defaultProcessingOptions() + po, err := defaultProcessingOptions(acceptHeader) if err != nil { return "", po, err } @@ -453,13 +472,15 @@ func parsePathSimple(parts []string) (string, processingOptions, error) { return "", po, err } - url, imgType, err := decodeURL(parts[5:]) + url, extension, err := decodeURL(parts[5:]) if err != nil { return "", po, err } - if err := applyFormatOption(&po, imgType); err != nil { - return "", po, errors.New("Resulting image type not supported") + if len(extension) > 0 { + if err := applyFormatOption(&po, []string{extension}); err != nil { + return "", po, errors.New("Resulting image type not supported") + } } return string(url), po, nil @@ -469,6 +490,11 @@ func parsePath(r *http.Request) (string, processingOptions, error) { path := r.URL.Path parts := strings.Split(strings.TrimPrefix(path, "/"), "/") + var acceptHeader string + if h, ok := r.Header["Accept"]; ok { + acceptHeader = h[0] + } + if len(parts) < 3 { return "", processingOptions{}, errors.New("Invalid path") } @@ -480,8 +506,8 @@ func parsePath(r *http.Request) (string, processingOptions, error) { } if _, ok := resizeTypes[parts[1]]; ok { - return parsePathSimple(parts[1:]) + return parsePathSimple(parts[1:], acceptHeader) } else { - return parsePathAdvanced(parts[1:]) + return parsePathAdvanced(parts[1:], acceptHeader) } }