mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	
		
			
				
	
	
		
			641 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
			
		
		
	
	
			641 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
| package encoder
 | |
| 
 | |
| import (
 | |
| 	"math/bits"
 | |
| 	"reflect"
 | |
| 	"unicode/utf8"
 | |
| 	"unsafe"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	lsb = 0x0101010101010101
 | |
| 	msb = 0x8080808080808080
 | |
| )
 | |
| 
 | |
| var needEscapeWithHTML = [256]bool{
 | |
| 	'"':  true,
 | |
| 	'&':  true,
 | |
| 	'<':  true,
 | |
| 	'>':  true,
 | |
| 	'\\': true,
 | |
| 	0x00: true,
 | |
| 	0x01: true,
 | |
| 	0x02: true,
 | |
| 	0x03: true,
 | |
| 	0x04: true,
 | |
| 	0x05: true,
 | |
| 	0x06: true,
 | |
| 	0x07: true,
 | |
| 	0x08: true,
 | |
| 	0x09: true,
 | |
| 	0x0a: true,
 | |
| 	0x0b: true,
 | |
| 	0x0c: true,
 | |
| 	0x0d: true,
 | |
| 	0x0e: true,
 | |
| 	0x0f: true,
 | |
| 	0x10: true,
 | |
| 	0x11: true,
 | |
| 	0x12: true,
 | |
| 	0x13: true,
 | |
| 	0x14: true,
 | |
| 	0x15: true,
 | |
| 	0x16: true,
 | |
| 	0x17: true,
 | |
| 	0x18: true,
 | |
| 	0x19: true,
 | |
| 	0x1a: true,
 | |
| 	0x1b: true,
 | |
| 	0x1c: true,
 | |
| 	0x1d: true,
 | |
| 	0x1e: true,
 | |
| 	0x1f: true,
 | |
| 	/* 0x20 - 0x7f */
 | |
| 	0x80: true,
 | |
| 	0x81: true,
 | |
| 	0x82: true,
 | |
| 	0x83: true,
 | |
| 	0x84: true,
 | |
| 	0x85: true,
 | |
| 	0x86: true,
 | |
| 	0x87: true,
 | |
| 	0x88: true,
 | |
| 	0x89: true,
 | |
| 	0x8a: true,
 | |
| 	0x8b: true,
 | |
| 	0x8c: true,
 | |
| 	0x8d: true,
 | |
| 	0x8e: true,
 | |
| 	0x8f: true,
 | |
| 	0x90: true,
 | |
| 	0x91: true,
 | |
| 	0x92: true,
 | |
| 	0x93: true,
 | |
| 	0x94: true,
 | |
| 	0x95: true,
 | |
| 	0x96: true,
 | |
| 	0x97: true,
 | |
| 	0x98: true,
 | |
| 	0x99: true,
 | |
| 	0x9a: true,
 | |
| 	0x9b: true,
 | |
| 	0x9c: true,
 | |
| 	0x9d: true,
 | |
| 	0x9e: true,
 | |
| 	0x9f: true,
 | |
| 	0xa0: true,
 | |
| 	0xa1: true,
 | |
| 	0xa2: true,
 | |
| 	0xa3: true,
 | |
| 	0xa4: true,
 | |
| 	0xa5: true,
 | |
| 	0xa6: true,
 | |
| 	0xa7: true,
 | |
| 	0xa8: true,
 | |
| 	0xa9: true,
 | |
| 	0xaa: true,
 | |
| 	0xab: true,
 | |
| 	0xac: true,
 | |
| 	0xad: true,
 | |
| 	0xae: true,
 | |
| 	0xaf: true,
 | |
| 	0xb0: true,
 | |
| 	0xb1: true,
 | |
| 	0xb2: true,
 | |
| 	0xb3: true,
 | |
| 	0xb4: true,
 | |
| 	0xb5: true,
 | |
| 	0xb6: true,
 | |
| 	0xb7: true,
 | |
| 	0xb8: true,
 | |
| 	0xb9: true,
 | |
| 	0xba: true,
 | |
| 	0xbb: true,
 | |
| 	0xbc: true,
 | |
| 	0xbd: true,
 | |
| 	0xbe: true,
 | |
| 	0xbf: true,
 | |
| 	0xc0: true,
 | |
| 	0xc1: true,
 | |
| 	0xc2: true,
 | |
| 	0xc3: true,
 | |
| 	0xc4: true,
 | |
| 	0xc5: true,
 | |
| 	0xc6: true,
 | |
| 	0xc7: true,
 | |
| 	0xc8: true,
 | |
| 	0xc9: true,
 | |
| 	0xca: true,
 | |
| 	0xcb: true,
 | |
| 	0xcc: true,
 | |
| 	0xcd: true,
 | |
| 	0xce: true,
 | |
| 	0xcf: true,
 | |
| 	0xd0: true,
 | |
| 	0xd1: true,
 | |
| 	0xd2: true,
 | |
| 	0xd3: true,
 | |
| 	0xd4: true,
 | |
| 	0xd5: true,
 | |
| 	0xd6: true,
 | |
| 	0xd7: true,
 | |
| 	0xd8: true,
 | |
| 	0xd9: true,
 | |
| 	0xda: true,
 | |
| 	0xdb: true,
 | |
| 	0xdc: true,
 | |
| 	0xdd: true,
 | |
| 	0xde: true,
 | |
| 	0xdf: true,
 | |
| 	0xe0: true,
 | |
| 	0xe1: true,
 | |
| 	0xe2: true,
 | |
| 	0xe3: true,
 | |
| 	0xe4: true,
 | |
| 	0xe5: true,
 | |
| 	0xe6: true,
 | |
| 	0xe7: true,
 | |
| 	0xe8: true,
 | |
| 	0xe9: true,
 | |
| 	0xea: true,
 | |
| 	0xeb: true,
 | |
| 	0xec: true,
 | |
| 	0xed: true,
 | |
| 	0xee: true,
 | |
| 	0xef: true,
 | |
| 	0xf0: true,
 | |
| 	0xf1: true,
 | |
| 	0xf2: true,
 | |
| 	0xf3: true,
 | |
| 	0xf4: true,
 | |
| 	0xf5: true,
 | |
| 	0xf6: true,
 | |
| 	0xf7: true,
 | |
| 	0xf8: true,
 | |
| 	0xf9: true,
 | |
| 	0xfa: true,
 | |
| 	0xfb: true,
 | |
| 	0xfc: true,
 | |
| 	0xfd: true,
 | |
| 	0xfe: true,
 | |
| 	0xff: true,
 | |
| }
 | |
