Jelajahi Sumber

adds Client Hints support using env variable. (#98)

* adds Client Hints support using env variable.

* Update processing_options.go

Co-Authored-By: selul <marius.cristea@vertistudio.com>

* improve client hints support based on the feedback

* fix build error and remove redundant line in docs.
Marius Cristea 6 tahun lalu
induk
melakukan
a1a74450d1
3 mengubah file dengan 39 tambahan dan 15 penghapusan
  1. 2 0
      config.go
  2. 9 1
      docs/configuration.md
  3. 28 14
      processing_options.go

+ 2 - 0
config.go

@@ -131,6 +131,7 @@ type config struct {
 
 	EnableWebpDetection bool
 	EnforceWebp         bool
+	EnableClientHints   bool
 
 	Key           []byte
 	Salt          []byte
@@ -221,6 +222,7 @@ func init() {
 
 	boolEnvConfig(&conf.EnableWebpDetection, "IMGPROXY_ENABLE_WEBP_DETECTION")
 	boolEnvConfig(&conf.EnforceWebp, "IMGPROXY_ENFORCE_WEBP")
+	boolEnvConfig(&conf.EnableClientHints, "IMGPROXY_ENABLE_CLIENT_HINTS")
 
 	hexEnvConfig(&conf.Key, "IMGPROXY_KEY")
 	hexEnvConfig(&conf.Salt, "IMGPROXY_SALT")

+ 9 - 1
docs/configuration.md

@@ -69,7 +69,15 @@ imgproxy can use the `Accept` HTTP header to detect if the browser supports WebP
 
 When WebP support detection is enabled, please take care to configure your CDN or caching proxy to take the `Accept` HTTP header into account while caching.
 
-**Warning**: Headers cannot be signed. This means that an attacker can bypass your CDN cache by changing the `Accept` HTTP header. Have this in mind when configuring your production caching setup.
+**Warning**: Headers cannot be signed. This means that an attacker can bypass your CDN cache by changing the `Accept` HTTP headers. Have this in mind when configuring your production caching setup.
+
+## Client Hints support
+
+imgproxy can use the `Width` or `Viewport-Width` HTTP header to determine the width of the image container using Client Hints when the width argument is ommited. 
+
+* `IMGPROXY_ENABLE_CLIENT_HINTS`: enables Client Hints support when the width is ommited for automatic responsive images . Read [here](https://developers.google.com/web/updates/2015/09/automating-resource-selection-with-client-hints) details about Client Hints.
+
+**Warning**: Headers cannot be signed. This means that an attacker can bypass your CDN cache by changing the `Width` or `Viewport-Width` HTTP headers. Have this in mind when configuring your production caching setup.
 
 ### Watermark
 

+ 28 - 14
processing_options.go

@@ -30,6 +30,12 @@ const (
 	imageTypeGIF     = imageType(C.GIF)
 )
 
+type processingHeaders struct {
+	Accept        string
+	Width         string
+	ViewportWidth string
+}
+
 var imageTypes = map[string]imageType{
 	"jpeg": imageTypeJPEG,
 	"jpg":  imageTypeJPEG,
@@ -685,7 +691,7 @@ func parseURLOptions(opts []string) (urlOptions, []string) {
 	return parsed, rest
 }
 
-func defaultProcessingOptions(acceptHeader string) (*processingOptions, error) {
+func defaultProcessingOptions(headers *processingHeaders) (*processingOptions, error) {
 	var err error
 
 	po := processingOptions{
@@ -702,10 +708,19 @@ func defaultProcessingOptions(acceptHeader string) (*processingOptions, error) {
 		UsedPresets: make([]string, 0, len(conf.Presets)),
 	}
 
-	if (conf.EnableWebpDetection || conf.EnforceWebp) && strings.Contains(acceptHeader, "image/webp") {
+	if (conf.EnableWebpDetection || conf.EnforceWebp) && strings.Contains(headers.Accept, "image/webp") {
 		po.Format = imageTypeWEBP
 	}
-
+	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 _, ok := conf.Presets["default"]; ok {
 		err = applyPresetOption(&po, []string{"default"})
 	}
@@ -713,8 +728,8 @@ func defaultProcessingOptions(acceptHeader string) (*processingOptions, error) {
 	return &po, err
 }
 
-func parsePathAdvanced(parts []string, acceptHeader string) (string, *processingOptions, error) {
-	po, err := defaultProcessingOptions(acceptHeader)
+func parsePathAdvanced(parts []string, headers *processingHeaders) (string, *processingOptions, error) {
+	po, err := defaultProcessingOptions(headers)
 	if err != nil {
 		return "", po, err
 	}
@@ -739,14 +754,14 @@ func parsePathAdvanced(parts []string, acceptHeader string) (string, *processing
 	return url, po, nil
 }
 
-func parsePathSimple(parts []string, acceptHeader string) (string, *processingOptions, error) {
+func parsePathSimple(parts []string, headers *processingHeaders) (string, *processingOptions, error) {
 	var err error
 
 	if len(parts) < 6 {
 		return "", nil, errInvalidPath
 	}
 
-	po, err := defaultProcessingOptions(acceptHeader)
+	po, err := defaultProcessingOptions(headers)
 	if err != nil {
 		return "", po, err
 	}
@@ -787,11 +802,6 @@ func parsePath(ctx context.Context, r *http.Request) (context.Context, 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 ctx, errInvalidPath
 	}
@@ -801,15 +811,19 @@ func parsePath(ctx context.Context, r *http.Request) (context.Context, error) {
 			return ctx, err
 		}
 	}
+	headers := &processingHeaders{}
+	headers.Accept = r.Header.Get("Accept")
+	headers.Width = r.Header.Get("Width")
+	headers.ViewportWidth = r.Header.Get("Viewport-Width")
 
 	var imageURL string
 	var po *processingOptions
 	var err error
 
 	if _, ok := resizeTypes[parts[1]]; ok {
-		imageURL, po, err = parsePathSimple(parts[1:], acceptHeader)
+		imageURL, po, err = parsePathSimple(parts[1:], headers)
 	} else {
-		imageURL, po, err = parsePathAdvanced(parts[1:], acceptHeader)
+		imageURL, po, err = parsePathAdvanced(parts[1:], headers)
 	}
 
 	if err != nil {