mirror of
				https://github.com/imgproxy/imgproxy.git
				synced 2025-10-30 23:08:02 +02:00 
			
		
		
		
	SVG instance
This commit is contained in:
		| @@ -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)) | ||||
|   | ||||
| @@ -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), | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -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
									
								
								processing/svg/config.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								processing/svg/config.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
| @@ -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)) | ||||
| 
 | ||||
| @@ -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) | ||||
		Reference in New Issue
	
	Block a user