package daemon import ( "encoding/json" "strconv" "unicode/utf8" ) // JSONStringDecoder consumes contents after a JSON string's opening quote. // Its zero value is ready to use. It retains incomplete characters and escapes // across Append calls, and stops at a closing quote or malformed escape. type JSONStringDecoder struct { high [6]byte escape [6]byte pending [utf8.UTFMax]byte escapeLen int pendingLen int hasHigh bool done bool } func (d *JSONStringDecoder) Done() bool { return d.done } // Append emits decoded text without retaining the input or accumulating output. func (d *JSONStringDecoder) Append(text string, emit func(string)) { for i := 0; i < len(text) && !d.done; { ch := text[i] if d.escapeLen > 0 { d.escape[d.escapeLen] = ch d.escapeLen++ i++ if d.escapeLen == 2 && ch == 'u' { continue } if d.escape[1] == 'u' && d.escapeLen < len(d.escape) { continue } if !d.decodeEscape(emit) { d.Finish(emit) } d.escapeLen = 0 continue } if d.pendingLen > 0 { // A non-continuation byte belongs to the next token. if ch&0xc0 != 0x80 { d.flushUTF8(emit) continue } d.pending[d.pendingLen] = ch d.pendingLen++ i++ if utf8.FullRune(d.pending[:d.pendingLen]) { d.flushUTF8(emit) } continue } switch { case ch == '"' || ch < 0x20: d.Finish(emit) case ch == '\\': d.escape[0], d.escapeLen = ch, 1 i++ case ch < utf8.RuneSelf: d.flushSurrogate(emit) start := i for i < len(text) && text[i] >= 0x20 && text[i] < utf8.RuneSelf && text[i] != '\\' && text[i] != '"' { i++ } emit(text[start:i]) case utf8.FullRuneInString(text[i:]): char, size := utf8.DecodeRuneInString(text[i:]) d.emitRune(char, emit) i += size default: d.pendingLen = copy(d.pending[:], text[i:]) return } } } // Only complete escapes reach encoding/json. A high surrogate waits for the // following escape so the standard decoder can interpret the pair together. func (d *JSONStringDecoder) decodeEscape(emit func(string)) bool { var decoded string atom := string(d.escape[:d.escapeLen]) if json.Unmarshal([]byte(`"`+atom+`"`), &decoded) != nil { return false } if d.escapeLen == 6 { // encoding/json has already validated these hexadecimal digits. code, _ := strconv.ParseUint(atom[2:], 16, 16) if code >= 0xd800 && code <= 0xdbff { d.flushSurrogate(emit) d.high, d.hasHigh = d.escape, true return true } } if d.hasHigh { if json.Unmarshal([]byte(`"`+string(d.high[:])+atom+`"`), &decoded) != nil { return false } d.hasHigh = false } emit(decoded) return true } func (d *JSONStringDecoder) emitRune(char rune, emit func(string)) { d.flushSurrogate(emit) emit(string(char)) } func (d *JSONStringDecoder) flushSurrogate(emit func(string)) { if d.hasHigh { emit(string(utf8.RuneError)) d.hasHigh = false } } func (d *JSONStringDecoder) flushUTF8(emit func(string)) { for i := 0; i < d.pendingLen; { char, size := utf8.DecodeRune(d.pending[i:d.pendingLen]) d.emitRune(char, emit) i += size } d.pendingLen = 0 } // Finish settles incomplete UTF-8 and unpaired surrogates, discards an // unfinished escape, and stops accepting input. func (d *JSONStringDecoder) Finish(emit func(string)) { if d.done { return } d.flushUTF8(emit) d.flushSurrogate(emit) d.done = true }