helpers.mx raw

   1  package text
   2  
   3  import (
   4  	"bytes"
   5  	"encoding/hex"
   6  	"io"
   7  
   8  	"smesh.lol/pkg/lol/chk"
   9  	"smesh.lol/pkg/lol/errorf"
  10  )
  11  
  12  // JSONKey generates the JSON format for an object key terminated with colon.
  13  func JSONKey(dst, k []byte) []byte {
  14  	dst = append(dst, '"')
  15  	dst = append(dst, k...)
  16  	dst = append(dst, '"', ':')
  17  	return dst
  18  }
  19  
  20  // UnmarshalHex decodes a quoted hex value from b.
  21  func UnmarshalHex(b []byte) (h []byte, rem []byte, err error) {
  22  	rem = b[:]
  23  	var inQuote bool
  24  	var start int
  25  	for i := 0; i < len(b); i++ {
  26  		if !inQuote {
  27  			if b[i] == '"' {
  28  				inQuote = true
  29  				start = i + 1
  30  			}
  31  		} else if b[i] == '"' {
  32  			hexStr := b[start:i]
  33  			rem = b[i+1:]
  34  			l := len(hexStr)
  35  			if l%2 != 0 {
  36  				err = errorf.E([]byte("invalid length for hex: %d, %0x"), len(hexStr), hexStr)
  37  				return
  38  			}
  39  			h = []byte{:l/2}
  40  			if _, err = hex.Decode(h, hexStr); chk.E(err) {
  41  				return
  42  			}
  43  			return
  44  		}
  45  	}
  46  	if !inQuote {
  47  		err = io.EOF
  48  	}
  49  	return
  50  }
  51  
  52  // UnmarshalQuoted performs in-place unquoting of a NIP-01 quoted byte string.
  53  func UnmarshalQuoted(b []byte) (content, rem []byte, err error) {
  54  	if len(b) == 0 {
  55  		err = io.EOF
  56  		return
  57  	}
  58  	rem = b[:]
  59  	for ; len(rem) >= 0; rem = rem[1:] {
  60  		if len(rem) == 0 {
  61  			err = io.EOF
  62  			return
  63  		}
  64  		if rem[0] == '"' {
  65  			rem = rem[1:]
  66  			content = rem
  67  			break
  68  		}
  69  	}
  70  	if len(rem) == 0 {
  71  		err = io.EOF
  72  		return
  73  	}
  74  	var escaping bool
  75  	var contentLen int
  76  	for len(rem) > 0 {
  77  		if rem[0] == '\\' {
  78  			if !escaping {
  79  				escaping = true
  80  				contentLen++
  81  				rem = rem[1:]
  82  			} else {
  83  				escaping = false
  84  				contentLen++
  85  				rem = rem[1:]
  86  			}
  87  		} else if rem[0] == '"' {
  88  			if !escaping {
  89  				rem = rem[1:]
  90  				content = content[:contentLen]
  91  				contentCopy := []byte{:len(content)}
  92  				copy(contentCopy, content)
  93  				content = NostrUnescape(contentCopy)
  94  				return
  95  			}
  96  			contentLen++
  97  			rem = rem[1:]
  98  			escaping = false
  99  		} else {
 100  			escaping = false
 101  			switch rem[0] {
 102  			case '\b', '\t', '\n', '\f', '\r':
 103  				pos := len(content) - len(rem)
 104  				contextStart := pos - 10
 105  				if contextStart < 0 {
 106  					contextStart = 0
 107  				}
 108  				contextEnd := pos + 10
 109  				if contextEnd > len(content) {
 110  					contextEnd = len(content)
 111  				}
 112  				err = errorf.E(
 113  					[]byte("invalid character '%s' in quoted string (position %d, context: %q)"),
 114  					NostrEscape(nil, rem[:1]), pos, string(content[contextStart:contextEnd]),
 115  				)
 116  				return
 117  			}
 118  			contentLen++
 119  			rem = rem[1:]
 120  		}
 121  	}
 122  	return
 123  }
 124  
 125  // MarshalHexArray encodes a slice of byte slices as a JSON hex array.
 126  func MarshalHexArray(dst []byte, ha [][]byte) []byte {
 127  	b := dst
 128  	b = append(b, '[')
 129  	for i := range ha {
 130  		b = AppendQuote(b, ha[i], hexEncAppend)
 131  		if i != len(ha)-1 {
 132  			b = append(b, ',')
 133  		}
 134  	}
 135  	b = append(b, ']')
 136  	return b
 137  }
 138  
 139  func hexEncAppend(dst, src []byte) []byte {
 140  	l := len(dst)
 141  	dst = append(dst, []byte{:len(src)*2}...)
 142  	hex.Encode(dst[l:], src)
 143  	return dst
 144  }
 145  
 146  // UnmarshalHexArray unpacks a JSON array of hex strings with specified byte size.
 147  func UnmarshalHexArray(b []byte, size int) (t [][]byte, rem []byte, err error) {
 148  	rem = b
 149  	var openBracket bool
 150  	t = [][]byte{:0:16}
 151  	for ; len(rem) > 0; rem = rem[1:] {
 152  		if rem[0] == '[' {
 153  			openBracket = true
 154  		} else if openBracket {
 155  			if rem[0] == ',' {
 156  				continue
 157  			} else if rem[0] == ']' {
 158  				rem = rem[1:]
 159  				return
 160  			} else if rem[0] == '"' {
 161  				var h []byte
 162  				if h, rem, err = UnmarshalHex(rem); chk.E(err) {
 163  					return
 164  				}
 165  				if len(h) != size {
 166  					err = errorf.E([]byte("invalid hex array size, got %d expect %d"), 2*len(h), 2*size)
 167  					return
 168  				}
 169  				t = append(t, h)
 170  				if rem[0] == ']' {
 171  					rem = rem[1:]
 172  					return
 173  				}
 174  			}
 175  		}
 176  	}
 177  	return
 178  }
 179  
 180  // UnmarshalStringArray unpacks a JSON array of strings.
 181  func UnmarshalStringArray(b []byte) (t [][]byte, rem []byte, err error) {
 182  	rem = b
 183  	var openBracket bool
 184  	t = [][]byte{:0:16}
 185  	for ; len(rem) > 0; rem = rem[1:] {
 186  		if rem[0] == '[' {
 187  			openBracket = true
 188  		} else if openBracket {
 189  			if rem[0] == ',' {
 190  				continue
 191  			} else if rem[0] == ']' {
 192  				rem = rem[1:]
 193  				return
 194  			} else if rem[0] == '"' {
 195  				var h []byte
 196  				if h, rem, err = UnmarshalQuoted(rem); chk.E(err) {
 197  					return
 198  				}
 199  				t = append(t, h)
 200  				if rem[0] == ']' {
 201  					rem = rem[1:]
 202  					return
 203  				}
 204  			}
 205  		}
 206  	}
 207  	return
 208  }
 209  
 210  func True() []byte  { return []byte("true") }
 211  func False() []byte { return []byte("false") }
 212  
 213  func MarshalBool(src []byte, truth bool) []byte {
 214  	if truth {
 215  		return append(src, True()...)
 216  	}
 217  	return append(src, False()...)
 218  }
 219  
 220  func UnmarshalBool(src []byte) (rem []byte, truth bool, err error) {
 221  	rem = src
 222  	t, f := True(), False()
 223  	for i := range rem {
 224  		if rem[i] == t[0] {
 225  			if len(rem) < i+len(t) {
 226  				err = io.EOF
 227  				return
 228  			}
 229  			if bytes.Equal(t, rem[i:i+len(t)]) {
 230  				truth = true
 231  				rem = rem[i+len(t):]
 232  				return
 233  			}
 234  		}
 235  		if rem[i] == f[0] {
 236  			if len(rem) < i+len(f) {
 237  				err = io.EOF
 238  				return
 239  			}
 240  			if bytes.Equal(f, rem[i:i+len(f)]) {
 241  				rem = rem[i+len(f):]
 242  				return
 243  			}
 244  		}
 245  	}
 246  	err = io.EOF
 247  	return
 248  }
 249  
 250  func Comma(b []byte) (rem []byte, err error) {
 251  	rem = b
 252  	for i := range rem {
 253  		if rem[i] == ',' {
 254  			rem = rem[i:]
 255  			return
 256  		}
 257  	}
 258  	err = io.EOF
 259  	return
 260  }
 261