Browse Source

Don't rely on global config in processing

DarthSim 2 weeks ago
parent
commit
b9cbc0f97b
4 changed files with 51 additions and 42 deletions
  1. 9 9
      integration/processing_handler_test.go
  2. 5 0
      processing/config.go
  3. 36 31
      processing/processing.go
  4. 1 2
      processing/watermark.go

+ 9 - 9
integration/processing_handler_test.go

@@ -36,9 +36,6 @@ func (s *ProcessingHandlerTestSuite) SetupSubTest() {
 }
 
 func (s *ProcessingHandlerTestSuite) TestSignatureValidationFailure() {
-	config.Keys = [][]byte{[]byte("test-key")}
-	config.Salts = [][]byte{[]byte("test-salt")}
-
 	tt := []struct {
 		name       string
 		url        string
@@ -63,6 +60,9 @@ func (s *ProcessingHandlerTestSuite) TestSignatureValidationFailure() {
 
 	for _, tc := range tt {
 		s.Run(tc.name, func() {
+			s.Config().Security.Keys = [][]byte{[]byte("test-key")}
+			s.Config().Security.Salts = [][]byte{[]byte("test-salt")}
+
 			res := s.GET(tc.url)
 			s.Require().Equal(tc.statusCode, res.StatusCode)
 		})
@@ -110,9 +110,9 @@ func (s *ProcessingHandlerTestSuite) TestSourceValidation() {
 
 	for _, tc := range tt {
 		s.Run(tc.name, func() {
-			config.AllowedSources = make([]*regexp.Regexp, len(tc.allowedSources))
+			s.Config().Security.AllowedSources = make([]*regexp.Regexp, len(tc.allowedSources))
 			for i, pattern := range tc.allowedSources {
-				config.AllowedSources[i] = configurators.RegexpFromPattern(pattern)
+				s.Config().Security.AllowedSources[i] = configurators.RegexpFromPattern(pattern)
 			}
 
 			res := s.GET(tc.requestPath)
@@ -467,7 +467,7 @@ func (s *ProcessingHandlerTestSuite) TestModifiedSinceReqCompareTooOldLastModifi
 }
 
 func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvg() {
-	config.AlwaysRasterizeSvg = true
+	s.Config().Processing.AlwaysRasterizeSvg = true
 
 	res := s.GET("/unsafe/rs:fill:40:40/plain/local:///test1.svg")
 
@@ -476,7 +476,7 @@ func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvg() {
 }
 
 func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvgWithEnforceAvif() {
-	config.AlwaysRasterizeSvg = true
+	s.Config().Processing.AlwaysRasterizeSvg = true
 	s.Config().Options.EnforceWebp = true
 
 	res := s.GET("/unsafe/plain/local:///test1.svg", http.Header{"Accept": []string{"image/webp"}})
@@ -486,7 +486,7 @@ func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvgWithEnforceAvif() {
 }
 
 func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvgDisabled() {
-	config.AlwaysRasterizeSvg = false
+	s.Config().Processing.AlwaysRasterizeSvg = false
 	s.Config().Options.EnforceWebp = true
 
 	res := s.GET("/unsafe/plain/local:///test1.svg")
@@ -496,7 +496,7 @@ func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvgDisabled() {
 }
 
 func (s *ProcessingHandlerTestSuite) TestAlwaysRasterizeSvgWithFormat() {
-	config.AlwaysRasterizeSvg = true
+	s.Config().Processing.AlwaysRasterizeSvg = true
 	s.Config().Options.SkipProcessingFormats = []imagetype.Type{imagetype.SVG}
 
 	res := s.GET("/unsafe/plain/local:///test1.svg@svg")

+ 5 - 0
processing/config.go

@@ -17,6 +17,8 @@ type Config struct {
 	WatermarkOpacity    float64
 	DisableShrinkOnLoad bool
 	UseLinearColorspace bool
+	SanitizeSvg         bool
+	AlwaysRasterizeSvg  bool
 }
 
 // NewConfig creates a new Config instance with the given parameters.
@@ -28,6 +30,7 @@ func NewDefaultConfig() Config {
 			imagetype.PNG,
 			imagetype.GIF,
 		},
+		SanitizeSvg: true,
 	}
 }
 
@@ -39,6 +42,8 @@ func LoadConfigFromEnv(c *Config) (*Config, error) {
 	c.DisableShrinkOnLoad = config.DisableShrinkOnLoad
 	c.UseLinearColorspace = config.UseLinearColorspace
 	c.PreferredFormats = config.PreferredFormats
+	c.SanitizeSvg = config.SanitizeSvg
+	c.AlwaysRasterizeSvg = config.AlwaysRasterizeSvg
 
 	return c, nil
 }

+ 36 - 31
processing/processing.go

@@ -8,7 +8,6 @@ import (
 	"slices"
 
 	"github.com/imgproxy/imgproxy/v3/auximageprovider"
-	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/imagedata"
 	"github.com/imgproxy/imgproxy/v3/imagetype"
 	"github.com/imgproxy/imgproxy/v3/options"
@@ -78,14 +77,14 @@ func (p *Processor) ProcessImage(
 
 	// Load a single page/frame of the image so we can analyze it
 	// and decide how to process it further
-	thumbnailLoaded, err := initialLoadImage(img, imgdata, po.EnforceThumbnail)
+	thumbnailLoaded, err := p.initialLoadImage(img, imgdata, po.EnforceThumbnail)
 	if err != nil {
 		return nil, err
 	}
 
 	// Let's check if we should skip standard processing
-	if shouldSkipStandardProcessing(imgdata.Format(), po) {
-		return skipStandardProcessing(img, imgdata, po)
+	if p.shouldSkipStandardProcessing(imgdata.Format(), po) {
+		return p.skipStandardProcessing(img, imgdata, po)
 	}
 
 	// Check if we expect image to be processed as animated.
@@ -96,7 +95,7 @@ func (p *Processor) ProcessImage(
 
 	// Determine output format and check if it's supported.
 	// The determined format is stored in po.Format.
-	if err = determineOutputFormat(img, imgdata, po, animated); err != nil {
+	if err = p.determineOutputFormat(img, imgdata, po, animated); err != nil {
 		return nil, err
 	}
 
@@ -108,13 +107,13 @@ func (p *Processor) ProcessImage(
 	// and remove animation-related data if not animated.
 	// Don't reload if we initially loaded a thumbnail.
 	if !thumbnailLoaded {
-		if err = reloadImageForProcessing(img, imgdata, po, animated); err != nil {
+		if err = p.reloadImageForProcessing(img, imgdata, po, animated); err != nil {
 			return nil, err
 		}
 	}
 
 	// Check image dimensions and number of frames for security reasons
-	originWidth, originHeight, err := checkImageSize(img, imgdata.Format(), po.SecurityOptions)
+	originWidth, originHeight, err := p.checkImageSize(img, imgdata.Format(), po.SecurityOptions)
 	if err != nil {
 		return nil, err
 	}
@@ -129,12 +128,12 @@ func (p *Processor) ProcessImage(
 		return nil, err
 	}
 
-	outData, err := saveImage(ctx, img, po)
+	outData, err := p.saveImage(ctx, img, po)
 	if err != nil {
 		return nil, err
 	}
 
-	resultWidth, resultHeight, _ := getImageSize(img)
+	resultWidth, resultHeight, _ := p.getImageSize(img)
 
 	return &Result{
 		OutData:      outData,
@@ -148,7 +147,7 @@ func (p *Processor) ProcessImage(
 // initialLoadImage loads a single page/frame of the image.
 // If the image format supports thumbnails and thumbnail loading is enforced,
 // it tries to load the thumbnail first.
-func initialLoadImage(
+func (p *Processor) initialLoadImage(
 	img *vips.Image,
 	imgdata imagedata.ImageData,
 	enforceThumbnail bool,
@@ -166,7 +165,7 @@ func initialLoadImage(
 
 // reloadImageForProcessing reloads the image for processing.
 // For animated images, it loads all frames up to MaxAnimationFrames.
-func reloadImageForProcessing(
+func (p *Processor) reloadImageForProcessing(
 	img *vips.Image,
 	imgdata imagedata.ImageData,
 	po *options.ProcessingOptions,
@@ -185,12 +184,12 @@ func reloadImageForProcessing(
 
 // checkImageSize checks the image dimensions and number of frames against security options.
 // It returns the image width, height and a security check error, if any.
-func checkImageSize(
+func (p *Processor) checkImageSize(
 	img *vips.Image,
 	imgtype imagetype.Type,
 	secops security.Options,
 ) (int, int, error) {
-	width, height, frames := getImageSize(img)
+	width, height, frames := p.getImageSize(img)
 
 	if imgtype.IsVector() {
 		// We don't check vector image dimensions as we can render it in any size
@@ -204,7 +203,7 @@ func checkImageSize(
 
 // getImageSize returns the width and height of the image, taking into account
 // orientation and animation.
-func getImageSize(img *vips.Image) (int, int, int) {
+func (p *Processor) getImageSize(img *vips.Image) (int, int, int) {
 	width, height := img.Width(), img.Height()
 	frames := 1
 
@@ -225,7 +224,10 @@ func getImageSize(img *vips.Image) (int, int, int) {
 }
 
 // Returns true if image should not be processed as usual
-func shouldSkipStandardProcessing(inFormat imagetype.Type, po *options.ProcessingOptions) bool {
+func (p *Processor) shouldSkipStandardProcessing(
+	inFormat imagetype.Type,
+	po *options.ProcessingOptions,
+) bool {
 	outFormat := po.Format
 	skipProcessingFormatEnabled := slices.Contains(po.SkipProcessingFormats, inFormat)
 
@@ -235,9 +237,9 @@ func shouldSkipStandardProcessing(inFormat imagetype.Type, po *options.Processin
 		switch {
 		case outFormat == imagetype.SVG:
 			return true
-		case isOutUnknown && !config.AlwaysRasterizeSvg:
+		case isOutUnknown && !p.config.AlwaysRasterizeSvg:
 			return true
-		case isOutUnknown && config.AlwaysRasterizeSvg && skipProcessingFormatEnabled:
+		case isOutUnknown && p.config.AlwaysRasterizeSvg && skipProcessingFormatEnabled:
 			return true
 		default:
 			return false
@@ -250,20 +252,20 @@ func shouldSkipStandardProcessing(inFormat imagetype.Type, po *options.Processin
 // skipStandardProcessing skips standard image processing and returns the original image data.
 //
 // SVG images may be sanitized if configured to do so.
-func skipStandardProcessing(
+func (p *Processor) skipStandardProcessing(
 	img *vips.Image,
 	imgdata imagedata.ImageData,
 	po *options.ProcessingOptions,
 ) (*Result, error) {
 	// Even if we skip standard processing, we still need to check image dimensions
 	// to not send an image bomb to the client
-	originWidth, originHeight, err := checkImageSize(img, imgdata.Format(), po.SecurityOptions)
+	originWidth, originHeight, err := p.checkImageSize(img, imgdata.Format(), po.SecurityOptions)
 	if err != nil {
 		return nil, err
 	}
 
 	// Even in this case, SVG is an exception
-	if imgdata.Format() == imagetype.SVG && config.SanitizeSvg {
+	if imgdata.Format() == imagetype.SVG && p.config.SanitizeSvg {
 		sanitized, err := svg.Sanitize(imgdata)
 		if err != nil {
 			return nil, err
@@ -292,7 +294,7 @@ func skipStandardProcessing(
 // and image properties.
 //
 // It modifies the ProcessingOptions in place to set the output format.
-func determineOutputFormat(
+func (p *Processor) determineOutputFormat(
 	img *vips.Image,
 	imgdata imagedata.ImageData,
 	po *options.ProcessingOptions,
@@ -314,10 +316,10 @@ func determineOutputFormat(
 			po.Format = imagetype.AVIF
 		case po.PreferWebP:
 			po.Format = imagetype.WEBP
-		case isImageTypePreferred(imgdata.Format()):
+		case p.isImageTypePreferred(imgdata.Format()):
 			po.Format = imgdata.Format()
 		default:
-			po.Format = findPreferredFormat(animated, expectTransparency)
+			po.Format = p.findPreferredFormat(animated, expectTransparency)
 		}
 	case po.EnforceJxl && !animated:
 		po.Format = imagetype.JXL
@@ -335,12 +337,15 @@ func determineOutputFormat(
 }
 
 // isImageTypePreferred checks if the given image type is in the list of preferred formats.
-func isImageTypePreferred(imgtype imagetype.Type) bool {
-	return slices.Contains(config.PreferredFormats, imgtype)
+func (p *Processor) isImageTypePreferred(imgtype imagetype.Type) bool {
+	return slices.Contains(p.config.PreferredFormats, imgtype)
 }
 
 // isImageTypeCompatible checks if the given image type is compatible with the image properties.
-func isImageTypeCompatible(imgtype imagetype.Type, animated, expectTransparency bool) bool {
+func (p *Processor) isImageTypeCompatible(
+	imgtype imagetype.Type,
+	animated, expectTransparency bool,
+) bool {
 	if animated && !imgtype.SupportsAnimationSave() {
 		return false
 	}
@@ -353,14 +358,14 @@ func isImageTypeCompatible(imgtype imagetype.Type, animated, expectTransparency
 }
 
 // findPreferredFormat finds a suitable preferred format based on image's properties.
-func findPreferredFormat(animated, expectTransparency bool) imagetype.Type {
-	for _, t := range config.PreferredFormats {
-		if isImageTypeCompatible(t, animated, expectTransparency) {
+func (p *Processor) findPreferredFormat(animated, expectTransparency bool) imagetype.Type {
+	for _, t := range p.config.PreferredFormats {
+		if p.isImageTypeCompatible(t, animated, expectTransparency) {
 			return t
 		}
 	}
 
-	return config.PreferredFormats[0]
+	return p.config.PreferredFormats[0]
 }
 
 func (p *Processor) transformImage(
@@ -494,7 +499,7 @@ func (p *Processor) transformAnimated(
 	return nil
 }
 
-func saveImage(
+func (p *Processor) saveImage(
 	ctx context.Context,
 	img *vips.Image,
 	po *options.ProcessingOptions,

+ 1 - 2
processing/watermark.go

@@ -4,7 +4,6 @@ import (
 	"context"
 	"math"
 
-	"github.com/imgproxy/imgproxy/v3/config"
 	"github.com/imgproxy/imgproxy/v3/imagedata"
 	"github.com/imgproxy/imgproxy/v3/imath"
 	"github.com/imgproxy/imgproxy/v3/options"
@@ -136,7 +135,7 @@ func (p *Processor) applyWatermark(
 	}
 
 	// TODO: Use runner config
-	opacity := opts.Opacity * config.WatermarkOpacity
+	opacity := opts.Opacity * p.config.WatermarkOpacity
 
 	// If we replicated the watermark and need to apply it to an animated image,
 	// it is faster to replicate the watermark to all the image and apply it single-pass