tinyrelayfile
tinyrelay/tinyclient/open.go/

tinyclient/open.go raw
1package tinyclient
2
3import (
4 "encoding/binary"
5 "encoding/hex"
6 "net/http"
7 "net/url"
8 "regexp"
9 "strconv"
10 "strings"
11)
12
13var hexID = regexp.MustCompile(`^[0-9a-f]{64}$`)
14
15// openLink handles web+nostr links registered through the installed app.
16// It decodes the NIP-19 target and redirects to the page that shows it.
17func (a *App) openLink(writer http.ResponseWriter, request *http.Request) {
18 path, ok := nostrTargetPath(request.URL.Query().Get("target"))
19 if !ok {
20 http.Error(writer, "unsupported nostr link", http.StatusBadRequest)
21 return
22 }
23 http.Redirect(writer, request, requestPrefix(request)+path, http.StatusSeeOther)
24}
25
26// nostrTargetPath maps a nostr: or web+nostr: target to a relay page.
27func nostrTargetPath(target string) (string, bool) {
28 value := strings.TrimSpace(target)
29 for _, prefix := range []string{"web+nostr:", "nostr:"} {
30 if strings.HasPrefix(strings.ToLower(value), prefix) {
31 value = value[len(prefix):]
32 break
33 }
34 }
35 value = strings.Trim(value, "/")
36 if hexID.MatchString(value) {
37 return "/e/" + value, true
38 }
39 hrp, data, ok := bech32Decode(strings.ToLower(value))
40 if !ok {
41 return "", false
42 }
43 switch hrp {
44 case "npub":
45 if len(data) != 32 {
46 return "", false
47 }
48 return "/search?author=" + hex.EncodeToString(data), true
49 case "note":
50 if len(data) != 32 {
51 return "", false
52 }
53 return "/e/" + hex.EncodeToString(data), true
54 case "nprofile", "nevent", "naddr":
55 fields := tlvFields(data)
56 switch hrp {
57 case "nprofile":
58 if len(fields[0]) != 32 {
59 return "", false
60 }
61 return "/search?author=" + hex.EncodeToString(fields[0]), true
62 case "nevent":
63 if len(fields[0]) != 32 {
64 return "", false
65 }
66 return "/e/" + hex.EncodeToString(fields[0]), true
67 default:
68 if len(fields[2]) != 32 || len(fields[3]) != 4 {
69 return "", false
70 }
71 kind := binary.BigEndian.Uint32(fields[3])
72 return "/a/" + strconv.FormatUint(uint64(kind), 10) + ":" + hex.EncodeToString(fields[2]) + ":" + url.PathEscape(string(fields[0])), true
73 }
74 }
75 return "", false
76}
77
78// tlvFields returns the first value of each NIP-19 TLV type.
79func tlvFields(data []byte) map[byte][]byte {
80 fields := map[byte][]byte{}
81 for len(data) >= 2 {
82 kind, length := data[0], int(data[1])
83 if len(data) < 2+length {
84 break
85 }
86 if _, seen := fields[kind]; !seen {
87 fields[kind] = data[2 : 2+length]
88 }
89 data = data[2+length:]
90 }
91 return fields
92}
93
94// bech32Decode verifies a bech32 string and returns its 8-bit payload.
95func bech32Decode(value string) (string, []byte, bool) {
96 separator := strings.LastIndexByte(value, '1')
97 if separator < 1 || separator+7 > len(value) || len(value) > 5000 {
98 return "", nil, false
99 }
100 hrp, encoded := value[:separator], value[separator+1:]
101 values := make([]byte, len(encoded))
102 for i, char := range encoded {
103 position := strings.IndexRune(bech32Charset, char)
104 if position < 0 {
105 return "", nil, false
106 }
107 values[i] = byte(position)
108 }
109 if bech32Polymod(append(bech32Expand(hrp), values...)) != 1 {
110 return "", nil, false
111 }
112 data := values[:len(values)-6]
113 accumulator, bits := 0, 0
114 var result []byte
115 for _, v := range data {
116 accumulator = accumulator<<5 | int(v)
117 bits += 5
118 if bits >= 8 {
119 bits -= 8
120 result = append(result, byte(accumulator>>bits&255))
121 }
122 }
123 if bits >= 5 || accumulator<<(8-bits)&255 != 0 {
124 return "", nil, false
125 }
126 return hrp, result, true
127}
128
129func bech32Expand(hrp string) []byte {
130 expanded := make([]byte, 0, len(hrp)*2+1)
131 for _, c := range hrp {
132 expanded = append(expanded, byte(c)>>5)
133 }
134 expanded = append(expanded, 0)
135 for _, c := range hrp {
136 expanded = append(expanded, byte(c)&31)
137 }
138 return expanded
139}

About

ownerc01503e8035f
visibilitypublic
headd730679639bf

Maintainers

  • c01503e8035f owner
  • ba1b5beed5b5 agent hermes

Clone

  • https://012.run/npub1cq2s86qrtadf36eqq3epm38wmhdynrrcwxf84eldk3f94dkv7ttsrulrz5/tinyrelay.git

Refs