| 
 | |
| var needEscape = [256]bool{
 | |
| 	'"':  true,
 | |
| 	'\\': true,
 | |
| 	0x00: true,
 | |
| 	0x01: true,
 | |
| 	0x02: true,
 | |
| 	0x03: true,
 | |
| 	0x04: true,
 | |
| 	0x05: true,
 | |
| 	0x06: true,
 | |
| 	0x07: true,
 | |
| 	0x08: true,
 | |
| 	0x09: true,
 | |
| 	0x0a: true,
 | |
| 	0x0b: true,
 | |
| 	0x0c: true,
 | |
| 	0x0d: true,
 | |
| 	0x0e: true,
 | |
| 	0x0f: true,
 | |
| 	0x10: true,
 | |
| 	0x11: true,
 | |
| 	0x12: true,
 | |
| 	0x13: true,
 | |
| 	0x14: true,
 | |
| 	0x15: true,
 | |
| 	0x16: true,
 | |
| 	0x17: true,
 | |
| 	0x18: true,
 | |
| 	0x19: true,
 | |
| 	0x1a: true,
 | |
| 	0x1b: true,
 | |
| 	0x1c: true,
 | |
| 	0x1d: true,
 | |
| 	0x1e: true,
 | |
| 	0x1f: true,
 | |
| 	/* 0x20 - 0x7f */
 | |
| 	0x80: true,
 | |
| 	0x81: true,
 | |
| 	0x82: true,
 | |
| 	0x83: true,
 | |
| 	0x84: true,
 | |
| 	0x85: true,
 | |
| 	0x86: true,
 | |
| 	0x87: true,
 | |
| 	0x88: true,
 | |
| 	0x89: true,
 | |
| 	0x8a: true,
 | |
| 	0x8b: true,
 | |
| 	0x8c: true,
 | |
| 	0x8d: true,
 | |
| 	0x8e: true,
 | |
| 	0x8f: true,
 | |
| 	0x90: true,
 | |
| 	0x91: true,
 | |
| 	0x92: true,
 | |
| 	0x93: true,
 | |
| 	0x94: true,
 | |
| 	0x95: true,
 | |
| 	0x96: true,
 | |
| 	0x97: true,
 | |
| 	0x98: true,
 | |
| 	0x99: true,
 | |
| 	0x9a: true,
 | |
| 	0x9b: true,
 | |
| 	0x9c: true,
 | |
| 	0x9d: true,
 | |
| 	0x9e: true,
 | |
| 	0x9f: true,
 | |
| 	0xa0: true,
 | |
| 	0xa1: true,
 | |
| 	0xa2: true,
 | |
| 	0xa3: true,
 | |
| 	0xa4: true,
 | |
| 	0xa5: true,
 | |
| 	0xa6: true,
 | |
| 	0xa7: true,
 | |
| 	0xa8: true,
 | |
| 	0xa9: true,
 | |
| 	0xaa: true,
 | |
| 	0xab: true,
 | |
| 	0xac: true,
 | |
| 	0xad: true,
 | |
| 	0xae: true,
 | |
| 	0xaf: true,
 | |
| 	0xb0: true,
 | |
| 	0xb1: true,
 | |
| 	0xb2: true,
 | |
| 	0xb3: true,
 | |
| 	0xb4: true,
 | |
| 	0xb5: true,
 | |
| 	0xb6: true,
 | |
| 	0xb7: true,
 | |
| 	0xb8: true,
 | |
| 	0xb9: true,
 | |
| 	0xba: true,
 | |
| 	0xbb: true,
 | |
| 	0xbc: true,
 | |
| 	0xbd: true,
 | |
| 	0xbe: true,
 | |
| 	0xbf: true,
 | |
| 	0xc0: true,
 | |
| 	0xc1: true,
 | |
| 	0xc2: true,
 | |
| 	0xc3: true,
 | |
| 	0xc4: true,
 | |
| 	0xc5: true,
 | |
| 	0xc6: true,
 | |
| 	0xc7: true,
 | |
| 	0xc8: true,
 | |
| 	0xc9: true,
 | |
| 	0xca: true,
 | |
| 	0xcb: true,
 | |
| 	0xcc: true,
 | |
| 	0xcd: true,
 | |
| 	0xce: true,
 | |
| 	0xcf: true,
 | |
| 	0xd0: true,
 | |
| 	0xd1: true,
 | |
| 	0xd2: true,
 | |
| 	0xd3: true,
 | |
| 	0xd4: true,
 | |
| 	0xd5: true,
 | |
| 	0xd6: true,
 | |
| 	0xd7: true,
 | |
| 	0xd8: true,
 | |
| 	0xd9: true,
 | |
| 	0xda: true,
 | |
| 	0xdb: true,
 | |
| 	0xdc: true,
 | |
| 	0xdd: true,
 | |
| 	0xde: true,
 | |
| 	0xdf: true,
 | |
| 	0xe0: true,
 | |
| 	0xe1: true,
 | |
| 	0xe2: true,
 | |
| 	0xe3: true,
 | |
| 	0xe4: true,
 | |
| 	0xe5: true,
 | |
| 	0xe6: true,
 | |
| 	0xe7: true,
 | |
| 	0xe8: true,
 | |
| 	0xe9: true,
 | |
| 	0xea: true,
 | |
| 	0xeb: true,
 | |
| 	0xec: true,
 | |
| 	0xed: true,
 | |
| 	0xee: true,
 | |
| 	0xef: true,
 | |
| 	0xf0: true,
 | |
| 	0xf1: true,
 | |
| 	0xf2: true,
 | |
| 	0xf3: true,
 | |
| 	0xf4: true,
 | |
| 	0xf5: true,
 | |
| 	0xf6: true,
 | |
| 	0xf7: true,
 | |
| 	0xf8: true,
 | |
| 	0xf9: true,
 | |
| 	0xfa: true,
 | |
| 	0xfb: true,
 | |
| 	0xfc: true,
 | |
| 	0xfd: true,
 | |
| 	0xfe: true,
 | |
| 	0xff: true,
 | |
| }
 | |
