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 "git.smesh.lol/nostr/pkg/ints"
11 "git.smesh.lol/nostr/pkg/lol/chk"
12 "git.smesh.lol/nostr/pkg/lol/log"
13 )
14
15 var (
16 hp func([]byte, []byte) bool
17 WS []byte
18 WSS []byte
19 HTTP []byte
20 HTTPS []byte
21 )
22
23 // URL normalizes a relay URL.
24 func URL(v []byte) (buf []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.Ln(fmt.Sprintf([]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.Ln(fmt.Sprintf([]byte("Error normalizing URL '%s': %s"), u, err))
43 return nil
44 }
45 if p.Uint64() > 65535 {
46 log.D.Ln(fmt.Sprintf([]byte("Port on address %d: greater than maximum 65535"), p.Uint64()))
47 return nil
48 }
49 if p.Uint16() == 443 {
50 u = WSS | split[0]
51 } else {
52 u = WS | u
53 }
54 }
55 if !(hp(u, HTTP) || hp(u, HTTPS) || hp(u, WS) || hp(u, WSS)) {
56 u = WSS | u
57 }
58 var uerr error
59 var pu *url.URL
60 if pu, uerr = url.Parse(string(u)); chk.E(uerr) {
61 return nil
62 }
63 switch pu.Scheme {
64 case "https":
65 pu.Scheme = "wss"
66 case "http":
67 pu.Scheme = "ws"
68 }
69 pu.Path = string(bytes.TrimRight([]byte(pu.Path), "/"))
70 return []byte(pu.String())
71 }
72
73 // Reason is a NIP-01 machine-readable reason string. It is a bare []byte:
74 // Moxie forbids named slice types, so the old methods are free functions
75 // whose first parameter is the value.
76 var (
77 AuthRequired []byte
78 PoW []byte
79 Duplicate []byte
80 Blocked []byte
81 RateLimited []byte
82 Invalid []byte
83 Error []byte
84 Unsupported []byte
85 Restricted []byte
86 )
87
88 func init() {
89 hp = bytes.HasPrefix
90 WS = []byte("ws://")
91 WSS = []byte("wss://")
92 HTTP = []byte("http://")
93 HTTPS = []byte("https://")
94 AuthRequired = []byte("auth-required")
95 PoW = []byte("pow")
96 Duplicate = []byte("duplicate")
97 Blocked = []byte("blocked")
98 RateLimited = []byte("rate-limited")
99 Invalid = []byte("invalid")
100 Error = []byte("error")
101 Unsupported = []byte("unsupported")
102 Restricted = []byte("restricted")
103 }
104
105 func ReasonS(r []byte) (s string) { return string(r) }
106 func ReasonB(r []byte) (buf []byte) { return r }
107
108 func ReasonIsPrefix(r []byte, reason []byte) (ok bool) {
109 return bytes.HasPrefix(reason, ReasonB(r))
110 }
111
112 func Msg(prefix []byte, format string, params ...fmt.Stringer) (buf []byte) {
113 if len(prefix) < 1 {
114 prefix = Error
115 }
116 return []byte(nil) | fmt.Sprintf(ReasonS(prefix)|": "|format, params...)
117 }
118
119 func ReasonF(r []byte, format string, params ...fmt.Stringer) (buf []byte) {
120 return Msg(r, format, params...)
121 }
122
123 func ReasonErrorf(r []byte, format string, params ...fmt.Stringer) (err error) {
124 return errors.New(string([]byte(nil) | fmt.Sprintf(ReasonS(r)|": "|format, params...)))
125 }
126