1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2025-03-17 20:17:48 +02:00

Merge branch 'feature/cookie-passthrough' of github.com:LivingLogic/imgproxy into feature/cookie-passthrough

This commit is contained in:
DarthSim 2021-11-11 14:48:11 +06:00
commit 475e67640b
5 changed files with 84 additions and 9 deletions

View File

@ -27,6 +27,7 @@ var (
TTL int
CacheControlPassthrough bool
CookiePassthrough bool
SetCanonicalHeader bool
SoReuseport bool
@ -87,7 +88,8 @@ var (
ETagEnabled bool
BaseURL string
BaseURL string
CookieBaseURL string
Presets []string
OnlyPresets bool
@ -155,6 +157,7 @@ func Reset() {
TTL = 3600
CacheControlPassthrough = false
CookiePassthrough = false
SetCanonicalHeader = false
SoReuseport = false
@ -216,6 +219,7 @@ func Reset() {
ETagEnabled = false
BaseURL = ""
CookieBaseURL = ""
Presets = make([]string, 0)
OnlyPresets = false
@ -279,6 +283,7 @@ func Configure() error {
configurators.Int(&TTL, "IMGPROXY_TTL")
configurators.Bool(&CacheControlPassthrough, "IMGPROXY_CACHE_CONTROL_PASSTHROUGH")
configurators.Bool(&CookiePassthrough, "IMGPROXY_COOKIE_PASSTHROUGH")
configurators.Bool(&SetCanonicalHeader, "IMGPROXY_SET_CANONICAL_HEADER")
configurators.Bool(&SoReuseport, "IMGPROXY_SO_REUSEPORT")
@ -360,6 +365,7 @@ func Configure() error {
configurators.Bool(&ETagEnabled, "IMGPROXY_USE_ETAG")
configurators.String(&BaseURL, "IMGPROXY_BASE_URL")
configurators.String(&CookieBaseURL, "IMGPROXY_COOKIE_BASE_URL")
configurators.StringSlice(&Presets, "IMGPROXY_PRESETS")
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.
## 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
* `IMGPROXY_QUALITY`: default quality of the resulting image, percentage. Default: `80`;

View File

@ -7,6 +7,7 @@ import (
"io/ioutil"
"net"
"net/http"
"net/http/cookiejar"
"time"
"github.com/imgproxy/imgproxy/v3/config"
@ -113,7 +114,7 @@ func headersToStore(res *http.Response) map[string]string {
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)
if err != nil {
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)
for k, v := range header {
@ -160,13 +167,13 @@ func requestImage(imageURL string, header http.Header) (*http.Response, error) {
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
if len(redirectAllRequestsTo) > 0 {
imageURL = redirectAllRequestsTo
}
res, err := requestImage(imageURL, header)
res, err := requestImage(imageURL, header, jar)
if res != nil {
defer res.Body.Close()
}

View File

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

View File

@ -3,7 +3,10 @@ package main
import (
"context"
"fmt"
"net/url"
"net/http"
"net/http/cookiejar"
"golang.org/x/net/publicsuffix"
"strconv"
"strings"
"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) {
ctx, timeoutCancel := context.WithTimeout(r.Context(), time.Duration(config.WriteTimeout)*time.Second)
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"))
}
jar, err := cookieJarFromRequest(r)
if err != nil {
panic(err)
}
// 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 {
panic(ierrors.New(
@ -213,7 +263,7 @@ func handleProcessing(reqID string, rw http.ResponseWriter, r *http.Request) {
originData, err := func() (*imagedata.ImageData, error) {
defer metrics.StartDownloadingSegment(ctx)()
return imagedata.Download(imageURL, "source image", imgRequestHeader)
return imagedata.Download(imageURL, "source image", imgRequestHeader, jar)
}()
if err == nil {