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