1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2025-12-11 23:48:13 +02:00

Add option to pass through incoming cookies in the image request.

This commit is contained in:
Walter Doerwald
2021-11-09 12:14:05 +01:00
parent d8dae48c4f
commit abbfb61ce6
5 changed files with 84 additions and 9 deletions

View File

@@ -27,6 +27,7 @@ var (
TTL int TTL int
CacheControlPassthrough bool CacheControlPassthrough bool
CookiePassthrough bool
SetCanonicalHeader bool SetCanonicalHeader bool
SoReuseport bool SoReuseport bool
@@ -87,7 +88,8 @@ var (
ETagEnabled bool ETagEnabled bool
BaseURL string BaseURL string
CookieBaseURL string
Presets []string Presets []string
OnlyPresets bool OnlyPresets bool
@@ -155,6 +157,7 @@ func Reset() {
TTL = 3600 TTL = 3600
CacheControlPassthrough = false CacheControlPassthrough = false
CookiePassthrough = false
SetCanonicalHeader = false SetCanonicalHeader = false
SoReuseport = false SoReuseport = false
@@ -216,6 +219,7 @@ func Reset() {
ETagEnabled = false ETagEnabled = false
BaseURL = "" BaseURL = ""
CookieBaseURL = ""
Presets = make([]string, 0) Presets = make([]string, 0)
OnlyPresets = false OnlyPresets = false
@@ -279,6 +283,7 @@ func Configure() error {
configurators.Int(&TTL, "IMGPROXY_TTL") configurators.Int(&TTL, "IMGPROXY_TTL")
configurators.Bool(&CacheControlPassthrough, "IMGPROXY_CACHE_CONTROL_PASSTHROUGH") configurators.Bool(&CacheControlPassthrough, "IMGPROXY_CACHE_CONTROL_PASSTHROUGH")
configurators.Bool(&CookiePassthrough, "IMGPROXY_COOKIE_PASSTHROUGH")
configurators.Bool(&SetCanonicalHeader, "IMGPROXY_SET_CANONICAL_HEADER") configurators.Bool(&SetCanonicalHeader, "IMGPROXY_SET_CANONICAL_HEADER")
configurators.Bool(&SoReuseport, "IMGPROXY_SO_REUSEPORT") configurators.Bool(&SoReuseport, "IMGPROXY_SO_REUSEPORT")
@@ -360,6 +365,7 @@ func Configure() error {
configurators.Bool(&ETagEnabled, "IMGPROXY_USE_ETAG") configurators.Bool(&ETagEnabled, "IMGPROXY_USE_ETAG")
configurators.String(&BaseURL, "IMGPROXY_BASE_URL") configurators.String(&BaseURL, "IMGPROXY_BASE_URL")
configurators.String(&CookieBaseURL, "IMGPROXY_COOKIE_BASE_URL")
configurators.StringSlice(&Presets, "IMGPROXY_PRESETS") configurators.StringSlice(&Presets, "IMGPROXY_PRESETS")
if err := configurators.StringSliceFile(&Presets, *presetsPath); err != nil { if err := configurators.StringSliceFile(&Presets, *presetsPath); err != nil {

View File

@@ -88,6 +88,17 @@ Also you may want imgproxy to respond with the same error message that it writes
* `IMGPROXY_DEVELOPMENT_ERRORS_MODE`: when true, imgproxy will respond with detailed error messages. Not recommended for production because some errors may contain stack trace. * `IMGPROXY_DEVELOPMENT_ERRORS_MODE`: when true, imgproxy will respond with detailed error messages. Not recommended for production because some errors may contain stack trace.
## Cookies
imgproxy can pass through cookies in image requests. This can be activated with `IMGPROXY_COOKIE_PASSTHROUGH`. Unfortunately a `Cookie` header doesn't contain information for which URLs these cookies are applicable, so imgproxy can only assume (or must be told).
When cookie forwarding is activated, imgproxy by default assumes the scope of the cookies to be all URLs with the same hostname/port and request scheme as given by the headers `X-Forwarded-Host`, `X-Forwarded-Port`, `X-Forwarded-Scheme` or `Host`. To change that use `IMGPROXY_COOKIE_BASE_URL`.
* `IMGPROXY_COOKIE_PASSTHROUGH`: when `true`, incoming cookies will be passed through to the image request if they are applicable for the image URL. Default: false;
* `IMGPROXY_COOKIE_BASE_URL`: when set, assume that cookies have a scope of this URL for the incoming request (instead of using the request headers). If the cookies are applicable to the image URL too, they will be passed along in the image request.
## Compression ## Compression
* `IMGPROXY_QUALITY`: default quality of the resulting image, percentage. Default: `80`; * `IMGPROXY_QUALITY`: default quality of the resulting image, percentage. Default: `80`;

View File

@@ -7,6 +7,7 @@ import (
"io/ioutil" "io/ioutil"
"net" "net"
"net/http" "net/http"
"net/http/cookiejar"
"time" "time"
"github.com/imgproxy/imgproxy/v3/config" "github.com/imgproxy/imgproxy/v3/config"
@@ -113,7 +114,7 @@ func headersToStore(res *http.Response) map[string]string {
return m return m
} }
func requestImage(imageURL string, header http.Header) (*http.Response, error) { func requestImage(imageURL string, header http.Header, jar *cookiejar.Jar) (*http.Response, error) {
req, err := http.NewRequest("GET", imageURL, nil) req, err := http.NewRequest("GET", imageURL, nil)
if err != nil { if err != nil {
return nil, ierrors.New(404, err.Error(), msgSourceImageIsUnreachable) return nil, ierrors.New(404, err.Error(), msgSourceImageIsUnreachable)
@@ -127,6 +128,12 @@ func requestImage(imageURL string, header http.Header) (*http.Response, error) {
) )
} }
if jar != nil {
for _, cookie := range jar.Cookies(req.URL) {
req.AddCookie(cookie)
}
}
req.Header.Set("User-Agent", config.UserAgent) req.Header.Set("User-Agent", config.UserAgent)
for k, v := range header { for k, v := range header {
@@ -160,13 +167,13 @@ func requestImage(imageURL string, header http.Header) (*http.Response, error) {
return res, nil return res, nil
} }
func download(imageURL string, header http.Header) (*ImageData, error) { func download(imageURL string, header http.Header, jar *cookiejar.Jar) (*ImageData, error) {
// We use this for testing // We use this for testing
if len(redirectAllRequestsTo) > 0 { if len(redirectAllRequestsTo) > 0 {
imageURL = redirectAllRequestsTo imageURL = redirectAllRequestsTo
} }
res, err := requestImage(imageURL, header) res, err := requestImage(imageURL, header, jar)
if res != nil { if res != nil {
defer res.Body.Close() defer res.Body.Close()
} }

View File

@@ -5,6 +5,7 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"net/http" "net/http"
"net/http/cookiejar"
"os" "os"
"strings" "strings"
"sync" "sync"
@@ -70,7 +71,7 @@ func loadWatermark() (err error) {
} }
if len(config.WatermarkURL) > 0 { if len(config.WatermarkURL) > 0 {
Watermark, err = Download(config.WatermarkURL, "watermark", nil) Watermark, err = Download(config.WatermarkURL, "watermark", nil, nil)
return return
} }
@@ -89,7 +90,7 @@ func loadFallbackImage() (err error) {
} }
if len(config.FallbackImageURL) > 0 { if len(config.FallbackImageURL) > 0 {
FallbackImage, err = Download(config.FallbackImageURL, "fallback image", nil) FallbackImage, err = Download(config.FallbackImageURL, "fallback image", nil, nil)
return return
} }
@@ -127,8 +128,8 @@ func FromFile(path, desc string) (*ImageData, error) {
return imgdata, nil return imgdata, nil
} }
func Download(imageURL, desc string, header http.Header) (*ImageData, error) { func Download(imageURL, desc string, header http.Header, jar *cookiejar.Jar) (*ImageData, error) {
imgdata, err := download(imageURL, header) imgdata, err := download(imageURL, header, jar)
if err != nil { if err != nil {
if nmErr, ok := err.(*ErrorNotModified); ok { if nmErr, ok := err.(*ErrorNotModified); ok {
nmErr.Message = fmt.Sprintf("Can't download %s: %s", desc, nmErr.Message) nmErr.Message = fmt.Sprintf("Can't download %s: %s", desc, nmErr.Message)

View File

@@ -3,7 +3,10 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"net/url"
"net/http" "net/http"
"net/http/cookiejar"
"golang.org/x/net/publicsuffix"
"strconv" "strconv"
"strings" "strings"
"time" "time"
@@ -135,6 +138,48 @@ func respondWithNotModified(reqID string, r *http.Request, rw http.ResponseWrite
) )
} }
func cookieJarFromRequest(r *http.Request) (*cookiejar.Jar, error) {
jar, err := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
if err != nil {
return nil, err
}
if config.CookiePassthrough && r != nil {
cookieBase := config.CookieBaseURL
if len(cookieBase) == 0 {
scheme := r.Header.Get("X-Forwarded-Proto")
if len(scheme) == 0 {
scheme = "http"
}
host := r.Header.Get("X-Forwarded-Host")
if len(host) == 0 {
host = r.Header.Get("Host")
}
if len(host) == 0 {
cookieBase = ""
} else {
port := r.Header.Get("X-Forwarded-Port")
if len(port) > 0 {
host = host + ":" + port
}
cookieBase = scheme + "://" + host + "/"
}
}
if len(cookieBase) > 0 {
cookieBaseURL, err := url.Parse(cookieBase)
if err != nil {
return nil, err
}
jar.SetCookies(cookieBaseURL, r.Cookies())
}
}
return jar, nil
}
func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) { func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
ctx, timeoutCancel := context.WithTimeout(r.Context(), time.Duration(config.WriteTimeout)*time.Second) ctx, timeoutCancel := context.WithTimeout(r.Context(), time.Duration(config.WriteTimeout)*time.Second)
defer timeoutCancel() defer timeoutCancel()
@@ -175,6 +220,11 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
panic(ierrors.New(404, fmt.Sprintf("Source URL is not allowed: %s", imageURL), "Invalid source")) panic(ierrors.New(404, fmt.Sprintf("Source URL is not allowed: %s", imageURL), "Invalid source"))
} }
jar, err := cookieJarFromRequest(r)
if err != nil {
panic(err)
}
// SVG is a special case. Though saving to svg is not supported, SVG->SVG is. // SVG is a special case. Though saving to svg is not supported, SVG->SVG is.
if !vips.SupportsSave(po.Format) && po.Format != imagetype.Unknown && po.Format != imagetype.SVG { if !vips.SupportsSave(po.Format) && po.Format != imagetype.Unknown && po.Format != imagetype.SVG {
panic(ierrors.New( panic(ierrors.New(
@@ -213,7 +263,7 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
originData, err := func() (*imagedata.ImageData, error) { originData, err := func() (*imagedata.ImageData, error) {
defer metrics.StartDownloadingSegment(ctx)() defer metrics.StartDownloadingSegment(ctx)()
return imagedata.Download(imageURL, "source image", imgRequestHeader) return imagedata.Download(imageURL, "source image", imgRequestHeader, jar)
}() }()
if err == nil { if err == nil {