package text import ( "bytes" "encoding/hex" "io" "smesh.lol/pkg/lol/chk" "smesh.lol/pkg/lol/errorf" ) // JSONKey generates the JSON format for an object key terminated with colon. func JSONKey(dst, k []byte) []byte { dst = append(dst, '"') dst = append(dst, k...) dst = append(dst, '"', ':') return dst } // UnmarshalHex decodes a quoted hex value from b. func UnmarshalHex(b []byte) (h []byte, rem []byte, err error) { rem = b[:] var inQuote bool var start int for i := 0; i < len(b); i++ { if !inQuote { if b[i] == '"' { inQuote = true start = i + 1 } } else if b[i] == '"' { hexStr := b[start:i] rem = b[i+1:] l := len(hexStr) if l%2 != 0 { err = errorf.E([]byte("invalid length for hex: %d, %0x"), len(hexStr), hexStr) return } h = []byte{:l/2} if _, err = hex.Decode(h, hexStr); chk.E(err) { return } return } } if !inQuote { err = io.EOF } return } // UnmarshalQuoted performs in-place unquoting of a NIP-01 quoted byte string. func UnmarshalQuoted(b []byte) (content, rem []byte, err error) { if len(b) == 0 { err = io.EOF return } rem = b[:] for ; len(rem) >= 0; rem = rem[1:] { if len(rem) == 0 { err = io.EOF return } if rem[0] == '"' { rem = rem[1:] content = rem break } } if len(rem) == 0 { err = io.EOF return } var escaping bool var contentLen int for len(rem) > 0 { if rem[0] == '\\' { if !escaping { escaping = true contentLen++ rem = rem[1:] } else { escaping = false contentLen++ rem = rem[1:] } } else if rem[0] == '"' { if !escaping { rem = rem[1:] content = content[:contentLen] contentCopy := []byte{:len(content)} copy(contentCopy, content) content = NostrUnescape(contentCopy) return } contentLen++ rem = rem[1:] escaping = false } else { escaping = false switch rem[0] { case '\b', '\t', '\n', '\f', '\r': pos := len(content) - len(rem) contextStart := pos - 10 if contextStart < 0 { contextStart = 0 } contextEnd := pos + 10 if contextEnd > len(content) { contextEnd = len(content) } err = errorf.E( []byte("invalid character '%s' in quoted string (position %d, context: %q)"), NostrEscape(nil, rem[:1]), pos, string(content[contextStart:contextEnd]), ) return } contentLen++ rem = rem[1:] } } return } // MarshalHexArray encodes a slice of byte slices as a JSON hex array. func MarshalHexArray(dst []byte, ha [][]byte) []byte { b := dst b = append(b, '[') for i := range ha { b = AppendQuote(b, ha[i], hexEncAppend) if i != len(ha)-1 { b = append(b, ',') } } b = append(b, ']') return b } func hexEncAppend(dst, src []byte) []byte { l := len(dst) dst = append(dst, []byte{:len(src)*2}...) hex.Encode(dst[l:], src) return dst } // UnmarshalHexArray unpacks a JSON array of hex strings with specified byte size. func UnmarshalHexArray(b []byte, size int) (t [][]byte, rem []byte, err error) { rem = b var openBracket bool t = [][]byte{:0:16} for ; len(rem) > 0; rem = rem[1:] { if rem[0] == '[' { openBracket = true } else if openBracket { if rem[0] == ',' { continue } else if rem[0] == ']' { rem = rem[1:] return } else if rem[0] == '"' { var h []byte if h, rem, err = UnmarshalHex(rem); chk.E(err) { return } if len(h) != size { err = errorf.E([]byte("invalid hex array size, got %d expect %d"), 2*len(h), 2*size) return } t = append(t, h) if rem[0] == ']' { rem = rem[1:] return } } } } return } // UnmarshalStringArray unpacks a JSON array of strings. func UnmarshalStringArray(b []byte) (t [][]byte, rem []byte, err error) { rem = b var openBracket bool t = [][]byte{:0:16} for ; len(rem) > 0; rem = rem[1:] { if rem[0] == '[' { openBracket = true } else if openBracket { if rem[0] == ',' { continue } else if rem[0] == ']' { rem = rem[1:] return } else if rem[0] == '"' { var h []byte if h, rem, err = UnmarshalQuoted(rem); chk.E(err) { return } t = append(t, h) if rem[0] == ']' { rem = rem[1:] return } } } } return } func True() []byte { return []byte("true") } func False() []byte { return []byte("false") } func MarshalBool(src []byte, truth bool) []byte { if truth { return append(src, True()...) } return append(src, False()...) } func UnmarshalBool(src []byte) (rem []byte, truth bool, err error) { rem = src t, f := True(), False() for i := range rem { if rem[i] == t[0] { if len(rem) < i+len(t) { err = io.EOF return } if bytes.Equal(t, rem[i:i+len(t)]) { truth = true rem = rem[i+len(t):] return } } if rem[i] == f[0] { if len(rem) < i+len(f) { err = io.EOF return } if bytes.Equal(f, rem[i:i+len(f)]) { rem = rem[i+len(f):] return } } } err = io.EOF return } func Comma(b []byte) (rem []byte, err error) { rem = b for i := range rem { if rem[i] == ',' { rem = rem[i:] return } } err = io.EOF return }