normalize.mx raw
1 // Package normalize provides URL and message normalization for Nostr.
2 package normalize
3
4 import (
5 "bytes"
6 "errors"
7 "fmt"
8 "net/url"
9
10 "smesh.lol/pkg/nostr/ints"
11 "smesh.lol/pkg/lol/chk"
12 "smesh.lol/pkg/lol/log"
13 )
14
15 var (
16 hp = bytes.HasPrefix
17 WS = []byte("ws://")
18 WSS = []byte("wss://")
19 HTTP = []byte("http://")
20 HTTPS = []byte("https://")
21 )
22
23 // URL normalizes a relay URL.
24 func URL(v []byte) []byte {
25 u := []byte{:len(v)}
26 copy(u, v)
27 if len(u) == 0 {
28 return nil
29 }
30 u = bytes.TrimSpace(u)
31 u = bytes.ToLower(u)
32 if bytes.Contains(u, []byte(":")) &&
33 !(hp(u, HTTP) || hp(u, HTTPS) || hp(u, WS) || hp(u, WSS)) {
34 split := bytes.Split(u, []byte(":"))
35 if len(split) != 2 {
36 log.D.F([]byte("Error: more than one ':' in URL: '%s'"), u)
37 return nil
38 }
39 p := ints.New(0)
40 _, err := p.Unmarshal(split[1])
41 if chk.E(err) {
42 log.D.F([]byte("Error normalizing URL '%s': %s"), u, err)
43 return nil
44 }
45 if p.Uint64() > 65535 {
46 log.D.F([]byte("Port on address %d: greater than maximum 65535"), p.Uint64())
47 return nil
48 }
49 if p.Uint16() == 443 {
50 u = append(WSS, split[0]...)
51 } else {
52 u = append(WS, u...)
53 }
54 }
55 if !(hp(u, HTTP) || hp(u, HTTPS) || hp(u, WS) || hp(u, WSS)) {
56 u = append(WSS, u...)
57 }
58 var err error
59 var p *url.URL
60 if p, err = url.Parse(string(u)); chk.E(err) {
61 return nil
62 }
63 switch p.Scheme {
64 case "https":
65 p.Scheme = "wss"
66 case "http":
67 p.Scheme = "ws"
68 }
69 p.Path = string(bytes.TrimRight([]byte(p.Path), "/"))
70 return []byte(p.String())
71 }
72
73 type Reason []byte
74
75 var (
76 AuthRequired = Reason("auth-required")
77 PoW = Reason("pow")
78 Duplicate = Reason("duplicate")
79 Blocked = Reason("blocked")
80 RateLimited = Reason("rate-limited")
81 Invalid = Reason("invalid")
82 Error = Reason("error")
83 Unsupported = Reason("unsupported")
84 Restricted = Reason("restricted")
85 )
86
87 func (r Reason) S() string { return string(r) }
88 func (r Reason) B() []byte { return r }
89
90 func (r Reason) IsPrefix(reason []byte) bool {
91 return bytes.HasPrefix(reason, r.B())
92 }
93
94 func Msg(prefix Reason, format string, params ...any) []byte {
95 if len(prefix) < 1 {
96 prefix = Error
97 }
98 return append([]byte(nil), fmt.Sprintf(prefix.S()+": "+format, params...)...)
99 }
100
101 func (r Reason) F(format string, params ...any) []byte {
102 return Msg(r, format, params...)
103 }
104
105 func (r Reason) Errorf(format string, params ...any) error {
106 return errors.New(string(append([]byte(nil), fmt.Sprintf(r.S()+": "+format, params...)...)))
107 }
108