svg.go 2.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. package svg
  2. import (
  3. "bytes"
  4. "context"
  5. "io"
  6. "strings"
  7. "github.com/tdewolff/parse/v2"
  8. "github.com/tdewolff/parse/v2/xml"
  9. "github.com/imgproxy/imgproxy/v3/bufpool"
  10. "github.com/imgproxy/imgproxy/v3/config"
  11. "github.com/imgproxy/imgproxy/v3/imagedata"
  12. )
  13. var svgBufPool = bufpool.New("svg", config.Workers, config.DownloadBufferSize)
  14. func BorrowBuffer() (*bytes.Buffer, context.CancelFunc) {
  15. buf := svgBufPool.Get(0, false)
  16. cancel := func() { svgBufPool.Put(buf) }
  17. return buf, cancel
  18. }
  19. func cloneHeaders(src map[string]string) map[string]string {
  20. if src == nil {
  21. return nil
  22. }
  23. dst := make(map[string]string, len(src))
  24. for k, v := range src {
  25. dst[k] = v
  26. }
  27. return dst
  28. }
  29. func Sanitize(data *imagedata.ImageData) (*imagedata.ImageData, error) {
  30. r := bytes.NewReader(data.Data)
  31. l := xml.NewLexer(parse.NewInput(r))
  32. buf, cancel := BorrowBuffer()
  33. ignoreTag := 0
  34. var curTagName string
  35. for {
  36. tt, tdata := l.Next()
  37. if ignoreTag > 0 {
  38. switch tt {
  39. case xml.ErrorToken:
  40. cancel()
  41. return nil, l.Err()
  42. case xml.EndTagToken, xml.StartTagCloseVoidToken:
  43. ignoreTag--
  44. case xml.StartTagToken:
  45. ignoreTag++
  46. }
  47. continue
  48. }
  49. switch tt {
  50. case xml.ErrorToken:
  51. if l.Err() != io.EOF {
  52. cancel()
  53. return nil, l.Err()
  54. }
  55. newData := imagedata.ImageData{
  56. Data: buf.Bytes(),
  57. Type: data.Type,
  58. Headers: cloneHeaders(data.Headers),
  59. }
  60. newData.SetCancel(cancel)
  61. return &newData, nil
  62. case xml.StartTagToken:
  63. curTagName = strings.ToLower(string(l.Text()))
  64. if curTagName == "script" {
  65. ignoreTag++
  66. continue
  67. }
  68. buf.Write(tdata)
  69. case xml.AttributeToken:
  70. attrName := strings.ToLower(string(l.Text()))
  71. if _, unsafe := unsafeAttrs[attrName]; unsafe {
  72. continue
  73. }
  74. if curTagName == "use" && (attrName == "href" || attrName == "xlink:href") {
  75. val := strings.TrimSpace(strings.Trim(string(l.AttrVal()), `"'`))
  76. if len(val) > 0 && val[0] != '#' {
  77. continue
  78. }
  79. }
  80. buf.Write(tdata)
  81. default:
  82. buf.Write(tdata)
  83. }
  84. }
  85. }