|  | // Copyright 2011 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package template | 
|  |  | 
|  | import ( | 
|  | "bytes" | 
|  | "fmt" | 
|  | "strings" | 
|  | "unicode" | 
|  | "unicode/utf8" | 
|  | ) | 
|  |  | 
|  | // endsWithCSSKeyword reports whether b ends with an ident that | 
|  | // case-insensitively matches the lower-case kw. | 
|  | func endsWithCSSKeyword(b []byte, kw string) bool { | 
|  | i := len(b) - len(kw) | 
|  | if i < 0 { | 
|  | // Too short. | 
|  | return false | 
|  | } | 
|  | if i != 0 { | 
|  | r, _ := utf8.DecodeLastRune(b[:i]) | 
|  | if isCSSNmchar(r) { | 
|  | // Too long. | 
|  | return false | 
|  | } | 
|  | } | 
|  | // Many CSS keywords, such as "!important" can have characters encoded, | 
|  | // but the URI production does not allow that according to | 
|  | // https://www.w3.org/TR/css3-syntax/#TOK-URI | 
|  | // This does not attempt to recognize encoded keywords. For example, | 
|  | // given "\75\72\6c" and "url" this return false. | 
|  | return string(bytes.ToLower(b[i:])) == kw | 
|  | } | 
|  |  | 
|  | // isCSSNmchar reports whether rune is allowed anywhere in a CSS identifier. | 
|  | func isCSSNmchar(r rune) bool { | 
|  | // Based on the CSS3 nmchar production but ignores multi-rune escape | 
|  | // sequences. | 
|  | // https://www.w3.org/TR/css3-syntax/#SUBTOK-nmchar | 
|  | return 'a' <= r && r <= 'z' || | 
|  | 'A' <= r && r <= 'Z' || | 
|  | '0' <= r && r <= '9' || | 
|  | r == '-' || | 
|  | r == '_' || | 
|  | // Non-ASCII cases below. | 
|  | 0x80 <= r && r <= 0xd7ff || | 
|  | 0xe000 <= r && r <= 0xfffd || | 
|  | 0x10000 <= r && r <= 0x10ffff | 
|  | } | 
|  |  | 
|  | // decodeCSS decodes CSS3 escapes given a sequence of stringchars. | 
|  | // If there is no change, it returns the input, otherwise it returns a slice | 
|  | // backed by a new array. | 
|  | // https://www.w3.org/TR/css3-syntax/#SUBTOK-stringchar defines stringchar. | 
|  | func decodeCSS(s []byte) []byte { | 
|  | i := bytes.IndexByte(s, '\\') | 
|  | if i == -1 { | 
|  | return s | 
|  | } | 
|  | // The UTF-8 sequence for a codepoint is never longer than 1 + the | 
|  | // number hex digits need to represent that codepoint, so len(s) is an | 
|  | // upper bound on the output length. | 
|  | b := make([]byte, 0, len(s)) | 
|  | for len(s) != 0 { | 
|  | i := bytes.IndexByte(s, '\\') | 
|  | if i == -1 { | 
|  | i = len(s) | 
|  | } | 
|  | b, s = append(b, s[:i]...), s[i:] | 
|  | if len(s) < 2 { | 
|  | break | 
|  | } | 
|  | // https://www.w3.org/TR/css3-syntax/#SUBTOK-escape | 
|  | // escape ::= unicode | '\' [#x20-#x7E#x80-#xD7FF#xE000-#xFFFD#x10000-#x10FFFF] | 
|  | if isHex(s[1]) { | 
|  | // https://www.w3.org/TR/css3-syntax/#SUBTOK-unicode | 
|  | //   unicode ::= '\' [0-9a-fA-F]{1,6} wc? | 
|  | j := 2 | 
|  | for j < len(s) && j < 7 && isHex(s[j]) { | 
|  | j++ | 
|  | } | 
|  | r := hexDecode(s[1:j]) | 
|  | if r > unicode.MaxRune { | 
|  | r, j = r/16, j-1 | 
|  | } | 
|  | n := utf8.EncodeRune(b[len(b):cap(b)], r) | 
|  | // The optional space at the end allows a hex | 
|  | // sequence to be followed by a literal hex. | 
|  | // string(decodeCSS([]byte(`\A B`))) == "\nB" | 
|  | b, s = b[:len(b)+n], skipCSSSpace(s[j:]) | 
|  | } else { | 
|  | // `\\` decodes to `\` and `\"` to `"`. | 
|  | _, n := utf8.DecodeRune(s[1:]) | 
|  | b, s = append(b, s[1:1+n]...), s[1+n:] | 
|  | } | 
|  | } | 
|  | return b | 
|  | } | 
|  |  | 
|  | // isHex reports whether the given character is a hex digit. | 
|  | func isHex(c byte) bool { | 
|  | return '0' <= c && c <= '9' || 'a' <= c && c <= 'f' || 'A' <= c && c <= 'F' | 
|  | } | 
|  |  | 
|  | // hexDecode decodes a short hex digit sequence: "10" -> 16. | 
|  | func hexDecode(s []byte) rune { | 
|  | n := '\x00' | 
|  | for _, c := range s { | 
|  | n <<= 4 | 
|  | switch { | 
|  | case '0' <= c && c <= '9': | 
|  | n |= rune(c - '0') | 
|  | case 'a' <= c && c <= 'f': | 
|  | n |= rune(c-'a') + 10 | 
|  | case 'A' <= c && c <= 'F': | 
|  | n |= rune(c-'A') + 10 | 
|  | default: | 
|  | panic(fmt.Sprintf("Bad hex digit in %q", s)) | 
|  | } | 
|  | } | 
|  | return n | 
|  | } | 
|  |  | 
|  | // skipCSSSpace returns a suffix of c, skipping over a single space. | 
|  | func skipCSSSpace(c []byte) []byte { | 
|  | if len(c) == 0 { | 
|  | return c | 
|  | } | 
|  | // wc ::= #x9 | #xA | #xC | #xD | #x20 | 
|  | switch c[0] { | 
|  | case '\t', '\n', '\f', ' ': | 
|  | return c[1:] | 
|  | case '\r': | 
|  | // This differs from CSS3's wc production because it contains a | 
|  | // probable spec error whereby wc contains all the single byte | 
|  | // sequences in nl (newline) but not CRLF. | 
|  | if len(c) >= 2 && c[1] == '\n' { | 
|  | return c[2:] | 
|  | } | 
|  | return c[1:] | 
|  | } | 
|  | return c | 
|  | } | 
|  |  | 
|  | // isCSSSpace reports whether b is a CSS space char as defined in wc. | 
|  | func isCSSSpace(b byte) bool { | 
|  | switch b { | 
|  | case '\t', '\n', '\f', '\r', ' ': | 
|  | return true | 
|  | } | 
|  | return false | 
|  | } | 
|  |  | 
|  | // cssEscaper escapes HTML and CSS special characters using \<hex>+ escapes. | 
|  | func cssEscaper(args ...interface{}) string { | 
|  | s, _ := stringify(args...) | 
|  | var b strings.Builder | 
|  | r, w, written := rune(0), 0, 0 | 
|  | for i := 0; i < len(s); i += w { | 
|  | // See comment in htmlEscaper. | 
|  | r, w = utf8.DecodeRuneInString(s[i:]) | 
|  | var repl string | 
|  | switch { | 
|  | case int(r) < len(cssReplacementTable) && cssReplacementTable[r] != "": | 
|  | repl = cssReplacementTable[r] | 
|  | default: | 
|  | continue | 
|  | } | 
|  | if written == 0 { | 
|  | b.Grow(len(s)) | 
|  | } | 
|  | b.WriteString(s[written:i]) | 
|  | b.WriteString(repl) | 
|  | written = i + w | 
|  | if repl != `\\` && (written == len(s) || isHex(s[written]) || isCSSSpace(s[written])) { | 
|  | b.WriteByte(' ') | 
|  | } | 
|  | } | 
|  | if written == 0 { | 
|  | return s | 
|  | } | 
|  | b.WriteString(s[written:]) | 
|  | return b.String() | 
|  | } | 
|  |  | 
|  | var cssReplacementTable = []string{ | 
|  | 0:    `\0`, | 
|  | '\t': `\9`, | 
|  | '\n': `\a`, | 
|  | '\f': `\c`, | 
|  | '\r': `\d`, | 
|  | // Encode HTML specials as hex so the output can be embedded | 
|  | // in HTML attributes without further encoding. | 
|  | '"':  `\22`, | 
|  | '&':  `\26`, | 
|  | '\'': `\27`, | 
|  | '(':  `\28`, | 
|  | ')':  `\29`, | 
|  | '+':  `\2b`, | 
|  | '/':  `\2f`, | 
|  | ':':  `\3a`, | 
|  | ';':  `\3b`, | 
|  | '<':  `\3c`, | 
|  | '>':  `\3e`, | 
|  | '\\': `\\`, | 
|  | '{':  `\7b`, | 
|  | '}':  `\7d`, | 
|  | } | 
|  |  | 
|  | var expressionBytes = []byte("expression") | 
|  | var mozBindingBytes = []byte("mozbinding") | 
|  |  | 
|  | // cssValueFilter allows innocuous CSS values in the output including CSS | 
|  | // quantities (10px or 25%), ID or class literals (#foo, .bar), keyword values | 
|  | // (inherit, blue), and colors (#888). | 
|  | // It filters out unsafe values, such as those that affect token boundaries, | 
|  | // and anything that might execute scripts. | 
|  | func cssValueFilter(args ...interface{}) string { | 
|  | s, t := stringify(args...) | 
|  | if t == contentTypeCSS { | 
|  | return s | 
|  | } | 
|  | b, id := decodeCSS([]byte(s)), make([]byte, 0, 64) | 
|  |  | 
|  | // CSS3 error handling is specified as honoring string boundaries per | 
|  | // https://www.w3.org/TR/css3-syntax/#error-handling : | 
|  | //     Malformed declarations. User agents must handle unexpected | 
|  | //     tokens encountered while parsing a declaration by reading until | 
|  | //     the end of the declaration, while observing the rules for | 
|  | //     matching pairs of (), [], {}, "", and '', and correctly handling | 
|  | //     escapes. For example, a malformed declaration may be missing a | 
|  | //     property, colon (:) or value. | 
|  | // So we need to make sure that values do not have mismatched bracket | 
|  | // or quote characters to prevent the browser from restarting parsing | 
|  | // inside a string that might embed JavaScript source. | 
|  | for i, c := range b { | 
|  | switch c { | 
|  | case 0, '"', '\'', '(', ')', '/', ';', '@', '[', '\\', ']', '`', '{', '}': | 
|  | return filterFailsafe | 
|  | case '-': | 
|  | // Disallow <!-- or -->. | 
|  | // -- should not appear in valid identifiers. | 
|  | if i != 0 && b[i-1] == '-' { | 
|  | return filterFailsafe | 
|  | } | 
|  | default: | 
|  | if c < utf8.RuneSelf && isCSSNmchar(rune(c)) { | 
|  | id = append(id, c) | 
|  | } | 
|  | } | 
|  | } | 
|  | id = bytes.ToLower(id) | 
|  | if bytes.Contains(id, expressionBytes) || bytes.Contains(id, mozBindingBytes) { | 
|  | return filterFailsafe | 
|  | } | 
|  | return string(b) | 
|  | } |