Victor Sokolov 2 dní pred
rodič
commit
ce2bc83ce7

+ 6 - 2
integration/processing_handler_test.go

@@ -13,7 +13,8 @@ import (
 	"github.com/imgproxy/imgproxy/v3/httpheaders"
 	"github.com/imgproxy/imgproxy/v3/imagedata"
 	"github.com/imgproxy/imgproxy/v3/imagetype"
-	"github.com/imgproxy/imgproxy/v3/svg"
+	"github.com/imgproxy/imgproxy/v3/options"
+	"github.com/imgproxy/imgproxy/v3/processing/svg"
 	"github.com/imgproxy/imgproxy/v3/testutil"
 	"github.com/imgproxy/imgproxy/v3/vips"
 	"github.com/stretchr/testify/suite"
@@ -213,7 +214,10 @@ func (s *ProcessingHandlerTestSuite) TestSkipProcessingSVG() {
 	data, err := idf.NewFromBytes(s.TestData.Read("test1.svg"))
 	s.Require().NoError(err)
 
-	expected, err := svg.Sanitize(data)
+	cfg := svg.NewDefaultConfig()
+	svg := svg.New(&cfg)
+
+	expected, err := svg.Process(&options.Options{}, data)
 	s.Require().NoError(err)
 
 	s.Require().True(testutil.ReadersEqual(s.T(), expected.Reader(), res.Body))

+ 9 - 5
processing/config.go

@@ -6,6 +6,7 @@ import (
 	"github.com/imgproxy/imgproxy/v3/ensure"
 	"github.com/imgproxy/imgproxy/v3/env"
 	"github.com/imgproxy/imgproxy/v3/imagetype"
+	"github.com/imgproxy/imgproxy/v3/processing/svg"
 	"github.com/imgproxy/imgproxy/v3/vips"
 )
 
@@ -15,7 +16,6 @@ var (
 	IMGPROXY_WATERMARK_OPACITY       = env.Describe("IMGPROXY_WATERMARK_OPACITY", "number between 0..1")
 	IMGPROXY_DISABLE_SHRINK_ON_LOAD  = env.Describe("IMGPROXY_DISABLE_SHRINK_ON_LOAD", "boolean")
 	IMGPROXY_USE_LINEAR_COLORSPACE   = env.Describe("IMGPROXY_USE_LINEAR_COLORSPACE", "boolean")
-	IMGPROXY_SANITIZE_SVG            = env.Describe("IMGPROXY_SANITIZE_SVG", "boolean")
 	IMGPROXY_ALWAYS_RASTERIZE_SVG    = env.Describe("IMGPROXY_ALWAYS_RASTERIZE_SVG", "boolean")
 	IMGPROXY_QUALITY                 = env.Describe("IMGPROXY_QUALITY", "number between 0..100")
 	IMGPROXY_FORMAT_QUALITY          = env.Describe("IMGPROXY_FORMAT_QUALITY", "comma-separated list of format=quality pairs where quality is between 0..100")
@@ -33,7 +33,6 @@ type Config struct {
 	WatermarkOpacity      float64
 	DisableShrinkOnLoad   bool
 	UseLinearColorspace   bool
-	SanitizeSvg           bool
 	AlwaysRasterizeSvg    bool
 	Quality               int
 	FormatQuality         map[imagetype.Type]int
@@ -42,6 +41,8 @@ type Config struct {
 	StripColorProfile     bool
 	AutoRotate            bool
 	EnforceThumbnail      bool
+
+	Svg svg.Config
 }
 
 // NewConfig creates a new Config instance with the given parameters.
@@ -53,8 +54,7 @@ func NewDefaultConfig() Config {
 			imagetype.PNG,
 			imagetype.GIF,
 		},
-		SanitizeSvg: true,
-		Quality:     80,
+		Quality: 80,
 		FormatQuality: map[imagetype.Type]int{
 			imagetype.WEBP: 79,
 			imagetype.AVIF: 63,
@@ -65,6 +65,8 @@ func NewDefaultConfig() Config {
 		StripColorProfile: true,
 		AutoRotate:        true,
 		EnforceThumbnail:  false,
+
+		Svg: svg.NewDefaultConfig(),
 	}
 }
 
@@ -72,11 +74,13 @@ func NewDefaultConfig() Config {
 func LoadConfigFromEnv(c *Config) (*Config, error) {
 	c = ensure.Ensure(c, NewDefaultConfig)
 
+	_, svgErr := svg.LoadConfigFromEnv(&c.Svg)
+
 	err := errors.Join(
+		svgErr,
 		env.Float(&c.WatermarkOpacity, IMGPROXY_WATERMARK_OPACITY),
 		env.Bool(&c.DisableShrinkOnLoad, IMGPROXY_DISABLE_SHRINK_ON_LOAD),
 		env.Bool(&c.UseLinearColorspace, IMGPROXY_USE_LINEAR_COLORSPACE),
-		env.Bool(&c.SanitizeSvg, IMGPROXY_SANITIZE_SVG),
 		env.Bool(&c.AlwaysRasterizeSvg, IMGPROXY_ALWAYS_RASTERIZE_SVG),
 		env.Int(&c.Quality, IMGPROXY_QUALITY),
 		env.ImageTypesQuality(c.FormatQuality, IMGPROXY_FORMAT_QUALITY),

+ 3 - 15
processing/processing.go

@@ -12,7 +12,6 @@ import (
 	"github.com/imgproxy/imgproxy/v3/options"
 	"github.com/imgproxy/imgproxy/v3/security"
 	"github.com/imgproxy/imgproxy/v3/server"
-	"github.com/imgproxy/imgproxy/v3/svg"
 	"github.com/imgproxy/imgproxy/v3/vips"
 )
 
@@ -268,20 +267,9 @@ func (p *Processor) skipStandardProcessing(
 		return nil, err
 	}
 
-	// Even in this case, SVG is an exception
-	if imgdata.Format() == imagetype.SVG && p.config.SanitizeSvg {
-		sanitized, err := svg.Sanitize(imgdata)
-		if err != nil {
-			return nil, err
-		}
-
-		return &Result{
-			OutData:      sanitized,
-			OriginWidth:  originWidth,
-			OriginHeight: originHeight,
-			ResultWidth:  originWidth,
-			ResultHeight: originHeight,
-		}, nil
+	imgdata, err = p.svg.Process(po.Options, imgdata)
+	if err != nil {
+		return nil, err
 	}
 
 	// Return the original image

+ 3 - 0
processing/processor.go

@@ -2,12 +2,14 @@ package processing
 
 import (
 	"github.com/imgproxy/imgproxy/v3/auximageprovider"
+	"github.com/imgproxy/imgproxy/v3/processing/svg"
 )
 
 // Processor is responsible for processing images according to the given configuration.
 type Processor struct {
 	config            *Config
 	watermarkProvider auximageprovider.Provider
+	svg               *svg.Processor
 }
 
 // New creates a new Processor instance with the given configuration and watermark provider
@@ -19,5 +21,6 @@ func New(config *Config, watermark auximageprovider.Provider) (*Processor, error
 	return &Processor{
 		config:            config,
 		watermarkProvider: watermark,
+		svg:               svg.New(&config.Svg),
 	}, nil
 }

+ 36 - 0
processing/svg/config.go

@@ -0,0 +1,36 @@
+package svg
+
+import (
+	"github.com/imgproxy/imgproxy/v3/ensure"
+	"github.com/imgproxy/imgproxy/v3/env"
+)
+
+var (
+	IMGPROXY_SANITIZE_SVG = env.Describe("IMGPROXY_SANITIZE_SVG", "boolean")
+)
+
+// Config holds SVG-specific configuration
+type Config struct {
+	Sanitize bool // Sanitize SVG content for security
+}
+
+// NewDefaultConfig creates a new Config instance with default values
+func NewDefaultConfig() Config {
+	return Config{
+		Sanitize: true, // By default, sanitize SVG for security
+	}
+}
+
+// LoadConfigFromEnv loads configuration from environment variables
+func LoadConfigFromEnv(c *Config) (*Config, error) {
+	c = ensure.Ensure(c, NewDefaultConfig)
+
+	err := env.Bool(&c.Sanitize, IMGPROXY_SANITIZE_SVG)
+
+	return c, err
+}
+
+// Validate checks if the configuration is valid
+func (c *Config) Validate() error {
+	return nil
+}

+ 0 - 0
svg/errors.go → processing/svg/errors.go


+ 39 - 4
svg/svg.go → processing/svg/svg.go

@@ -7,20 +7,55 @@ import (
 	"strings"
 	"sync"
 
-	"github.com/tdewolff/parse/v2"
-	"github.com/tdewolff/parse/v2/xml"
-
 	"github.com/imgproxy/imgproxy/v3/imagedata"
 	"github.com/imgproxy/imgproxy/v3/imagetype"
+	"github.com/imgproxy/imgproxy/v3/options"
+	"github.com/tdewolff/parse/v2"
+	"github.com/tdewolff/parse/v2/xml"
 )
 
+// pool represents temorary pool for svg sanitized data
 var pool = sync.Pool{
 	New: func() any {
 		return bytes.NewBuffer(nil)
 	},
 }
 
-func Sanitize(data imagedata.ImageData) (imagedata.ImageData, error) {
+// Processor provides SVG processing capabilities
+type Processor struct {
+	config *Config
+}
+
+// New creates a new SVG processor instance
+func New(config *Config) *Processor {
+	return &Processor{
+		config: config,
+	}
+}
+
+// Process processes the given image data
+func (p *Processor) Process(o *options.Options, data imagedata.ImageData) (imagedata.ImageData, error) {
+	if data.Format() != imagetype.SVG {
+		return data, nil
+	}
+
+	var err error
+
+	data, err = p.sanitize(data)
+	if err != nil {
+		return data, err
+	}
+
+	return data, nil
+}
+
+// sanitize sanitizes the SVG data.
+// It strips <script> and unsafe attributes (on* events).
+func (p *Processor) sanitize(data imagedata.ImageData) (imagedata.ImageData, error) {
+	if !p.config.Sanitize {
+		return data, nil
+	}
+
 	r := data.Reader()
 	l := xml.NewLexer(parse.NewInput(r))
 

+ 5 - 1
svg/svg_test.go → processing/svg/svg_test.go

@@ -9,6 +9,7 @@ import (
 
 	"github.com/imgproxy/imgproxy/v3/imagedata"
 	"github.com/imgproxy/imgproxy/v3/imagetype"
+	"github.com/imgproxy/imgproxy/v3/options"
 	"github.com/imgproxy/imgproxy/v3/testutil"
 )
 
@@ -45,7 +46,10 @@ func (s *SvgTestSuite) TestSanitize() {
 	origin := s.readTestFile("test1.svg")
 	expected := s.readTestFile("test1.sanitized.svg")
 
-	actual, err := Sanitize(origin)
+	config := NewDefaultConfig()
+	svg := New(&config)
+
+	actual, err := svg.Process(options.New(), origin)
 	s.Require().NoError(err)
 
 	s.compare(expected, actual)

+ 0 - 0
svg/unsafe_attrs.go → processing/svg/unsafe_attrs.go