130 lines
3.0 KiB
Go
130 lines
3.0 KiB
Go
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
|
|
}
|