1
0
Viktor Sokolov 1 долоо хоног өмнө
parent
commit
baed50c7bc

+ 6 - 0
config.go

@@ -2,6 +2,7 @@ package imgproxy
 
 import (
 	"github.com/imgproxy/imgproxy/v3/auximageprovider"
+	"github.com/imgproxy/imgproxy/v3/cookies"
 	"github.com/imgproxy/imgproxy/v3/ensure"
 	"github.com/imgproxy/imgproxy/v3/fetcher"
 	processinghandler "github.com/imgproxy/imgproxy/v3/handlers/processing"
@@ -30,6 +31,7 @@ type Config struct {
 	Security       security.Config
 	Processing     processing.Config
 	OptionsParser  optionsparser.Config
+	Cookies        cookies.Config
 }
 
 // NewDefaultConfig creates a new default configuration
@@ -96,5 +98,9 @@ func LoadConfigFromEnv(c *Config) (*Config, error) {
 		return nil, err
 	}
 
+	if _, err = cookies.LoadConfigFromEnv(&c.Cookies); err != nil {
+		return nil, err
+	}
+
 	return c, nil
 }

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 513 - 507
config/config.go


+ 49 - 0
cookies/config.go

@@ -0,0 +1,49 @@
+package cookies
+
+import (
+	"errors"
+
+	"github.com/imgproxy/imgproxy/v3/ensure"
+	"github.com/imgproxy/imgproxy/v3/env"
+)
+
+var (
+	IMGPROXY_COOKIE_PASSTHROUGH     = env.Describe("IMGPROXY_COOKIE_PASSTHROUGH", "boolean")
+	IMGPROXY_COOKIE_PASSTHROUGH_ALL = env.Describe("IMGPROXY_COOKIE_PASSTHROUGH_ALL", "boolean")
+	IMGPROXY_COOKIE_BASE_URL        = env.Describe("IMGPROXY_COOKIE_BASE_URL", "string")
+)
+
+// Config holds cookie-related configuration.
+type Config struct {
+	CookiePassthrough    bool
+	CookiePassthroughAll bool
+	CookieBaseURL        string
+}
+
+// NewDefaultConfig creates a new Config instance with default values.
+func NewDefaultConfig() Config {
+	return Config{
+		CookiePassthroughAll: false,
+		CookieBaseURL:        "",
+		CookiePassthrough:    false,
+	}
+}
+
+// LoadConfigFromEnv creates a new Config instance loading values from environment variables.
+func LoadConfigFromEnv(c *Config) (*Config, error) {
+	c = ensure.Ensure(c, NewDefaultConfig)
+
+	err := errors.Join(
+		env.Bool(&c.CookiePassthrough, IMGPROXY_COOKIE_PASSTHROUGH),
+		env.Bool(&c.CookiePassthroughAll, IMGPROXY_COOKIE_PASSTHROUGH_ALL),
+		env.String(&c.CookieBaseURL, IMGPROXY_COOKIE_BASE_URL),
+	)
+
+	return c, err
+}
+
+// Validate checks if the configuration is valid
+func (c *Config) Validate() error {
+	// No validation needed for cookie config currently
+	return nil
+}

+ 45 - 38
cookies/cookies.go

@@ -5,65 +5,72 @@ import (
 	"net/http"
 	"net/http/cookiejar"
 	"net/url"
+	"slices"
 	"sync"
 
 	"golang.org/x/net/publicsuffix"
 
-	"github.com/imgproxy/imgproxy/v3/config"
+	"github.com/imgproxy/imgproxy/v3/httpheaders"
 	"github.com/imgproxy/imgproxy/v3/ierrors"
 )
 
