css.go 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. // Copyright 2011 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package template
  5. import (
  6. "bytes"
  7. "fmt"
  8. "unicode"
  9. "unicode/utf8"
  10. )
  11. // endsWithCSSKeyword reports whether b ends with an ident that
  12. // case-insensitively matches the lower-case kw.
  13. func endsWithCSSKeyword(b []byte, kw string) bool {
  14. i := len(b) - len(kw)
  15. if i < 0 {
  16. // Too short.
  17. return false
  18. }
  19. if i != 0 {
  20. r, _ := utf8.DecodeLastRune(b[:i])
  21. if isCSSNmchar(r) {
  22. // Too long.
  23. return false
  24. }
  25. }
  26. // Many CSS keywords, such as "!important" can have characters encoded,
  27. // but the URI production does not allow that according to
  28. // http://www.w3.org/TR/css3-syntax/#TOK-URI
  29. // This does not attempt to recognize encoded keywords. For example,
  30. // given "\75\72\6c" and "url" this return false.
  31. return string(bytes.ToLower(b[i:])) == kw
  32. }
  33. // isCSSNmchar reports whether rune is allowed anywhere in a CSS identifier.
  34. func isCSSNmchar(r rune) bool {
  35. // Based on the CSS3 nmchar production but ignores multi-rune escape
  36. // sequences.
  37. // http://www.w3.org/TR/css3-syntax/#SUBTOK-nmchar
  38. return 'a' <= r && r <= 'z' ||
  39. 'A' <= r && r <= 'Z' ||
  40. '0' <= r && r <= '9' ||
  41. r == '-' ||
  42. r == '_' ||
  43. // Non-ASCII cases below.
  44. 0x80 <= r && r <= 0xd7ff ||
  45. 0xe000 <= r && r <= 0xfffd ||
  46. 0x10000 <= r && r <= 0x10ffff
  47. }
  48. // decodeCSS decodes CSS3 escapes given a sequence of stringchars.
  49. // If there is no change, it returns the input, otherwise it returns a slice
  50. // backed by a new array.
  51. // http://www.w3.org/TR/css3-syntax/#SUBTOK-stringchar defines stringchar.
  52. func decodeCSS(s []byte) []byte {
  53. i := bytes.IndexByte(s, '\\')
  54. if i == -1 {
  55. return s
  56. }
  57. // The UTF-8 sequence for a codepoint is never longer than 1 + the
  58. // number hex digits need to represent that codepoint, so len(s) is an
  59. // upper bound on the output length.
  60. b := make([]byte, 0, len(s))
  61. for len(s) != 0 {
  62. i := bytes.IndexByte(s, '\\')
  63. if i == -1 {
  64. i = len(s)
  65. }
  66. b, s = append(b, s[:i]...), s[i:]
  67. if len(s) < 2 {
  68. break
  69. }
  70. // http://www.w3.org/TR/css3-syntax/#SUBTOK-escape
  71. // escape ::= unicode | '\' [#x20-#x7E#x80-#xD7FF#xE000-#xFFFD#x10000-#x10FFFF]
  72. if isHex(s[1]) {
  73. // http://www.w3.org/TR/css3-syntax/#SUBTOK-unicode
  74. // unicode ::= '\' [0-9a-fA-F]{1,6} wc?
  75. j := 2
  76. for j < len(s) && j < 7 && isHex(s[j]) {
  77. j++
  78. }
  79. r := hexDecode(s[1:j])
  80. if r > unicode.MaxRune {
  81. r, j = r/16, j-1
  82. }
  83. n := utf8.EncodeRune(b[len(b):cap(b)], r)
  84. // The optional space at the end allows a hex
  85. // sequence to be followed by a literal hex.
  86. // string(decodeCSS([]byte(`\A B`))) == "\nB"
  87. b, s = b[:len(b)+n], skipCSSSpace(s[j:])
  88. } else {
  89. // `\\` decodes to `\` and `\"` to `"`.
  90. _, n := utf8.DecodeRune(s[1:])
  91. b, s = append(b, s[1:1+n]...), s[1+n:]
  92. }
  93. }
  94. return b
  95. }
  96. // isHex reports whether the given character is a hex digit.
  97. func isHex(c byte) bool {
  98. return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F'
  99. }
  100. // hexDecode decodes a short hex digit sequence: "10" -> 16.
  101. func hexDecode(s []byte) rune {
  102. n := '\x00'
  103. for _, c := range s {
  104. n <<= 4
  105. switch {
  106. case '0' <= c && c <= '9':
  107. n |= rune(c - '0')
  108. case 'a' <= c && c <= 'f':
  109. n |= rune(c-'a') + 10
  110. case 'A' <= c && c <= 'F':
  111. n |= rune(c-'A') + 10
  112. default:
  113. panic(fmt.Sprintf("Bad hex digit in %q", s))
  114. }
  115. }
  116. return n
  117. }
  118. // skipCSSSpace returns a suffix of c, skipping over a single space.
  119. func skipCSSSpace(c []byte) []byte {
  120. if len(c) == 0 {
  121. return c
  122. }
  123. // wc ::= #x9 | #xA | #xC | #xD | #x20
  124. switch c[0] {
  125. case '\t', '\n', '\f', ' ':
  126. return c[1:]
  127. case '\r':
  128. // This differs from CSS3's wc production because it contains a
  129. // probable spec error whereby wc contains all the single byte
  130. // sequences in nl (newline) but not CRLF.
  131. if len(c) >= 2 && c[1] == '\n' {
  132. return c[2:]
  133. }
  134. return c[1:]
  135. }
  136. return c
  137. }
  138. // isCSSSpace reports whether b is a CSS space char as defined in wc.
  139. func isCSSSpace(b byte) bool {
  140. switch b {
  141. case '\t', '\n', '\f', '\r', ' ':
  142. return true
  143. }
  144. return false
  145. }
  146. // cssEscaper escapes HTML and CSS special characters using \<hex>+ escapes.
  147. func cssEscaper(args ...interface{}) string {
  148. s, _ := stringify(args...)
  149. var b bytes.Buffer
  150. r, w, written := rune(0), 0, 0
  151. for i := 0; i < len(s); i += w {
  152. // See comment in htmlEscaper.
  153. r, w = utf8.DecodeRuneInString(s[i:])
  154. var repl string
  155. switch {
  156. case int(r) < len(cssReplacementTable) && cssReplacementTable[r] != "":
  157. repl = cssReplacementTable[r]
  158. default:
  159. continue
  160. }
  161. b.WriteString(s[written:i])
  162. b.WriteString(repl)
  163. written = i + w
  164. if repl != `\\` && (written == len(s) || isHex(s[written]) || isCSSSpace(s[written])) {
  165. b.WriteByte(' ')
  166. }
  167. }
  168. if written == 0 {
  169. return s
  170. }
  171. b.WriteString(s[written:])
  172. return b.String()
  173. }
  174. var cssReplacementTable = []string{
  175. 0: `\0`,
  176. '\t': `\9`,
  177. '\n': `\a`,
  178. '\f': `\c`,
  179. '\r': `\d`,
  180. // Encode HTML specials as hex so the output can be embedded
  181. // in HTML attributes without further encoding.
  182. '"': `\22`,
  183. '&': `\26`,
  184. '\'': `\27`,
  185. '(': `\28`,
  186. ')': `\29`,
  187. '+': `\2b`,
  188. '/': `\2f`,
  189. ':': `\3a`,
  190. ';': `\3b`,
  191. '<': `\3c`,
  192. '>': `\3e`,
  193. '\\': `\\`,
  194. '{': `\7b`,
  195. '}': `\7d`,
  196. }
  197. var expressionBytes = []byte("expression")
  198. var mozBindingBytes = []byte("mozbinding")
  199. // cssValueFilter allows innocuous CSS values in the output including CSS
  200. // quantities (10px or 25%), ID or class literals (#foo, .bar), keyword values
  201. // (inherit, blue), and colors (#888).
  202. // It filters out unsafe values, such as those that affect token boundaries,
  203. // and anything that might execute scripts.
  204. func cssValueFilter(args ...interface{}) string {
  205. s, t := stringify(args...)
  206. if t == contentTypeCSS {
  207. return s
  208. }
  209. b, id := decodeCSS([]byte(s)), make([]byte, 0, 64)
  210. // CSS3 error handling is specified as honoring string boundaries per
  211. // http://www.w3.org/TR/css3-syntax/#error-handling :
  212. // Malformed declarations. User agents must handle unexpected
  213. // tokens encountered while parsing a declaration by reading until
  214. // the end of the declaration, while observing the rules for
  215. // matching pairs of (), [], {}, "", and '', and correctly handling
  216. // escapes. For example, a malformed declaration may be missing a
  217. // property, colon (:) or value.
  218. // So we need to make sure that values do not have mismatched bracket
  219. // or quote characters to prevent the browser from restarting parsing
  220. // inside a string that might embed JavaScript source.
  221. for i, c := range b {
  222. switch c {
  223. case 0, '"', '\'', '(', ')', '/', ';', '@', '[', '\\', ']', '`', '{', '}':
  224. return filterFailsafe
  225. case '-':
  226. // Disallow <!-- or -->.
  227. // -- should not appear in valid identifiers.
  228. if i != 0 && b[i-1] == '-' {
  229. return filterFailsafe
  230. }
  231. default:
  232. if c < utf8.RuneSelf && isCSSNmchar(rune(c)) {
  233. id = append(id, c)
  234. }
  235. }
  236. }
  237. id = bytes.ToLower(id)
  238. if bytes.Contains(id, expressionBytes) || bytes.Contains(id, mozBindingBytes) {
  239. return filterFailsafe
  240. }
  241. return string(b)
  242. }