1 package nostr
2 3 import "unicode/utf8"
4 5 // AppendJSONString appends s as a JSON string literal to dst using the same
6 // escaping rules as JavaScript's JSON.stringify:
7 //
8 // - quote and backslash are escaped;
9 // - \b \f \n \r \t use their short escapes;
10 // - other C0 control bytes become \u00XX with lowercase hex digits;
11 // - everything else, including DEL, U+2028 and U+2029, is emitted as UTF-8.
12 //
13 // Invalid UTF-8 is replaced with U+FFFD, matching what a JSON parser would
14 // produce for such input in a JS engine. This is what makes the event ID stable
15 // across implementations: NIP-01 defines the serialization in terms of
16 // JSON.stringify, not in terms of Go's encoder.
17 func AppendJSONString(dst []byte, s string) []byte {
18 dst = append(dst, '"')
19 start := 0
20 for i := 0; i < len(s); {
21 c := s[i]
22 if c < utf8.RuneSelf {
23 if c >= 0x20 && c != '"' && c != '\\' {
24 i++
25 continue
26 }
27 dst = append(dst, s[start:i]...)
28 switch c {
29 case '"':
30 dst = append(dst, '\\', '"')
31 case '\\':
32 dst = append(dst, '\\', '\\')
33 case '\b':
34 dst = append(dst, '\\', 'b')
35 case '\f':
36 dst = append(dst, '\\', 'f')
37 case '\n':
38 dst = append(dst, '\\', 'n')
39 case '\r':
40 dst = append(dst, '\\', 'r')
41 case '\t':
42 dst = append(dst, '\\', 't')
43 default:
44 const hexDigits = "0123456789abcdef"
45 dst = append(dst, '\\', 'u', '0', '0', hexDigits[c>>4], hexDigits[c&0x0f])
46 }
47 i++
48 start = i
49 continue
50 }
51 r, size := utf8.DecodeRuneInString(s[i:])
52 if r == utf8.RuneError && size == 1 {
53 dst = append(dst, s[start:i]...)
54 dst = append(dst, "\uFFFD"...)
55 i++
56 start = i
57 continue
58 }
59 i += size
60 }
61 dst = append(dst, s[start:]...)
62 return append(dst, '"')
63 }
64