8// JSON Unicode stuff: see https://tools.ietf.org/html/rfc7159#section-7
10const supplementalPlanesOffset = 0x10000
11const highSurrogateOffset = 0xD800
12const lowSurrogateOffset = 0xDC00
14const basicMultilingualPlaneReservedOffset = 0xDFFF
15const basicMultilingualPlaneOffset = 0xFFFF
17func combineUTF16Surrogates(high, low rune) rune {
18 return supplementalPlanesOffset + (high-highSurrogateOffset)<<10 + (low - lowSurrogateOffset)
25 case c >= '0' && c <= '9':
27 case c >= 'A' && c <= 'F':
28 return int(c - 'A' + 10)
29 case c >= 'a' && c <= 'f':
30 return int(c - 'a' + 10)
35// decodeSingleUnicodeEscape decodes a single \uXXXX escape sequence. The prefix \u is assumed to be present and
37// In JSON, these escapes can either come alone or as part of "UTF16 surrogate pairs" that must be handled together.
38// This function only handles one; decodeUnicodeEscape handles this more complex case.
39func decodeSingleUnicodeEscape(in []byte) (rune, bool) {
40 // We need at least 6 characters total
42 return utf8.RuneError, false
45 // Convert hex to decimal
46 h1, h2, h3, h4 := h2I(in[2]), h2I(in[3]), h2I(in[4]), h2I(in[5])
47 if h1 == badHex || h2 == badHex || h3 == badHex || h4 == badHex {
48 return utf8.RuneError, false
51 // Compose the hex digits
52 return rune(h1<<12 + h2<<8 + h3<<4 + h4), true
55// isUTF16EncodedRune checks if a rune is in the range for non-BMP characters,
56// which is used to describe UTF16 chars.
57// Source: https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane
58func isUTF16EncodedRune(r rune) bool {
59 return highSurrogateOffset <= r && r <= basicMultilingualPlaneReservedOffset
62func decodeUnicodeEscape(in []byte) (rune, int) {
63 if r, ok := decodeSingleUnicodeEscape(in); !ok {
64 // Invalid Unicode escape
65 return utf8.RuneError, -1
66 } else if r <= basicMultilingualPlaneOffset && !isUTF16EncodedRune(r) {
67 // Valid Unicode escape in Basic Multilingual Plane
69 } else if r2, ok := decodeSingleUnicodeEscape(in[6:]); !ok { // Note: previous decodeSingleUnicodeEscape success guarantees at least 6 bytes remain
70 // UTF16 "high surrogate" without manditory valid following Unicode escape for the "low surrogate"
71 return utf8.RuneError, -1
72 } else if r2 < lowSurrogateOffset {
73 // Invalid UTF16 "low surrogate"
74 return utf8.RuneError, -1
76 // Valid UTF16 surrogate pair
77 return combineUTF16Surrogates(r, r2), 12
81// backslashCharEscapeTable: when '\X' is found for some byte X, it is to be replaced with backslashCharEscapeTable[X]
82var backslashCharEscapeTable = [...]byte{
93// unescapeToUTF8 unescapes the single escape sequence starting at 'in' into 'out' and returns
94// how many characters were consumed from 'in' and emitted into 'out'.
95// If a valid escape sequence does not appear as a prefix of 'in', (-1, -1) to signal the error.
96func unescapeToUTF8(in, out []byte) (inLen int, outLen int) {
97 if len(in) < 2 || in[0] != '\\' {
98 // Invalid escape due to insufficient characters for any escape or no initial backslash
102 // https://tools.ietf.org/html/rfc7159#section-7
103 switch e := in[1]; e {
104 case '"', '\\', '/', 'b', 'f', 'n', 'r', 't':
105 // Valid basic 2-character escapes (use lookup table)
106 out[0] = backslashCharEscapeTable[e]
110 if r, inLen := decodeUnicodeEscape(in); inLen == -1 {
111 // Invalid Unicode escape
114 // Valid Unicode escape; re-encode as UTF8
115 outLen := utf8.EncodeRune(out, r)
123// unescape unescapes the string contained in 'in' and returns it as a slice.
124// If 'in' contains no escaped characters:
126// Else, if 'out' is of sufficient capacity (guaranteed if cap(out) >= len(in)):
127// 'out' is used to build the unescaped string and is returned with no extra allocation
129// A new slice is allocated and returned.
130func Unescape(in, out []byte) ([]byte, error) {
131 firstBackslash := bytes.IndexByte(in, '\\')
132 if firstBackslash == -1 {
136 // Get a buffer of sufficient size (allocate if needed)
137 if cap(out) < len(in) {
138 out = make([]byte, len(in))
143 // Copy the first sequence of unescaped bytes to the output and obtain a buffer pointer (subslice)
144 copy(out, in[:firstBackslash])
145 in = in[firstBackslash:]
146 buf := out[firstBackslash:]
149 // Unescape the next escaped character
150 inLen, bufLen := unescapeToUTF8(in, buf)
152 return nil, MalformedStringEscapeError
158 // Copy everything up until the next backslash
159 nextBackslash := bytes.IndexByte(in, '\\')
160 if nextBackslash == -1 {
165 copy(buf, in[:nextBackslash])
166 buf = buf[nextBackslash:]
167 in = in[nextBackslash:]
171 // Trim the out buffer to the amount that was actually emitted
172 return out[:len(out)-len(buf)], nil
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list