// Package normalize provides URL and message normalization for Nostr. package normalize import ( "bytes" "errors" "fmt" "net/url" "smesh.lol/pkg/nostr/ints" "smesh.lol/pkg/lol/chk" "smesh.lol/pkg/lol/log" ) var ( hp = bytes.HasPrefix WS = []byte("ws://") WSS = []byte("wss://") HTTP = []byte("http://") HTTPS = []byte("https://") ) // URL normalizes a relay URL. func URL(v []byte) []byte { u := []byte{:len(v)} copy(u, v) if len(u) == 0 { return nil } u = bytes.TrimSpace(u) u = bytes.ToLower(u) if bytes.Contains(u, []byte(":")) && !(hp(u, HTTP) || hp(u, HTTPS) || hp(u, WS) || hp(u, WSS)) { split := bytes.Split(u, []byte(":")) if len(split) != 2 { log.D.F([]byte("Error: more than one ':' in URL: '%s'"), u) return nil } p := ints.New(0) _, err := p.Unmarshal(split[1]) if chk.E(err) { log.D.F([]byte("Error normalizing URL '%s': %s"), u, err) return nil } if p.Uint64() > 65535 { log.D.F([]byte("Port on address %d: greater than maximum 65535"), p.Uint64()) return nil } if p.Uint16() == 443 { u = append(WSS, split[0]...) } else { u = append(WS, u...) } } if !(hp(u, HTTP) || hp(u, HTTPS) || hp(u, WS) || hp(u, WSS)) { u = append(WSS, u...) } var err error var p *url.URL if p, err = url.Parse(string(u)); chk.E(err) { return nil } switch p.Scheme { case "https": p.Scheme = "wss" case "http": p.Scheme = "ws" } p.Path = string(bytes.TrimRight([]byte(p.Path), "/")) return []byte(p.String()) } type Reason []byte var ( AuthRequired = Reason("auth-required") PoW = Reason("pow") Duplicate = Reason("duplicate") Blocked = Reason("blocked") RateLimited = Reason("rate-limited") Invalid = Reason("invalid") Error = Reason("error") Unsupported = Reason("unsupported") Restricted = Reason("restricted") ) func (r Reason) S() string { return string(r) } func (r Reason) B() []byte { return r } func (r Reason) IsPrefix(reason []byte) bool { return bytes.HasPrefix(reason, r.B()) } func Msg(prefix Reason, format string, params ...any) []byte { if len(prefix) < 1 { prefix = Error } return append([]byte(nil), fmt.Sprintf(prefix.S()+": "+format, params...)...) } func (r Reason) F(format string, params ...any) []byte { return Msg(r, format, params...) } func (r Reason) Errorf(format string, params ...any) error { return errors.New(string(append([]byte(nil), fmt.Sprintf(r.S()+": "+format, params...)...))) }