package nostr import "unicode/utf8" // AppendJSONString appends s as a JSON string literal to dst using the same // escaping rules as JavaScript's JSON.stringify: // // - quote and backslash are escaped; // - \b \f \n \r \t use their short escapes; // - other C0 control bytes become \u00XX with lowercase hex digits; // - everything else, including DEL, U+2028 and U+2029, is emitted as UTF-8. // // Invalid UTF-8 is replaced with U+FFFD, matching what a JSON parser would // produce for such input in a JS engine. This is what makes the event ID stable // across implementations: NIP-01 defines the serialization in terms of // JSON.stringify, not in terms of Go's encoder. func AppendJSONString(dst []byte, s string) []byte { dst = append(dst, '"') start := 0 for i := 0; i < len(s); { c := s[i] if c < utf8.RuneSelf { if c >= 0x20 && c != '"' && c != '\\' { i++ continue } dst = append(dst, s[start:i]...) switch c { case '"': dst = append(dst, '\\', '"') case '\\': dst = append(dst, '\\', '\\') case '\b': dst = append(dst, '\\', 'b') case '\f': dst = append(dst, '\\', 'f') case '\n': dst = append(dst, '\\', 'n') case '\r': dst = append(dst, '\\', 'r') case '\t': dst = append(dst, '\\', 't') default: const hexDigits = "0123456789abcdef" dst = append(dst, '\\', 'u', '0', '0', hexDigits[c>>4], hexDigits[c&0x0f]) } i++ start = i continue } r, size := utf8.DecodeRuneInString(s[i:]) if r == utf8.RuneError && size == 1 { dst = append(dst, s[start:i]...) dst = append(dst, "\uFFFD"...) i++ start = i continue } i += size } dst = append(dst, s[start:]...) return append(dst, '"') }