-
-
Notifications
You must be signed in to change notification settings - Fork 347
Expand file tree
/
Copy pathreservedtopic.go
More file actions
98 lines (81 loc) · 3.01 KB
/
Copy pathreservedtopic.go
File metadata and controls
98 lines (81 loc) · 3.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package mercure
import (
"strings"
wurl "github.com/nlnwa/whatwg-url/url"
)
// reservedNamespaceBase is the synthetic absolute URL against which topics are
// resolved to test whether they address the reserved hub namespace. Only the
// path component of the result matters and the hub path is fixed
// (defaultHubURL), so the host is a placeholder: ".invalid" is reserved by
// RFC 6761 §6.4 and cannot collide with a real topic.
const reservedNamespaceBase = "https://mercure.invalid" + defaultHubURL
// addressesReservedNamespace reports whether the topic, resolved as a URL
// reference against the hub's URL, has a path component equal to
// "/.well-known/mercure" or nested under it, regardless of scheme or
// authority. The protocol reserves this namespace for resources generated by
// the hub itself (subscription events), so publishers must not be able to
// address it.
//
// The reserved path is defaultHubURL, the same constant the hub uses to mint
// subscription-event topics (see Subscriber.getSubscriptions in subscriber.go).
// If the hub path ever becomes configurable, both must derive from the
// configured value together, or a publisher could forge subscription events.
//
// Resolution uses the WHATWG URL algorithm — the same canonicalization
// applied when matching topics against URL patterns — so inputs that net/url
// leaves intact (backslashes in special schemes, mixed-case percent triplets)
// cannot pass this test yet still match subscribers' patterns.
// Percent-encoded octets for unreserved characters are decoded before
// comparison, as required by the specification.
func addressesReservedNamespace(topic string) bool {
resolved, err := wurl.ParseRef(reservedNamespaceBase, topic)
if err != nil {
// Not interpretable as a URL: cannot resolve into the reserved namespace.
return false
}
path := decodeUnreservedPercentEncoding(resolved.Pathname())
return path == defaultHubURL || strings.HasPrefix(path, defaultHubURL+"/")
}
// decodeUnreservedPercentEncoding decodes %XX octets corresponding to RFC 3986
// unreserved characters (ALPHA / DIGIT / "-" / "." / "_" / "~"): the
// percent-encoding normalization of RFC 3986 §6.2.2.2.
func decodeUnreservedPercentEncoding(s string) string {
i := strings.IndexByte(s, '%')
if i == -1 {
return s
}
var b strings.Builder
b.Grow(len(s))
b.WriteString(s[:i])
for ; i < len(s); i++ {
if s[i] == '%' && i+2 < len(s) {
hi, okHi := unhex(s[i+1])
lo, okLo := unhex(s[i+2])
if okHi && okLo {
if c := hi<<4 | lo; isUnreservedByte(c) {
b.WriteByte(c)
i += 2
continue
}
}
}
b.WriteByte(s[i])
}
return b.String()
}
func unhex(c byte) (byte, bool) {
switch {
case '0' <= c && c <= '9':
return c - '0', true
case 'a' <= c && c <= 'f':
return c - 'a' + 10, true
case 'A' <= c && c <= 'F':
return c - 'A' + 10, true
default:
return 0, false
}
}
func isUnreservedByte(c byte) bool {
return 'A' <= c && c <= 'Z' || 'a' <= c && c <= 'z' ||
'0' <= c && c <= '9' || c == '-' || c == '.' || c == '_' || c == '~'
}