A webmail client. Forked from https://git.sr.ht/~migadu/alps
Não pode escolher mais do que 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 

194 linhas
4.1 KiB

  1. package koushinbase
  2. import (
  3. "bytes"
  4. "fmt"
  5. "regexp"
  6. "strings"
  7. "github.com/aymerick/douceur/css"
  8. cssparser "github.com/chris-ramon/douceur/parser"
  9. "github.com/microcosm-cc/bluemonday"
  10. "golang.org/x/net/html"
  11. )
  12. // TODO: this doesn't accomodate for quoting
  13. var (
  14. cssURLRegexp = regexp.MustCompile(`url\([^)]*\)`)
  15. cssExprRegexp = regexp.MustCompile(`expression\([^)]*\)`)
  16. )
  17. var allowedStyles = map[string]bool{
  18. "direction": true,
  19. "font": true,
  20. "font-family": true,
  21. "font-style": true,
  22. "font-variant": true,
  23. "font-size": true,
  24. "font-weight": true,
  25. "letter-spacing": true,
  26. "line-height": true,
  27. "text-align": true,
  28. "text-decoration": true,
  29. "text-indent": true,
  30. "text-overflow": true,
  31. "text-shadow": true,
  32. "text-transform": true,
  33. "white-space": true,
  34. "word-spacing": true,
  35. "word-wrap": true,
  36. "vertical-align": true,
  37. "color": true,
  38. "background": true,
  39. "background-color": true,
  40. "background-image": true,
  41. "background-repeat": true,
  42. "border": true,
  43. "border-color": true,
  44. "border-radius": true,
  45. "height": true,
  46. "margin": true,
  47. "padding": true,
  48. "width": true,
  49. "max-width": true,
  50. "min-width": true,
  51. "clear": true,
  52. "float": true,
  53. "border-collapse": true,
  54. "border-spacing": true,
  55. "caption-side": true,
  56. "empty-cells": true,
  57. "table-layout": true,
  58. "list-style-type": true,
  59. "list-style-position": true,
  60. }
  61. func sanitizeCSSDecls(decls []*css.Declaration) []*css.Declaration {
  62. sanitized := make([]*css.Declaration, 0, len(decls))
  63. for _, decl := range decls {
  64. if !allowedStyles[decl.Property] {
  65. continue
  66. }
  67. if cssExprRegexp.FindStringIndex(decl.Value) != nil {
  68. continue
  69. }
  70. // TODO: more robust CSS declaration parsing
  71. decl.Value = cssURLRegexp.ReplaceAllString(decl.Value, "url(about:blank)")
  72. sanitized = append(sanitized, decl)
  73. }
  74. return sanitized
  75. }
  76. func sanitizeCSSRule(rule *css.Rule) {
  77. // Disallow @import
  78. if rule.Kind == css.AtRule && strings.EqualFold(rule.Name, "@import") {
  79. rule.Prelude = "url(about:blank)"
  80. }
  81. rule.Declarations = sanitizeCSSDecls(rule.Declarations)
  82. for _, child := range rule.Rules {
  83. sanitizeCSSRule(child)
  84. }
  85. }
  86. func sanitizeNode(n *html.Node) {
  87. if n.Type == html.ElementNode {
  88. if strings.EqualFold(n.Data, "img") {
  89. for i := range n.Attr {
  90. attr := &n.Attr[i]
  91. if strings.EqualFold(attr.Key, "src") {
  92. attr.Val = "about:blank"
  93. }
  94. }
  95. } else if strings.EqualFold(n.Data, "style") {
  96. var s string
  97. c := n.FirstChild
  98. for c != nil {
  99. if c.Type == html.TextNode {
  100. s += c.Data
  101. }
  102. next := c.NextSibling
  103. n.RemoveChild(c)
  104. c = next
  105. }
  106. stylesheet, err := cssparser.Parse(s)
  107. if err != nil {
  108. s = ""
  109. } else {
  110. for _, rule := range stylesheet.Rules {
  111. sanitizeCSSRule(rule)
  112. }
  113. s = stylesheet.String()
  114. }
  115. n.AppendChild(&html.Node{
  116. Type: html.TextNode,
  117. Data: s,
  118. })
  119. }
  120. for i := range n.Attr {
  121. // Don't use `i, attr := range n.Attr` since `attr` would be a copy
  122. attr := &n.Attr[i]
  123. if strings.EqualFold(attr.Key, "style") {
  124. decls, err := cssparser.ParseDeclarations(attr.Val)
  125. if err != nil {
  126. attr.Val = ""
  127. continue
  128. }
  129. decls = sanitizeCSSDecls(decls)
  130. attr.Val = ""
  131. for _, d := range decls {
  132. attr.Val += d.String()
  133. }
  134. }
  135. }
  136. }
  137. for c := n.FirstChild; c != nil; c = c.NextSibling {
  138. sanitizeNode(c)
  139. }
  140. }
  141. func sanitizeHTML(b []byte) ([]byte, error) {
  142. doc, err := html.Parse(bytes.NewReader(b))
  143. if err != nil {
  144. return nil, fmt.Errorf("failed to parse HTML: %v", err)
  145. }
  146. sanitizeNode(doc)
  147. var buf bytes.Buffer
  148. if err := html.Render(&buf, doc); err != nil {
  149. return nil, fmt.Errorf("failed to render HTML: %v", err)
  150. }
  151. b = buf.Bytes()
  152. // bluemonday must always be run last
  153. p := bluemonday.UGCPolicy()
  154. // TODO: use bluemonday's AllowStyles once it's released and
  155. // supports <style>
  156. p.AllowElements("style")
  157. p.AllowAttrs("style").Globally()
  158. p.AddTargetBlankToFullyQualifiedLinks(true)
  159. p.RequireNoFollowOnLinks(true)
  160. return p.SanitizeBytes(b), nil
  161. }