// Package normalize provides URL and message normalization for Nostr. package normalize import ( "bytes" "errors" "fmt" "net/url" "git.smesh.lol/nostr/pkg/ints" "git.smesh.lol/nostr/pkg/lol/chk" "git.smesh.lol/nostr/pkg/lol/log" ) var ( hp func([]byte, []byte) bool WS []byte WSS []byte HTTP []byte HTTPS []byte ) // URL normalizes a relay URL. func URL(v []byte) (buf []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.Ln(fmt.Sprintf([]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.Ln(fmt.Sprintf([]byte("Error normalizing URL '%s': %s"), u, err)) return nil } if p.Uint64() > 65535 { log.D.Ln(fmt.Sprintf([]byte("Port on address %d: greater than maximum 65535"), p.Uint64())) return nil } if p.Uint16() == 443 { u = WSS | split[0] } else { u = WS | u } } if !(hp(u, HTTP) || hp(u, HTTPS) || hp(u, WS) || hp(u, WSS)) { u = WSS | u } var uerr error var pu *url.URL if pu, uerr = url.Parse(string(u)); chk.E(uerr) { return nil } switch pu.Scheme { case "https": pu.Scheme = "wss" case "http": pu.Scheme = "ws" } pu.Path = string(bytes.TrimRight([]byte(pu.Path), "/")) return []byte(pu.String()) } // Reason is a NIP-01 machine-readable reason string. It is a bare []byte: // Moxie forbids named slice types, so the old methods are free functions // whose first parameter is the value. var ( AuthRequired []byte PoW []byte Duplicate []byte Blocked []byte RateLimited []byte Invalid []byte Error []byte Unsupported []byte Restricted []byte ) func init() { hp = bytes.HasPrefix WS = []byte("ws://") WSS = []byte("wss://") HTTP = []byte("http://") HTTPS = []byte("https://") AuthRequired = []byte("auth-required") PoW = []byte("pow") Duplicate = []byte("duplicate") Blocked = []byte("blocked") RateLimited = []byte("rate-limited") Invalid = []byte("invalid") Error = []byte("error") Unsupported = []byte("unsupported") Restricted = []byte("restricted") } func ReasonS(r []byte) (s string) { return string(r) } func ReasonB(r []byte) (buf []byte) { return r } func ReasonIsPrefix(r []byte, reason []byte) (ok bool) { return bytes.HasPrefix(reason, ReasonB(r)) } func Msg(prefix []byte, format string, params ...fmt.Stringer) (buf []byte) { if len(prefix) < 1 { prefix = Error } return []byte(nil) | fmt.Sprintf(ReasonS(prefix)|": "|format, params...) } func ReasonF(r []byte, format string, params ...fmt.Stringer) (buf []byte) { return Msg(r, format, params...) } func ReasonErrorf(r []byte, format string, params ...fmt.Stringer) (err error) { return errors.New(string([]byte(nil) | fmt.Sprintf(ReasonS(r)|": "|format, params...))) }