You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
129 lines
3.0 KiB
129 lines
3.0 KiB
package text |
|
|
|
// NostrEscape for JSON encoding according to RFC8259. |
|
// |
|
// This is the efficient implementation based on the NIP-01 specification: |
|
// |
|
// To prevent implementation differences from creating a different event ID for |
|
// the same event, the following rules MUST be followed while serializing: |
|
// |
|
// No whitespace, line breaks or other unnecessary formatting should be included |
|
// in the output JSON. No characters except the following should be escaped, and |
|
// instead should be included verbatim: |
|
// |
|
// - A line break, 0x0A, as \n |
|
// - A double quote, 0x22, as \" |
|
// - A backslash, 0x5C, as \\ |
|
// - A carriage return, 0x0D, as \r |
|
// - A tab character, 0x09, as \t |
|
// - A backspace, 0x08, as \b |
|
// - A form feed, 0x0C, as \f |
|
// |
|
// UTF-8 should be used for encoding. |
|
func NostrEscape(dst, src []byte) []byte { |
|
l := len(src) |
|
for i := 0; i < l; i++ { |
|
c := src[i] |
|
switch { |
|
case c == '"': |
|
dst = append(dst, '\\', '"') |
|
case c == '\\': |
|
// if i+1 < l && src[i+1] == 'u' || i+1 < l && src[i+1] == '/' { |
|
if i+1 < l && src[i+1] == 'u' { |
|
dst = append(dst, '\\') |
|
} else { |
|
dst = append(dst, '\\', '\\') |
|
} |
|
case c == '\b': |
|
dst = append(dst, '\\', 'b') |
|
case c == '\t': |
|
dst = append(dst, '\\', 't') |
|
case c == '\n': |
|
dst = append(dst, '\\', 'n') |
|
case c == '\f': |
|
dst = append(dst, '\\', 'f') |
|
case c == '\r': |
|
dst = append(dst, '\\', 'r') |
|
default: |
|
dst = append(dst, c) |
|
} |
|
} |
|
return dst |
|
} |
|
|
|
// NostrUnescape reverses the operation of NostrEscape except instead of |
|
// appending it to the provided slice, it rewrites it, eliminating a memory |
|
// copy. Keep in mind that the original JSON will be mangled by this operation, |
|
// but the resultant slices will cost zero allocations. |
|
func NostrUnescape(dst []byte) (b []byte) { |
|
var r, w int |
|
for ; r < len(dst); r++ { |
|
if dst[r] == '\\' { |
|
r++ |
|
c := dst[r] |
|
switch { |
|
|
|
// nip-01 specifies the following single letter C-style escapes for |
|
// control codes under 0x20. |
|
// |
|
// no others are specified but must be preserved, so only these can |
|
// be safely decoded at runtime as they must be re-encoded when |
|
// marshalled. |
|
case c == '"': |
|
dst[w] = '"' |
|
w++ |
|
case c == '\\': |
|
dst[w] = '\\' |
|
w++ |
|
case c == 'b': |
|
dst[w] = '\b' |
|
w++ |
|
case c == 't': |
|
dst[w] = '\t' |
|
w++ |
|
case c == 'n': |
|
dst[w] = '\n' |
|
w++ |
|
case c == 'f': |
|
dst[w] = '\f' |
|
w++ |
|
case c == 'r': |
|
dst[w] = '\r' |
|
w++ |
|
|
|
// special cases for non-nip-01 specified json escapes (must be |
|
// preserved for ID generation). |
|
case c == 'u': |
|
dst[w] = '\\' |
|
w++ |
|
dst[w] = 'u' |
|
w++ |
|
case c == '/': |
|
dst[w] = '\\' |
|
w++ |
|
dst[w] = '/' |
|
w++ |
|
|
|
// special case for octal escapes (must be preserved for ID |
|
// generation). |
|
case c >= '0' && c <= '9': |
|
dst[w] = '\\' |
|
w++ |
|
dst[w] = c |
|
w++ |
|
|
|
// anything else after a reverse solidus just preserve it. |
|
default: |
|
dst[w] = dst[r] |
|
w++ |
|
dst[w] = c |
|
w++ |
|
} |
|
} else { |
|
dst[w] = dst[r] |
|
w++ |
|
} |
|
} |
|
b = dst[:w] |
|
return |
|
}
|
|
|