1
0
mirror of https://github.com/imgproxy/imgproxy.git synced 2025-10-30 23:08:02 +02:00

SVG instance

This commit is contained in:
Viktor Sokolov
2025-10-03 10:32:40 +02:00
parent bec004c652
commit 1c1e728fa7
9 changed files with 101 additions and 27 deletions

View File

@@ -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))

View File

@@ -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),

View File

@@ -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

View File

@@ -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
View 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
}

View File

@@ -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))

View File

@@ -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)