-type cookieError string
-
-func (e cookieError) Error() string { return string(e) }
-
-type anyCookieJarEntry struct {
-	Name   string
-	Value  string
-	Quoted bool
+// Cookies represents a cookies manager
+type Cookies struct {
+	config *Config
 }
 
-// anyCookieJar is a cookie jar that stores all cookies in memory
+// cookieJar is a cookie jar that stores all cookies in memory
 // and doesn't care about domains and paths
-type anyCookieJar struct {
-	entries []anyCookieJarEntry
+type cookieJar struct {
+	entries []*http.Cookie
 	mu      sync.RWMutex
 }
 
-func (j *anyCookieJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
+// New creates a new Cookies instance
+func New(config *Config) (*Cookies, error) {
+	if err := config.Validate(); err != nil {
+		return nil, err
+	}
+
+	cookies := &Cookies{config: config}
+
+	return cookies, nil
+}
+
+// SetCookies stores the cookies in the jar. For each source cookie it creates
+// a new cookie with only Name, Value and Quoted fields set.
+func (j *cookieJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
 	j.mu.Lock()
 	defer j.mu.Unlock()
 
+	// Remove all unimportant cookie params
 	for _, c := range cookies {
-		entry := anyCookieJarEntry{
+		j.entries = append(j.entries, &http.Cookie{
 			Name:   c.Name,
 			Value:  c.Value,
 			Quoted: c.Quoted,
-		}
-		j.entries = append(j.entries, entry)
+		})
 	}
 }
 
-func (j *anyCookieJar) Cookies(u *url.URL) []*http.Cookie {
+// Cookies returns all stored cookies
+func (j *cookieJar) Cookies(u *url.URL) []*http.Cookie {
 	j.mu.RLock()
 	defer j.mu.RUnlock()
 
-	cookies := make([]*http.Cookie, 0, len(j.entries))
-	for _, e := range j.entries {
-		c := http.Cookie{
-			Name:   e.Name,
-			Value:  e.Value,
-			Quoted: e.Quoted,
-		}
-		cookies = append(cookies, &c)
-	}
-
-	return cookies
+	// NOTE: do we need to clone, or we could just return a ref?
+	return slices.Clone(j.entries)
 }
 
-func JarFromRequest(r *http.Request) (jar http.CookieJar, err error) {
-	if config.CookiePassthroughAll {
-		jar = &anyCookieJar{}
+// JarFromRequest creates a cookie jar from the given HTTP request
+func (c *Cookies) JarFromRequest(r *http.Request) (jar http.CookieJar, err error) {
+	// If cookie passthrough is disabled, return nil jar
+	if !c.config.CookiePassthrough {
+		return nil, nil
+	}
+
+	if c.config.CookiePassthroughAll {
+		jar = &cookieJar{}
 	} else {
 		jar, err = cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List})
 		if err != nil {
@@ -77,22 +84,22 @@ func JarFromRequest(r *http.Request) (jar http.CookieJar, err error) {
 
 	var cookieBase *url.URL
 
-	if !config.CookiePassthroughAll {
-		if len(config.CookieBaseURL) > 0 {
-			if cookieBase, err = url.Parse(config.CookieBaseURL); err != nil {
+	if !c.config.CookiePassthroughAll {
+		if len(c.config.CookieBaseURL) > 0 {
+			if cookieBase, err = url.Parse(c.config.CookieBaseURL); err != nil {
 				return nil, ierrors.Wrap(cookieError(fmt.Sprintf("can't parse cookie base URL: %s", err)), 0)
 			}
 		}
 
 		if cookieBase == nil {
-			scheme := r.Header.Get("X-Forwarded-Proto")
+			scheme := r.Header.Get(httpheaders.XForwardedProto)
 			if len(scheme) == 0 {
 				scheme = "http"
 			}
 
-			host := r.Header.Get("X-Forwarded-Host")
+			host := r.Header.Get(httpheaders.XForwardedHost)
 			if len(host) == 0 {
-				host = r.Header.Get("Host")
+				host = r.Header.Get(httpheaders.Host)
 			}
 			if len(host) == 0 {
 				host = r.Host
@@ -102,7 +109,7 @@ func JarFromRequest(r *http.Request) (jar http.CookieJar, err error) {
 				return jar, nil
 			}
 
-			port := r.Header.Get("X-Forwarded-Port")
+			port := r.Header.Get(httpheaders.XForwardedPort)
 			if len(port) > 0 {
 				host = host + ":" + port
 			}

+ 5 - 0
cookies/errors.go

@@ -0,0 +1,5 @@
+package cookies
+
+type cookieError string
+
+func (e cookieError) Error() string { return string(e) }

+ 0 - 4
handlers/processing/config.go

@@ -9,7 +9,6 @@ import (
 )
 
 var (
-	IMGPROXY_COOKIE_PASSTHROUGH        = env.Describe("IMGPROXY_COOKIE_PASSTHROUGH", "boolean")
 	IMGPROXY_REPORT_DOWNLOADING_ERRORS = env.Describe("IMGPROXY_REPORT_DOWNLOADING_ERRORS", "boolean")
 	IMGPROXY_LAST_MODIFIED_ENABLED     = env.Describe("IMGPROXY_LAST_MODIFIED_ENABLED", "boolean")
 	IMGPROXY_ETAG_ENABLED              = env.Describe("IMGPROXY_ETAG_ENABLED", "boolean")
@@ -20,7 +19,6 @@ var (
 
 // Config represents handler config
 type Config struct {
-	CookiePassthrough       bool // Whether to passthrough cookies
 	ReportDownloadingErrors bool // Whether to report downloading errors
 	LastModifiedEnabled     bool // Whether to enable Last-Modified
 	ETagEnabled             bool // Whether to enable ETag
@@ -32,7 +30,6 @@ type Config struct {
 // NewDefaultConfig creates a new configuration with defaults
 func NewDefaultConfig() Config {
 	return Config{
-		CookiePassthrough:       false,
 		ReportDownloadingErrors: true,
 		LastModifiedEnabled:     true,
 		ETagEnabled:             true,
@@ -47,7 +44,6 @@ func LoadConfigFromEnv(c *Config) (*Config, error) {
 	c = ensure.Ensure(c, NewDefaultConfig)
 
 	err := errors.Join(
-		env.Bool(&c.CookiePassthrough, IMGPROXY_COOKIE_PASSTHROUGH),
 		env.Bool(&c.ReportDownloadingErrors, IMGPROXY_REPORT_DOWNLOADING_ERRORS),
 		env.Bool(&c.LastModifiedEnabled, IMGPROXY_LAST_MODIFIED_ENABLED),
 		env.Bool(&c.ETagEnabled, IMGPROXY_ETAG_ENABLED),

+ 2 - 0
handlers/processing/handler.go

@@ -6,6 +6,7 @@ import (
 	"net/url"
 
 	"github.com/imgproxy/imgproxy/v3/auximageprovider"
+	"github.com/imgproxy/imgproxy/v3/cookies"
 	"github.com/imgproxy/imgproxy/v3/errorreport"
 	"github.com/imgproxy/imgproxy/v3/handlers"
 	"github.com/imgproxy/imgproxy/v3/handlers/stream"
@@ -30,6 +31,7 @@ type HandlerContext interface {
 	Security() *security.Checker
 	OptionsParser() *optionsparser.Parser
 	Processor() *processing.Processor
+	Cookies() *cookies.Cookies
 }
 
 // Handler handles image processing requests

+ 3 - 6
handlers/processing/request_methods.go

@@ -7,7 +7,6 @@ import (
 	"net/http"
 	"strconv"
 
-	"github.com/imgproxy/imgproxy/v3/cookies"
 	"github.com/imgproxy/imgproxy/v3/errorreport"
 	"github.com/imgproxy/imgproxy/v3/handlers"
 	"github.com/imgproxy/imgproxy/v3/httpheaders"
@@ -75,11 +74,9 @@ func (r *request) makeDownloadOptions(ctx context.Context, h http.Header) imaged
 func (r *request) fetchImage(ctx context.Context, do imagedata.DownloadOptions) (imagedata.ImageData, http.Header, error) {
 	var err error
 
-	if r.config.CookiePassthrough {
-		do.CookieJar, err = cookies.JarFromRequest(r.req)
-		if err != nil {
-			return nil, nil, ierrors.Wrap(err, 0, ierrors.WithCategory(handlers.CategoryDownload))
-		}
+	do.CookieJar, err = r.Cookies().JarFromRequest(r.req)
+	if err != nil {
+		return nil, nil, ierrors.Wrap(err, 0, ierrors.WithCategory(handlers.CategoryDownload))
 	}
 
 	return r.ImageDataFactory().DownloadAsync(ctx, r.imageURL, "source image", do)

+ 1 - 8
handlers/stream/config.go

@@ -15,9 +15,6 @@ var (
 
 // Config represents the configuration for the image streamer
 type Config struct {
-	// CookiePassthrough indicates whether cookies should be passed through to the image response
-	CookiePassthrough bool
-
 	// PassthroughRequestHeaders specifies the request headers to include in the passthrough response
 	PassthroughRequestHeaders []string
 
@@ -28,7 +25,6 @@ type Config struct {
 // NewDefaultConfig returns a new Config instance with default values.
 func NewDefaultConfig() Config {
 	return Config{
-		CookiePassthrough: false,
 		PassthroughRequestHeaders: []string{
 			httpheaders.IfNoneMatch,
 			httpheaders.IfModifiedSince,
@@ -49,10 +45,7 @@ func NewDefaultConfig() Config {
 // LoadConfigFromEnv loads config variables from environment
 func LoadConfigFromEnv(c *Config) (*Config, error) {
 	c = ensure.Ensure(c, NewDefaultConfig)
-
-	err := env.Bool(&c.CookiePassthrough, IMGPROXY_COOKIE_PASSTHROUGH)
-
-	return c, err
+	return c, nil
 }
 
 // Validate checks config for errors

+ 4 - 6
handlers/stream/handler.go

@@ -37,6 +37,7 @@ var (
 type Handler struct {
 	config  *Config          // Configuration for the streamer
 	fetcher *fetcher.Fetcher // Fetcher instance to handle image fetching
+	cookies *cookies.Cookies // Cookies manager
 }
 
 // request holds the parameters and state for a single streaming request
@@ -50,7 +51,7 @@ type request struct {
 }
 
 // New creates new handler object
-func New(config *Config, fetcher *fetcher.Fetcher) (*Handler, error) {
+func New(config *Config, fetcher *fetcher.Fetcher, cookies *cookies.Cookies) (*Handler, error) {
 	if err := config.Validate(); err != nil {
 		return nil, err
 	}
@@ -58,6 +59,7 @@ func New(config *Config, fetcher *fetcher.Fetcher) (*Handler, error) {
 	return &Handler{
 		fetcher: fetcher,
 		config:  config,
+		cookies: cookies,
 	}, nil
 }
 
@@ -134,11 +136,7 @@ func (s *request) execute(ctx context.Context) error {
 
 // getCookieJar returns non-empty cookie jar if cookie passthrough is enabled
 func (s *request) getCookieJar() (http.CookieJar, error) {
-	if !s.handler.config.CookiePassthrough {
-		return nil, nil
-	}
-
-	return cookies.JarFromRequest(s.imageRequest)
+	return s.handler.cookies.JarFromRequest(s.imageRequest)
 }
 
 // getImageRequestHeaders returns a new http.Header containing only

+ 21 - 2
handlers/stream/handler_test.go

@@ -12,6 +12,7 @@ import (
 	"github.com/stretchr/testify/suite"
 
 	"github.com/imgproxy/imgproxy/v3/config"
+	"github.com/imgproxy/imgproxy/v3/cookies"
 	"github.com/imgproxy/imgproxy/v3/fetcher"
 	"github.com/imgproxy/imgproxy/v3/httpheaders"
 	"github.com/imgproxy/imgproxy/v3/logger"
@@ -29,6 +30,9 @@ type HandlerTestSuite struct {
 	rwConf    testutil.LazyObj[*responsewriter.Config]
 	rwFactory testutil.LazyObj[*responsewriter.Factory]
 
+	cookieConf testutil.LazyObj[*cookies.Config]
+	cookies    testutil.LazyObj[*cookies.Cookies]
+
 	config  testutil.LazyObj[*Config]
 	handler testutil.LazyObj[*Handler]
 
@@ -55,6 +59,21 @@ func (s *HandlerTestSuite) SetupSuite() {
 		},
 	)
 
+	s.cookieConf, _ = testutil.NewLazySuiteObj(
+		s,
+		func() (*cookies.Config, error) {
+			c := cookies.NewDefaultConfig()
+			return &c, nil
+		},
+	)
+
+	s.cookies, _ = testutil.NewLazySuiteObj(
+		s,
+		func() (*cookies.Cookies, error) {
+			return cookies.New(s.cookieConf())
+		},
+	)
+
 	s.config, _ = testutil.NewLazySuiteObj(
 		s,
 		func() (*Config, error) {
@@ -72,7 +91,7 @@ func (s *HandlerTestSuite) SetupSuite() {
 			fetcher, err := fetcher.New(&fc)
 			s.Require().NoError(err)
 
-			return New(s.config(), fetcher)
+			return New(s.config(), fetcher, s.cookies())
 		},
 	)
 
@@ -391,7 +410,7 @@ func (s *HandlerTestSuite) TestHandlerErrorResponse() {
 
 // TestHandlerCookiePassthrough tests the cookie passthrough behavior of the streaming service.
 func (s *HandlerTestSuite) TestHandlerCookiePassthrough() {
-	s.config().CookiePassthrough = true
+	s.cookieConf().CookiePassthrough = true
 
 	data := s.testData.Read("test1.png")
 

+ 1 - 0
httpheaders/headers.go

@@ -62,6 +62,7 @@ const (
 	XContentTypeOptions             = "X-Content-Type-Options"
 	XForwardedFor                   = "X-Forwarded-For"
 	XForwardedHost                  = "X-Forwarded-Host"
+	XForwardedPort                  = "X-Forwarded-Port"
 	XForwardedProto                 = "X-Forwarded-Proto"
 	XFrameOptions                   = "X-Frame-Options"
 	XOriginWidth                    = "X-Origin-Width"

+ 13 - 1
imgproxy.go

@@ -6,6 +6,7 @@ import (
 	"time"
 
 	"github.com/imgproxy/imgproxy/v3/auximageprovider"
+	"github.com/imgproxy/imgproxy/v3/cookies"
 	"github.com/imgproxy/imgproxy/v3/fetcher"
 	healthhandler "github.com/imgproxy/imgproxy/v3/handlers/health"
 	landinghandler "github.com/imgproxy/imgproxy/v3/handlers/landing"
@@ -45,6 +46,7 @@ type Imgproxy struct {
 	security         *security.Checker
 	optionsParser    *optionsparser.Parser
 	processor        *processing.Processor
+	cookies          *cookies.Cookies
 	config           *Config
 }
 
@@ -87,6 +89,11 @@ func New(ctx context.Context, config *Config) (*Imgproxy, error) {
 		return nil, err
 	}
 
+	cookies, err := cookies.New(&config.Cookies)
+	if err != nil {
+		return nil, err
+	}
+
 	imgproxy := &Imgproxy{
 		workers:          workers,
 		fallbackImage:    fallbackImage,
@@ -97,12 +104,13 @@ func New(ctx context.Context, config *Config) (*Imgproxy, error) {
 		security:         security,
 		optionsParser:    optionsParser,
 		processor:        processor,
+		cookies:          cookies,
 	}
 
 	imgproxy.handlers.Health = healthhandler.New()
 	imgproxy.handlers.Landing = landinghandler.New()
 
-	imgproxy.handlers.Stream, err = streamhandler.New(&config.Handlers.Stream, fetcher)
+	imgproxy.handlers.Stream, err = streamhandler.New(&config.Handlers.Stream, fetcher, cookies)
 	if err != nil {
 		return nil, err
 	}
@@ -223,3 +231,7 @@ func (i *Imgproxy) OptionsParser() *optionsparser.Parser {
 func (i *Imgproxy) Processor() *processing.Processor {
 	return i.processor
 }
+
+func (i *Imgproxy) Cookies() *cookies.Cookies {
+	return i.cookies
+}

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно