svg.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. package svg
  2. import (
  3. "bytes"
  4. "errors"
  5. "io"
  6. "strings"
  7. "sync"
  8. "github.com/imgproxy/imgproxy/v3/imagedata"
  9. "github.com/imgproxy/imgproxy/v3/imagetype"
  10. "github.com/imgproxy/imgproxy/v3/options"
  11. "github.com/tdewolff/parse/v2"
  12. "github.com/tdewolff/parse/v2/xml"
  13. )
  14. // pool represents temorary pool for svg sanitized data
  15. var pool = sync.Pool{
  16. New: func() any {
  17. return bytes.NewBuffer(nil)
  18. },
  19. }
  20. // Processor provides SVG processing capabilities
  21. type Processor struct {
  22. config *Config
  23. }
  24. // New creates a new SVG processor instance
  25. func New(config *Config) *Processor {
  26. return &Processor{
  27. config: config,
  28. }
  29. }
  30. // Process processes the given image data
  31. func (p *Processor) Process(o *options.Options, data imagedata.ImageData) (imagedata.ImageData, error) {
  32. if data.Format() != imagetype.SVG {
  33. return data, nil
  34. }
  35. var err error
  36. data, err = p.sanitize(data)
  37. if err != nil {
  38. return data, err
  39. }
  40. return data, nil
  41. }
  42. // sanitize sanitizes the SVG data.
  43. // It strips <script> and unsafe attributes (on* events).
  44. func (p *Processor) sanitize(data imagedata.ImageData) (imagedata.ImageData, error) {
  45. if !p.config.Sanitize {
  46. return data, nil
  47. }
  48. r := data.Reader()
  49. l := xml.NewLexer(parse.NewInput(r))
  50. buf, ok := pool.Get().(*bytes.Buffer)
  51. if !ok {
  52. return nil, newSanitizeError(errors.New("svg.Sanitize: failed to get buffer from pool"))
  53. }
  54. buf.Reset()
  55. cancel := func() {
  56. pool.Put(buf)
  57. }
  58. ignoreTag := 0
  59. var curTagName string
  60. for {
  61. tt, tdata := l.Next()
  62. if tt == xml.ErrorToken {
  63. if l.Err() != io.EOF {
  64. cancel()
  65. return nil, newSanitizeError(l.Err())
  66. }
  67. break
  68. }
  69. if ignoreTag > 0 {
  70. switch tt {
  71. case xml.EndTagToken, xml.StartTagCloseVoidToken:
  72. ignoreTag--
  73. case xml.StartTagToken:
  74. ignoreTag++
  75. }
  76. continue
  77. }
  78. switch tt {
  79. case xml.StartTagToken:
  80. curTagName = strings.ToLower(string(l.Text()))
  81. if curTagName == "script" {
  82. ignoreTag++
  83. continue
  84. }
  85. buf.Write(tdata)
  86. case xml.AttributeToken:
  87. attrName := strings.ToLower(string(l.Text()))
  88. if _, unsafe := unsafeAttrs[attrName]; unsafe {
  89. continue
  90. }
  91. if curTagName == "use" && (attrName == "href" || attrName == "xlink:href") {
  92. val := strings.TrimSpace(strings.Trim(string(l.AttrVal()), `"'`))
  93. if len(val) > 0 && val[0] != '#' {
  94. continue
  95. }
  96. }
  97. buf.Write(tdata)
  98. default:
  99. buf.Write(tdata)
  100. }
  101. }
  102. newData := imagedata.NewFromBytesWithFormat(
  103. imagetype.SVG,
  104. buf.Bytes(),
  105. )
  106. newData.AddCancel(cancel)
  107. return newData, nil
  108. }