| 
 | |
| var hex = "0123456789abcdef"
 | |
| 
 | |
| // escapeIndex finds the index of the first char in `s` that requires escaping.
 | |
| // A char requires escaping if it's outside of the range of [0x20, 0x7F] or if
 | |
| // it includes a double quote or backslash.
 | |
| // If no chars in `s` require escaping, the return value is -1.
 | |
| func escapeIndex(s string) int {
 | |
| 	chunks := stringToUint64Slice(s)
 | |
| 	for _, n := range chunks {
 | |
| 		// combine masks before checking for the MSB of each byte. We include
 | |
| 		// `n` in the mask to check whether any of the *input* byte MSBs were
 | |
| 		// set (i.e. the byte was outside the ASCII range).
 | |
| 		mask := n | below(n, 0x20) | contains(n, '"') | contains(n, '\\')
 | |
| 		if (mask & msb) != 0 {
 | |
| 			return bits.TrailingZeros64(mask&msb) / 8
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	valLen := len(s)
 | |
| 	for i := len(chunks) * 8; i < valLen; i++ {
 | |
| 		if needEscape[s[i]] {
 | |
| 			return i
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return -1
 | |
| }
 | |
| 
 | |
| // below return a mask that can be used to determine if any of the bytes
 | |
| // in `n` are below `b`. If a byte's MSB is set in the mask then that byte was
 | |
| // below `b`. The result is only valid if `b`, and each byte in `n`, is below
 | |
| // 0x80.
 | |
| func below(n uint64, b byte) uint64 {
 | |
| 	return n - expand(b)
 | |
| }
 | |
| 
 | |
| // contains returns a mask that can be used to determine if any of the
 | |
| // bytes in `n` are equal to `b`. If a byte's MSB is set in the mask then
 | |
| // that byte is equal to `b`. The result is only valid if `b`, and each
 | |
| // byte in `n`, is below 0x80.
 | |
| func contains(n uint64, b byte) uint64 {
 | |
| 	return (n ^ expand(b)) - lsb
 | |
| }
 | |
| 
 | |
| // expand puts the specified byte into each of the 8 bytes of a uint64.
 | |
| func expand(b byte) uint64 {
 | |
| 	return lsb * uint64(b)
 | |
| }
 | |
| 
 | |
| //nolint:govet
 | |
| func stringToUint64Slice(s string) []uint64 {
 | |
| 	return *(*[]uint64)(unsafe.Pointer(&reflect.SliceHeader{
 | |
| 		Data: ((*reflect.StringHeader)(unsafe.Pointer(&s))).Data,
 | |
| 		Len:  len(s) / 8,
 | |
| 		Cap:  len(s) / 8,
 | |
| 	}))
 | |
| }
 | |
| 
 | |
| func AppendString(ctx *RuntimeContext, buf []byte, s string) []byte {
 | |
| 	if ctx.Option.Flag&HTMLEscapeOption == 0 {
 | |
| 		return appendString(buf, s)
 | |
| 	}
 | |
| 	valLen := len(s)
 | |
| 	if valLen == 0 {
 | |
| 		return append(buf, `""`...)
 | |
| 	}
 | |
| 	buf = append(buf, '"')
 | |
| 	var (
 | |
| 		i, j int
 | |
| 	)
 | |
| 	if valLen >= 8 {
 | |
| 		chunks := stringToUint64Slice(s)
 | |
| 		for _, n := range chunks {
 | |
| 			// combine masks before checking for the MSB of each byte. We include
 | |
| 			// `n` in the mask to check whether any of the *input* byte MSBs were
 | |
| 			// set (i.e. the byte was outside the ASCII range).
 | |
| 			mask := n | (n - (lsb * 0x20)) |
 | |
| 				((n ^ (lsb * '"')) - lsb) |
 | |
| 				((n ^ (lsb * '\\')) - lsb) |
 | |
| 				((n ^ (lsb * '<')) - lsb) |
 | |
| 				((n ^ (lsb * '>')) - lsb) |
 | |
| 				((n ^ (lsb * '&')) - lsb)
 | |
| 			if (mask & msb) != 0 {
 | |
| 				j = bits.TrailingZeros64(mask&msb) / 8
 | |
| 				goto ESCAPE_END
 | |
| 			}
 | |
| 		}
 | |
| 		for i := len(chunks) * 8; i < valLen; i++ {
 | |
| 			if needEscapeWithHTML[s[i]] {
 | |
| 				j = i
 | |
| 				goto ESCAPE_END
 | |
| 			}
 | |
| 		}
 | |
| 		// no found any escape characters.
 | |
| 		return append(append(buf, s...), '"')
 | |
| 	}
 | |
| ESCAPE_END:
 | |
| 	for j < valLen {
 | |
| 		c := s[j]
 | |
| 
 | |
| 		if !needEscapeWithHTML[c] {
 | |
| 			// fast path: most of the time, printable ascii characters are used
 | |
| 			j++
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		switch c {
 | |
| 		case '\\', '"':
 | |
| 			buf = append(buf, s[i:j]...)
 | |
| 			buf = append(buf, '\\', c)
 | |
| 			i = j + 1
 | |
| 			j = j + 1
 | |
| 			continue
 | |
| 
 | |
| 		case '\n':
 | |
| 			buf = append(buf, s[i:j]...)
 | |
| 			buf = append(buf, '\\', 'n')
 | |
| 			i = j + 1
 | |
| 			j = j + 1
 | |
| 			continue
 | |
| 
 | |
| 		case '\r':
 | |
| 			buf = append(buf, s[i:j]...)
 | |
| 			buf = append(buf, '\\', 'r')
 | |
| 			i = j + 1
 | |
| 			j = j + 1
 | |
| 			continue
 | |
| 
 | |
| 		case '\t':
 | |
| 			buf = append(buf, s[i:j]...)
 | |
| 			buf = append(buf, '\\', 't')
 | |
| 			i = j + 1
 | |
| 			j = j + 1
 | |
| 			continue
 | |
| 
 | |
| 		case '<', '>', '&':
 | |
| 			buf = append(buf, s[i:j]...)
 | |
| 			buf = append(buf, `\u00`...)
 | |
| 			buf = append(buf, hex[c>>4], hex[c&0xF])
 | |
| 			i = j + 1
 | |
| 			j = j + 1
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// This encodes bytes < 0x20 except for \t, \n and \r.
 | |
| 		if c < 0x20 {
 | |
| 			buf = append(buf, s[i:j]...)
 | |
| 			buf = append(buf, `\u00`...)
 | |
| 			buf = append(buf, hex[c>>4], hex[c&0xF])
 | |
| 			i = j + 1
 | |
| 			j = j + 1
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		r, size := utf8.DecodeRuneInString(s[j:])
 | |
| 
 | |
| 		if r == utf8.RuneError && size == 1 {
 | |
| 			buf = append(buf, s[i:j]...)
 | |
| 			buf = append(buf, `\ufffd`...)
 | |
| 			i = j + size
 | |
| 			j = j + size
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		switch r {
 | |
| 		case '\u2028', '\u2029':
 | |
| 			// U+2028 is LINE SEPARATOR.
 | |
| 			// U+2029 is PARAGRAPH SEPARATOR.
 | |
| 			// They are both technically valid characters in JSON strings,
 | |
| 			// but don't work in JSONP, which has to be evaluated as JavaScript,
 | |
| 			// and can lead to security holes there. It is valid JSON to
 | |
| 			// escape them, so we do so unconditionally.
 | |
| 			// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
 | |
| 			buf = append(buf, s[i:j]...)
 | |
| 			buf = append(buf, `\u202`...)
 | |
| 			buf = append(buf, hex[r&0xF])
 | |
| 			i = j + size
 | |
| 			j = j + size
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		j += size
 | |
| 	}
 | |
| 
 | |
| 	return append(append(buf, s[i:]...), '"')
 | |
| }
 | |
| 
 | |
| func appendString(buf []byte, s string) []byte {
 | |
| 	valLen := len(s)
 | |
| 	if valLen == 0 {
 | |
| 		return append(buf, `""`...)
 | |
| 	}
 | |
| 	buf = append(buf, '"')
 | |
| 	var escapeIdx int
 | |
| 	if valLen >= 8 {
 | |
| 		if escapeIdx = escapeIndex(s); escapeIdx < 0 {
 | |
| 			return append(append(buf, s...), '"')
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	i := 0
 | |
| 	j := escapeIdx
 | |
| 	for j < valLen {
 | |
| 		c := s[j]
 | |
| 
 | |
| 		if c >= 0x20 && c <= 0x7f && c != '\\' && c != '"' {
 | |
| 			// fast path: most of the time, printable ascii characters are used
 | |
| 			j++
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		switch c {
 | |
| 		case '\\', '"':
 | |
| 			buf = append(buf, s[i:j]...)
 | |
| 			buf = append(buf, '\\', c)
 | |
| 			i = j + 1
 | |
| 			j = j + 1
 | |
| 			continue
 | |
| 
 | |
| 		case '\n':
 | |
| 			buf = append(buf, s[i:j]...)
 | |
| 			buf = append(buf, '\\', 'n')
 | |
| 			i = j + 1
 | |
| 			j = j + 1
 | |
| 			continue
 | |
| 
 | |
| 		case '\r':
 | |
| 			buf = append(buf, s[i:j]...)
 | |
| 			buf = append(buf, '\\', 'r')
 | |
| 			i = j + 1
 | |
| 			j = j + 1
 | |
| 			continue
 | |
| 
 | |
| 		case '\t':
 | |
| 			buf = append(buf, s[i:j]...)
 | |
| 			buf = append(buf, '\\', 't')
 | |
| 			i = j + 1
 | |
| 			j = j + 1
 | |
| 			continue
 | |
| 
 | |
| 		case '<', '>', '&':
 | |
| 			buf = append(buf, s[i:j]...)
 | |
| 			buf = append(buf, `\u00`...)
 | |
| 			buf = append(buf, hex[c>>4], hex[c&0xF])
 | |
| 			i = j + 1
 | |
| 			j = j + 1
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		// This encodes bytes < 0x20 except for \t, \n and \r.
 | |
| 		if c < 0x20 {
 | |
| 			buf = append(buf, s[i:j]...)
 | |
| 			buf = append(buf, `\u00`...)
 | |
| 			buf = append(buf, hex[c>>4], hex[c&0xF])
 | |
| 			i = j + 1
 | |
| 			j = j + 1
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		r, size := utf8.DecodeRuneInString(s[j:])
 | |
| 
 | |
| 		if r == utf8.RuneError && size == 1 {
 | |
| 			buf = append(buf, s[i:j]...)
 | |
| 			buf = append(buf, `\ufffd`...)
 | |
| 			i = j + size
 | |
| 			j = j + size
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		switch r {
 | |
| 		case '\u2028', '\u2029':
 | |
| 			// U+2028 is LINE SEPARATOR.
 | |
| 			// U+2029 is PARAGRAPH SEPARATOR.
 | |
| 			// They are both technically valid characters in JSON strings,
 | |
| 			// but don't work in JSONP, which has to be evaluated as JavaScript,
 | |
| 			// and can lead to security holes there. It is valid JSON to
 | |
| 			// escape them, so we do so unconditionally.
 | |
| 			// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
 | |
| 			buf = append(buf, s[i:j]...)
 | |
| 			buf = append(buf, `\u202`...)
 | |
| 			buf = append(buf, hex[r&0xF])
 | |
| 			i = j + size
 | |
| 			j = j + size
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		j += size
 | |
| 	}
 | |
| 
 | |
| 	return append(append(buf, s[i:]...), '"')
 | |
| }
 |