...

Source file src/html/template/css.go

Documentation: html/template

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

View as plain text