@amritk/nish-x86_64-linux 0.17.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +43 -25
- package/bin/nish +0 -0
- package/package.json +2 -2
- package/runtime/nish.d.ts +55 -5
- package/runtime/nish.h +231 -20
- package/runtime/nish.mjs +16 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +7 -6
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +213 -15
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +203 -0
- package/runtime/runtime-wasm.c +16 -3
- package/runtime/runtime.c +89 -23
- package/runtime/shim.mjs +71 -1
- package/scripts/build.sh +225 -20
- package/std/README.md +86 -55
- package/std/bytes.ts +163 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +245 -53
- package/std/crypto/p256.ts +1376 -42
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +9 -9
- package/std/json-tokenizer.ts +1296 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1163 -0
- package/std/json.ts +1412 -70
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +417 -55
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +231 -45
- package/std/net/http3.ts +187 -28
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-conn-params.ts +86 -16
- package/std/net/quic-listener.ts +576 -219
- package/std/net/quic-packet.ts +443 -58
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +434 -28
- package/std/net/quic.ts +922 -206
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +204 -57
- package/std/net/tls.ts +1132 -305
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/text.ts +77 -0
- package/std/threads.ts +438 -14
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/router` — a method and path router for HTTP servers, independent
|
|
3
|
+
* of the carrier: the same table answers an HTTP/1 request line and an
|
|
4
|
+
* HTTP/2 or HTTP/3 `:path`.
|
|
5
|
+
*
|
|
6
|
+
* const HOME: i32 = 0 // module constants, in the order the routes are added
|
|
7
|
+
* const USER: i32 = 1
|
|
8
|
+
* const router = new Router(new RouterLimits())
|
|
9
|
+
* if (router.add("GET", "/") !== HOME || router.add("GET", "/users/:id") !== USER) panic(...)
|
|
10
|
+
* // per request, with `params` made once beside the connection:
|
|
11
|
+
* switch (router.match(p.head, p.methodStart, p.methodEnd, p.head, p.targetStart, p.targetEnd, params)) {
|
|
12
|
+
* case HOME: ... case USER: ... case ROUTER_NOT_FOUND: ...
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* Nish has no function values, so a route is the index `add` answered, 0 for
|
|
16
|
+
* the first and one more for each after, and the program dispatches on it
|
|
17
|
+
* with `switch`, whose labels are module constants. A parameter is answered as a
|
|
18
|
+
* `[start, end)` pair of offsets into the target, never as a copy, and
|
|
19
|
+
* `routerDecode` percent-decodes one of them into a buffer the caller owns.
|
|
20
|
+
*
|
|
21
|
+
* **The table is built once; a match allocates nothing.** `add` refuses a
|
|
22
|
+
* duplicate, an ambiguous or an over-long pattern with a negative code and
|
|
23
|
+
* adds nothing. `match` walks flat `i32` arrays the table grew and writes into
|
|
24
|
+
* scratch the constructor sized, so a warmed server moves the arena by
|
|
25
|
+
* nothing per request. A `Router` is therefore not reentrant: one match at a
|
|
26
|
+
* time, which is what a connection loop does.
|
|
27
|
+
*
|
|
28
|
+
* **The rules** (`docs/wp43-http-app-layer.md` §2):
|
|
29
|
+
*
|
|
30
|
+
* - The path is the target up to its first `?` (the query is left alone) and
|
|
31
|
+
* must start with `/`: the `*` of `OPTIONS *` and the authority form of
|
|
32
|
+
* `CONNECT` match nothing, so the program answers them before it routes.
|
|
33
|
+
* - A path is split on `/` into segments; `/a/` has two, `a` and an empty one.
|
|
34
|
+
* A trailing slash is significant, and matching is on raw bytes, so `%2F`
|
|
35
|
+
* never splits a segment.
|
|
36
|
+
* - A pattern segment is a literal, a parameter `:name` (one non-empty
|
|
37
|
+
* segment), or `*` as the last segment (the rest of the path, possibly
|
|
38
|
+
* empty, as one more pair). Precedence is literal, then parameter, then
|
|
39
|
+
* wildcard, segment by segment from the left; a branch that fails further
|
|
40
|
+
* down falls back to the next one. Every edge consumes one segment, so a
|
|
41
|
+
* lookup visits each node of the table at most once.
|
|
42
|
+
* - A path that matches only routes of other methods answers
|
|
43
|
+
* `ROUTER_METHOD_NOT_ALLOWED`, so a server can tell 405 from 404.
|
|
44
|
+
*
|
|
45
|
+
* Private helpers share the importing program's flat symbol namespace
|
|
46
|
+
* (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/** `add`: the pattern is empty, does not start with `/`, or misuses `:`, `*`, `%`, `?` or a byte outside `!` to `~`. */
|
|
50
|
+
export const ROUTER_BAD_PATTERN: i32 = -1
|
|
51
|
+
/** `add`: the method is not a token of 1 to 32 bytes. */
|
|
52
|
+
export const ROUTER_BAD_METHOD: i32 = -2
|
|
53
|
+
/** `add`: the pattern is over `RouterLimits`' bytes, segments or parameters. */
|
|
54
|
+
export const ROUTER_TOO_LONG: i32 = -3
|
|
55
|
+
/** `add`: a `*` that is not the last segment, where how much it takes would be a guess. */
|
|
56
|
+
export const ROUTER_AMBIGUOUS: i32 = -4
|
|
57
|
+
/** `add`: the method already has a route of this shape, whatever its parameters are called. */
|
|
58
|
+
export const ROUTER_DUPLICATE: i32 = -5
|
|
59
|
+
/** `add`: the table holds `RouterLimits.maxRoutes` routes or `maxMethods` methods already. */
|
|
60
|
+
export const ROUTER_FULL: i32 = -6
|
|
61
|
+
/** `match`: no route's pattern matches the path, or the target is not `/`-rooted. */
|
|
62
|
+
export const ROUTER_NOT_FOUND: i32 = -7
|
|
63
|
+
/** `match`: the path matches only routes of other methods. */
|
|
64
|
+
export const ROUTER_METHOD_NOT_ALLOWED: i32 = -8
|
|
65
|
+
/** `match`: the route matched, but its parameters need more than `params` holds; nothing was written. */
|
|
66
|
+
export const ROUTER_TOO_MANY_PARAMS: i32 = -9
|
|
67
|
+
/** `routerDecode`: a `%` not followed by two hex digits. */
|
|
68
|
+
export const ROUTER_BAD_ESCAPE: i32 = -10
|
|
69
|
+
/** `routerDecode`: the decoded bytes do not fit `out`; nothing was written. */
|
|
70
|
+
export const ROUTER_NO_ROOM: i32 = -11
|
|
71
|
+
|
|
72
|
+
/** The longest method `add` takes, as `nish/net/http1`'s `HTTP1_MAX_METHOD`. */
|
|
73
|
+
const ROUTER_MAX_METHOD: i32 = 32
|
|
74
|
+
/** No node or route: an `i32` spelled once, so that `--number-mode f64` does not read a bare `-1` as a double. */
|
|
75
|
+
const ROUTER_NONE: i32 = -1
|
|
76
|
+
/** A pattern segment's kind, in `Router.add`'s scratch. */
|
|
77
|
+
const ROUTER_LITERAL: i32 = 0
|
|
78
|
+
const ROUTER_PARAM: i32 = 1
|
|
79
|
+
const ROUTER_WILD: i32 = 2
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* What a `Router` holds and refuses past. Each field has a default a server
|
|
83
|
+
* can use as it is; the constructor panics on a value no table could work with.
|
|
84
|
+
*/
|
|
85
|
+
export class RouterLimits {
|
|
86
|
+
/** The longest pattern, in bytes (1 to 65536). */
|
|
87
|
+
maxPatternBytes: i32 = 1024
|
|
88
|
+
/** The most segments in one pattern (1 to 256): the deepest a route can be. */
|
|
89
|
+
maxSegments: i32 = 32
|
|
90
|
+
/** The most parameters in one pattern, a final `*` counted (0 to 256). */
|
|
91
|
+
maxParams: i32 = 16
|
|
92
|
+
/** The most routes in the table (1 to 1048576). */
|
|
93
|
+
maxRoutes: i32 = 4096
|
|
94
|
+
/** The most distinct methods (1 to 64). */
|
|
95
|
+
maxMethods: i32 = 16
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A route table: a tree whose edges are path segments, kept in flat arrays.
|
|
100
|
+
* Node 0 is the root, the point before the first segment; a node at depth `d`
|
|
101
|
+
* has consumed `d` segments.
|
|
102
|
+
*/
|
|
103
|
+
export class Router {
|
|
104
|
+
// References, then 32-bit fields: the order the layout packs without padding.
|
|
105
|
+
/** Per node: its first literal child, or -1; siblings chain through `nextSibling`. */
|
|
106
|
+
firstChild: i32[]
|
|
107
|
+
/** Per node: the next literal child of its parent, or -1. */
|
|
108
|
+
nextSibling: i32[]
|
|
109
|
+
/** Per node: its literal label, `labels[labelStart .. labelEnd)`; empty for a parameter or `*` node. */
|
|
110
|
+
labelStart: i32[]
|
|
111
|
+
labelEnd: i32[]
|
|
112
|
+
/** Per node: its parameter child, or -1; one per node, since names take no part in matching. */
|
|
113
|
+
paramChild: i32[]
|
|
114
|
+
/** Per node: its `*` child, or -1. */
|
|
115
|
+
wildChild: i32[]
|
|
116
|
+
/** Per node: the first route ending there, or -1; routes chain through `endpointNext`. */
|
|
117
|
+
firstEndpoint: i32[]
|
|
118
|
+
/** Every literal label, back to back. */
|
|
119
|
+
labels: u8[]
|
|
120
|
+
/** Per route: its method's index into `methodStart`, the next route ending at its node, and its parameter pairs. */
|
|
121
|
+
routeMethod: i32[]
|
|
122
|
+
endpointNext: i32[]
|
|
123
|
+
routeParams: i32[]
|
|
124
|
+
/** The methods the table knows, `methodBytes[methodStart[m] .. methodEnd[m])`, compared exactly (RFC 9110 §9.1). */
|
|
125
|
+
methodBytes: u8[]
|
|
126
|
+
methodStart: i32[]
|
|
127
|
+
methodEnd: i32[]
|
|
128
|
+
/** `match`'s scratch, sized by the constructor: each segment's span, then the pairs found so far. */
|
|
129
|
+
segStart: i32[]
|
|
130
|
+
segEnd: i32[]
|
|
131
|
+
found: i32[]
|
|
132
|
+
/** `add`'s scratch: each pattern segment's kind and span in the pattern. */
|
|
133
|
+
patKind: i32[]
|
|
134
|
+
patStart: i32[]
|
|
135
|
+
patEnd: i32[]
|
|
136
|
+
/** The limits, copied out of the `RouterLimits` the table was made with. */
|
|
137
|
+
maxPatternBytes: i32 = 0
|
|
138
|
+
maxSegments: i32 = 0
|
|
139
|
+
maxParams: i32 = 0
|
|
140
|
+
maxRoutes: i32 = 0
|
|
141
|
+
maxMethods: i32 = 0
|
|
142
|
+
/** The most segments any route has: a path is split no further than one past it. */
|
|
143
|
+
deepest: i32 = 0
|
|
144
|
+
/** During a match: how many segments the path has, where it ends, the method's index, and the pairs in `found`. */
|
|
145
|
+
segments: i32 = 0
|
|
146
|
+
pathEnd: i32 = 0
|
|
147
|
+
wanted: i32 = 0
|
|
148
|
+
foundCount: i32 = 0
|
|
149
|
+
/** During a match: whether a route of another method ended at the path's end. */
|
|
150
|
+
otherMethod: boolean = false
|
|
151
|
+
/** The nodes the last `match` visited: never more than `nodeCount()`, which the tests pin. */
|
|
152
|
+
visited: i32 = 0
|
|
153
|
+
|
|
154
|
+
/** An empty table. Panics, naming the field, on a limit outside the range `RouterLimits` gives it. */
|
|
155
|
+
constructor(limits: RouterLimits) {
|
|
156
|
+
routerLimit("maxPatternBytes", limits.maxPatternBytes, 1, 65536)
|
|
157
|
+
routerLimit("maxSegments", limits.maxSegments, 1, 256)
|
|
158
|
+
routerLimit("maxParams", limits.maxParams, 0, 256)
|
|
159
|
+
routerLimit("maxRoutes", limits.maxRoutes, 1, 1048576)
|
|
160
|
+
routerLimit("maxMethods", limits.maxMethods, 1, 64)
|
|
161
|
+
this.maxPatternBytes = limits.maxPatternBytes
|
|
162
|
+
this.maxSegments = limits.maxSegments
|
|
163
|
+
this.maxParams = limits.maxParams
|
|
164
|
+
this.maxRoutes = limits.maxRoutes
|
|
165
|
+
this.maxMethods = limits.maxMethods
|
|
166
|
+
this.firstChild = [-1]
|
|
167
|
+
this.nextSibling = [-1]
|
|
168
|
+
this.labelStart = [0]
|
|
169
|
+
this.labelEnd = [0]
|
|
170
|
+
this.paramChild = [-1]
|
|
171
|
+
this.wildChild = [-1]
|
|
172
|
+
this.firstEndpoint = [-1]
|
|
173
|
+
this.labels = []
|
|
174
|
+
this.routeMethod = []
|
|
175
|
+
this.endpointNext = []
|
|
176
|
+
this.routeParams = []
|
|
177
|
+
this.methodBytes = []
|
|
178
|
+
this.methodStart = []
|
|
179
|
+
this.methodEnd = []
|
|
180
|
+
this.segStart = new Array<i32>(limits.maxSegments + 1)
|
|
181
|
+
this.segEnd = new Array<i32>(limits.maxSegments + 1)
|
|
182
|
+
this.found = new Array<i32>(2 * limits.maxParams)
|
|
183
|
+
this.patKind = new Array<i32>(limits.maxSegments)
|
|
184
|
+
this.patStart = new Array<i32>(limits.maxSegments)
|
|
185
|
+
this.patEnd = new Array<i32>(limits.maxSegments)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** How many routes the table holds; the next `add` answers this index. */
|
|
189
|
+
routes(): i32 {
|
|
190
|
+
return toI32(this.routeMethod.length)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** How many nodes the tree has, the root included: the most one `match` visits. */
|
|
194
|
+
nodeCount(): i32 {
|
|
195
|
+
return toI32(this.firstChild.length)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** How many `[start, end)` pairs `route` answers: its parameters, and one more for a final `*`. */
|
|
199
|
+
paramCount(route: i32): i32 {
|
|
200
|
+
if (route < 0 || route >= this.routes()) {
|
|
201
|
+
panic(`Router.paramCount: route ${route} is not in a table of ${this.routes()}`)
|
|
202
|
+
}
|
|
203
|
+
return this.routeParams[route]
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** How many nodes the last `match` visited. */
|
|
207
|
+
lastVisited(): i32 {
|
|
208
|
+
return this.visited
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Adds the route `method pattern` and answers its index, counting from 0, or
|
|
213
|
+
* a negative `ROUTER_*` code; a refused pattern adds nothing. Whether a
|
|
214
|
+
* pattern is refused never depends on the order routes were added in.
|
|
215
|
+
*/
|
|
216
|
+
add(method: string, pattern: string): i32 {
|
|
217
|
+
const methodLength: i32 = toI32(method.length)
|
|
218
|
+
if (methodLength < 1 || methodLength > ROUTER_MAX_METHOD) {
|
|
219
|
+
return ROUTER_BAD_METHOD
|
|
220
|
+
}
|
|
221
|
+
for (let k: i32 = 0; k < methodLength; k++) {
|
|
222
|
+
if (!routerTchar(toI32(method.charCodeAt(k)))) {
|
|
223
|
+
return ROUTER_BAD_METHOD
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const length: i32 = toI32(pattern.length)
|
|
227
|
+
if (length > this.maxPatternBytes) {
|
|
228
|
+
return ROUTER_TOO_LONG
|
|
229
|
+
}
|
|
230
|
+
if (length === 0 || toI32(pattern.charCodeAt(0)) !== 47) {
|
|
231
|
+
return ROUTER_BAD_PATTERN
|
|
232
|
+
}
|
|
233
|
+
const count: i32 = this.splitPattern(pattern)
|
|
234
|
+
if (count < 0) {
|
|
235
|
+
return count
|
|
236
|
+
}
|
|
237
|
+
let params: i32 = 0
|
|
238
|
+
for (let k: i32 = 0; k < count; k++) {
|
|
239
|
+
if (this.patKind[k] !== ROUTER_LITERAL) {
|
|
240
|
+
params += 1
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (params > this.maxParams) {
|
|
244
|
+
return ROUTER_TOO_LONG
|
|
245
|
+
}
|
|
246
|
+
// The walk that finds where the pattern ends, adding nothing yet: a
|
|
247
|
+
// duplicate is a pattern whose every node already exists.
|
|
248
|
+
let node: i32 = 0
|
|
249
|
+
let depth: i32 = 0
|
|
250
|
+
while (depth < count && node >= 0) {
|
|
251
|
+
node = this.childFor(node, pattern, depth)
|
|
252
|
+
depth += 1
|
|
253
|
+
}
|
|
254
|
+
const known: i32 = this.methodIndex(method)
|
|
255
|
+
if (node >= 0 && known >= 0) {
|
|
256
|
+
for (let e: i32 = this.firstEndpoint[node]; e >= 0; e = this.endpointNext[e]) {
|
|
257
|
+
if (this.routeMethod[e] === known) {
|
|
258
|
+
return ROUTER_DUPLICATE
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (this.routes() >= this.maxRoutes || (known < 0 && toI32(this.methodStart.length) >= this.maxMethods)) {
|
|
263
|
+
return ROUTER_FULL
|
|
264
|
+
}
|
|
265
|
+
let methodId: i32 = known
|
|
266
|
+
if (methodId < 0) {
|
|
267
|
+
methodId = toI32(this.methodStart.length)
|
|
268
|
+
this.methodStart.push(toI32(this.methodBytes.length))
|
|
269
|
+
for (let k: i32 = 0; k < methodLength; k++) {
|
|
270
|
+
this.methodBytes.push(toU8(toI32(method.charCodeAt(k))))
|
|
271
|
+
}
|
|
272
|
+
this.methodEnd.push(toI32(this.methodBytes.length))
|
|
273
|
+
}
|
|
274
|
+
node = 0
|
|
275
|
+
for (let k: i32 = 0; k < count; k++) {
|
|
276
|
+
const next: i32 = this.childFor(node, pattern, k)
|
|
277
|
+
node = next >= 0 ? next : this.addChild(node, pattern, k)
|
|
278
|
+
}
|
|
279
|
+
const route: i32 = this.routes()
|
|
280
|
+
this.routeMethod.push(methodId)
|
|
281
|
+
this.routeParams.push(params)
|
|
282
|
+
this.endpointNext.push(this.firstEndpoint[node])
|
|
283
|
+
this.firstEndpoint[node] = route
|
|
284
|
+
if (count > this.deepest) {
|
|
285
|
+
this.deepest = count
|
|
286
|
+
}
|
|
287
|
+
return route
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Splits `pattern`, which starts with `/`, into `patKind`/`patStart`/`patEnd`
|
|
292
|
+
* and answers how many segments it has, or the code that refuses it.
|
|
293
|
+
*/
|
|
294
|
+
splitPattern(pattern: string): i32 {
|
|
295
|
+
const length: i32 = toI32(pattern.length)
|
|
296
|
+
let count: i32 = 0
|
|
297
|
+
let start: i32 = 1
|
|
298
|
+
for (let i: i32 = 1; i <= length; i++) {
|
|
299
|
+
const c: i32 = i < length ? toI32(pattern.charCodeAt(i)) : 47
|
|
300
|
+
if (c < 33 || c > 126 || c === 37 || c === 63) {
|
|
301
|
+
return ROUTER_BAD_PATTERN
|
|
302
|
+
}
|
|
303
|
+
if (c !== 47) {
|
|
304
|
+
continue
|
|
305
|
+
}
|
|
306
|
+
if (count === this.maxSegments) {
|
|
307
|
+
return ROUTER_TOO_LONG
|
|
308
|
+
}
|
|
309
|
+
if (count > 0 && this.patKind[count - 1] === ROUTER_WILD) {
|
|
310
|
+
return ROUTER_AMBIGUOUS
|
|
311
|
+
}
|
|
312
|
+
const kind: i32 = routerSegmentKind(pattern, start, i)
|
|
313
|
+
if (kind < 0) {
|
|
314
|
+
return kind
|
|
315
|
+
}
|
|
316
|
+
this.patKind[count] = kind
|
|
317
|
+
this.patStart[count] = kind === ROUTER_PARAM ? start + 1 : start
|
|
318
|
+
this.patEnd[count] = i
|
|
319
|
+
if (kind === ROUTER_PARAM) {
|
|
320
|
+
for (let k: i32 = 0; k < count; k++) {
|
|
321
|
+
if (
|
|
322
|
+
this.patKind[k] === ROUTER_PARAM &&
|
|
323
|
+
routerSameName(pattern, this.patStart[k], this.patEnd[k], start + 1, i)
|
|
324
|
+
) {
|
|
325
|
+
return ROUTER_BAD_PATTERN
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
count += 1
|
|
330
|
+
start = i + 1
|
|
331
|
+
}
|
|
332
|
+
return count
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/** The child of `node` that pattern segment `k` leads to, or -1. */
|
|
336
|
+
childFor(node: i32, pattern: string, k: i32): i32 {
|
|
337
|
+
const kind: i32 = this.patKind[k]
|
|
338
|
+
if (kind === ROUTER_PARAM) {
|
|
339
|
+
return this.paramChild[node]
|
|
340
|
+
}
|
|
341
|
+
if (kind === ROUTER_WILD) {
|
|
342
|
+
return this.wildChild[node]
|
|
343
|
+
}
|
|
344
|
+
const start: i32 = this.patStart[k]
|
|
345
|
+
const length: i32 = this.patEnd[k] - start
|
|
346
|
+
for (let c: i32 = this.firstChild[node]; c >= 0; c = this.nextSibling[c]) {
|
|
347
|
+
const at: i32 = this.labelStart[c]
|
|
348
|
+
if (this.labelEnd[c] - at === length && routerLabelIsText(this.labels, at, pattern, start, length)) {
|
|
349
|
+
return c
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return -1
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Adds the child of `node` that pattern segment `k` leads to, and answers it. */
|
|
356
|
+
addChild(node: i32, pattern: string, k: i32): i32 {
|
|
357
|
+
const child: i32 = this.nodeCount()
|
|
358
|
+
const kind: i32 = this.patKind[k]
|
|
359
|
+
const at: i32 = toI32(this.labels.length)
|
|
360
|
+
this.firstChild.push(ROUTER_NONE)
|
|
361
|
+
this.nextSibling.push(ROUTER_NONE)
|
|
362
|
+
this.paramChild.push(ROUTER_NONE)
|
|
363
|
+
this.wildChild.push(ROUTER_NONE)
|
|
364
|
+
this.firstEndpoint.push(ROUTER_NONE)
|
|
365
|
+
this.labelStart.push(at)
|
|
366
|
+
if (kind === ROUTER_LITERAL) {
|
|
367
|
+
const n: i32 = toI32(pattern.length)
|
|
368
|
+
for (let i: i32 = this.patStart[k]; i >= 0 && i < this.patEnd[k] && i < n; i++) {
|
|
369
|
+
this.labels.push(toU8(toI32(pattern.charCodeAt(i))))
|
|
370
|
+
}
|
|
371
|
+
this.nextSibling[child] = this.firstChild[node]
|
|
372
|
+
this.firstChild[node] = child
|
|
373
|
+
} else if (kind === ROUTER_PARAM) {
|
|
374
|
+
this.paramChild[node] = child
|
|
375
|
+
} else {
|
|
376
|
+
this.wildChild[node] = child
|
|
377
|
+
}
|
|
378
|
+
this.labelEnd.push(toI32(this.labels.length))
|
|
379
|
+
return child
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/** The index of `method` among the methods the table knows, or -1. */
|
|
383
|
+
methodIndex(method: string): i32 {
|
|
384
|
+
const length: i32 = toI32(method.length)
|
|
385
|
+
for (let m: i32 = 0; m < toI32(this.methodStart.length); m++) {
|
|
386
|
+
const at: i32 = this.methodStart[m]
|
|
387
|
+
if (this.methodEnd[m] - at === length && routerLabelIsText(this.methodBytes, at, method, 0, length)) {
|
|
388
|
+
return m
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return -1
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* The route `method[mStart .. mEnd)` and `target[tStart .. tEnd)` reach, or
|
|
396
|
+
* a negative `ROUTER_*` code. On a route, its parameters are written to
|
|
397
|
+
* `params` as `[start, end)` offsets into `target`, two slots each, in the
|
|
398
|
+
* pattern's order, and a final `*` as one more pair; on a refusal `params`
|
|
399
|
+
* is untouched. Panics on a range outside its array.
|
|
400
|
+
*/
|
|
401
|
+
match(method: u8[], mStart: i32, mEnd: i32, target: u8[], tStart: i32, tEnd: i32, params: i32[]): i32 {
|
|
402
|
+
if (mStart < 0 || mEnd < mStart || mEnd > toI32(method.length)) {
|
|
403
|
+
panic(`Router.match: the method range ${mStart} to ${mEnd} is outside its ${method.length} bytes`)
|
|
404
|
+
}
|
|
405
|
+
if (tStart < 0 || tEnd < tStart || tEnd > toI32(target.length)) {
|
|
406
|
+
panic(`Router.match: the target range ${tStart} to ${tEnd} is outside its ${target.length} bytes`)
|
|
407
|
+
}
|
|
408
|
+
this.visited = 0
|
|
409
|
+
if (tStart === tEnd || toI32(target[tStart]) !== 47) {
|
|
410
|
+
return ROUTER_NOT_FOUND
|
|
411
|
+
}
|
|
412
|
+
// One pass over the path: where it ends, and each segment's span up to one
|
|
413
|
+
// past the deepest route, which is as far as any edge can reach.
|
|
414
|
+
let end: i32 = tStart
|
|
415
|
+
while (end < tEnd && toI32(target[end]) !== 63) {
|
|
416
|
+
end += 1
|
|
417
|
+
}
|
|
418
|
+
const keep: i32 = this.deepest + 1
|
|
419
|
+
let segments: i32 = 0
|
|
420
|
+
let start: i32 = tStart + 1
|
|
421
|
+
for (let i: i32 = tStart + 1; i <= end; i++) {
|
|
422
|
+
if (i >= 0 && i < end && i < toI32(target.length) && toI32(target[i]) !== 47) {
|
|
423
|
+
continue
|
|
424
|
+
}
|
|
425
|
+
if (segments < keep) {
|
|
426
|
+
this.segStart[segments] = start
|
|
427
|
+
this.segEnd[segments] = i
|
|
428
|
+
}
|
|
429
|
+
segments += 1
|
|
430
|
+
start = i + 1
|
|
431
|
+
}
|
|
432
|
+
this.segments = segments
|
|
433
|
+
this.pathEnd = end
|
|
434
|
+
this.wanted = -1
|
|
435
|
+
const methodLength: i32 = mEnd - mStart
|
|
436
|
+
for (let m: i32 = 0; m < toI32(this.methodStart.length) && this.wanted < 0; m++) {
|
|
437
|
+
const at: i32 = this.methodStart[m]
|
|
438
|
+
if (
|
|
439
|
+
this.methodEnd[m] - at === methodLength &&
|
|
440
|
+
routerSameBytes(this.methodBytes, at, method, mStart, methodLength)
|
|
441
|
+
) {
|
|
442
|
+
this.wanted = m
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
this.foundCount = 0
|
|
446
|
+
this.otherMethod = false
|
|
447
|
+
const route: i32 = this.walk(target, toI32(0), toI32(0))
|
|
448
|
+
if (route < 0) {
|
|
449
|
+
return this.otherMethod ? ROUTER_METHOD_NOT_ALLOWED : ROUTER_NOT_FOUND
|
|
450
|
+
}
|
|
451
|
+
const pairs: i32 = this.foundCount
|
|
452
|
+
if (2 * pairs > toI32(params.length)) {
|
|
453
|
+
return ROUTER_TOO_MANY_PARAMS
|
|
454
|
+
}
|
|
455
|
+
for (let k: i32 = 0; k < 2 * pairs && k < toI32(params.length); k++) {
|
|
456
|
+
params[k] = this.found[k]
|
|
457
|
+
}
|
|
458
|
+
return route
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* The route below `node`, which has consumed `depth` segments, for the
|
|
463
|
+
* wanted method, or -1. Literal, then parameter, then `*`: the first branch
|
|
464
|
+
* that reaches a route wins, and a branch that does not is left with the
|
|
465
|
+
* pairs it pushed popped.
|
|
466
|
+
*/
|
|
467
|
+
walk(target: u8[], node: i32, depth: i32): i32 {
|
|
468
|
+
this.visited += 1
|
|
469
|
+
if (depth === this.segments) {
|
|
470
|
+
return this.endpointFor(node)
|
|
471
|
+
}
|
|
472
|
+
const start: i32 = this.segStart[depth]
|
|
473
|
+
const end: i32 = this.segEnd[depth]
|
|
474
|
+
const length: i32 = end - start
|
|
475
|
+
for (let c: i32 = this.firstChild[node]; c >= 0; c = this.nextSibling[c]) {
|
|
476
|
+
const at: i32 = this.labelStart[c]
|
|
477
|
+
if (this.labelEnd[c] - at === length && routerSameBytes(this.labels, at, target, start, length)) {
|
|
478
|
+
const route: i32 = this.walk(target, c, depth + 1)
|
|
479
|
+
if (route >= 0) {
|
|
480
|
+
return route
|
|
481
|
+
}
|
|
482
|
+
// Labels under one node are distinct, so no other literal child can match.
|
|
483
|
+
break
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
const param: i32 = this.paramChild[node]
|
|
487
|
+
if (param >= 0 && length > 0) {
|
|
488
|
+
const top: i32 = this.foundCount
|
|
489
|
+
this.found[2 * top] = start
|
|
490
|
+
this.found[2 * top + 1] = end
|
|
491
|
+
this.foundCount = top + 1
|
|
492
|
+
const route: i32 = this.walk(target, param, depth + 1)
|
|
493
|
+
if (route >= 0) {
|
|
494
|
+
return route
|
|
495
|
+
}
|
|
496
|
+
this.foundCount = top
|
|
497
|
+
}
|
|
498
|
+
const wild: i32 = this.wildChild[node]
|
|
499
|
+
if (wild >= 0) {
|
|
500
|
+
this.visited += 1
|
|
501
|
+
const route: i32 = this.endpointFor(wild)
|
|
502
|
+
if (route >= 0) {
|
|
503
|
+
const top: i32 = this.foundCount
|
|
504
|
+
this.found[2 * top] = start
|
|
505
|
+
this.found[2 * top + 1] = this.pathEnd
|
|
506
|
+
this.foundCount = top + 1
|
|
507
|
+
return route
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return -1
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/** The route of the wanted method ending at `node`, or -1, noting a route of another method there. */
|
|
514
|
+
endpointFor(node: i32): i32 {
|
|
515
|
+
for (let e: i32 = this.firstEndpoint[node]; e >= 0; e = this.endpointNext[e]) {
|
|
516
|
+
if (this.routeMethod[e] === this.wanted) {
|
|
517
|
+
return e
|
|
518
|
+
}
|
|
519
|
+
this.otherMethod = true
|
|
520
|
+
}
|
|
521
|
+
return -1
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Percent-decodes `target[start .. end)`, one range `match` answered, into
|
|
527
|
+
* `out` from 0, and answers the decoded length or a negative `ROUTER_*` code;
|
|
528
|
+
* on a code nothing is written. `%2F` becomes `/` here, inside the one
|
|
529
|
+
* parameter, because `match` split the path before anything was decoded. `+`
|
|
530
|
+
* stays `+`: a space spelled `+` is the query's form encoding, not the path's.
|
|
531
|
+
* The output may hold any byte, NUL and `/` included. Panics on a range
|
|
532
|
+
* outside `target`.
|
|
533
|
+
*/
|
|
534
|
+
export const routerDecode = (target: u8[], start: i32, end: i32, out: u8[]): i32 => {
|
|
535
|
+
if (start < 0 || end < start || end > toI32(target.length)) {
|
|
536
|
+
panic(`routerDecode: the range ${start} to ${end} is outside its ${target.length} bytes`)
|
|
537
|
+
}
|
|
538
|
+
let length: i32 = 0
|
|
539
|
+
let i: i32 = start
|
|
540
|
+
while (i < end) {
|
|
541
|
+
if (toI32(target[i]) === 37) {
|
|
542
|
+
if (i + 2 >= end || routerHex(toI32(target[i + 1])) < 0 || routerHex(toI32(target[i + 2])) < 0) {
|
|
543
|
+
return ROUTER_BAD_ESCAPE
|
|
544
|
+
}
|
|
545
|
+
i += 3
|
|
546
|
+
} else {
|
|
547
|
+
i += 1
|
|
548
|
+
}
|
|
549
|
+
length += 1
|
|
550
|
+
}
|
|
551
|
+
if (length > toI32(out.length)) {
|
|
552
|
+
return ROUTER_NO_ROOM
|
|
553
|
+
}
|
|
554
|
+
let at: i32 = 0
|
|
555
|
+
i = start
|
|
556
|
+
const room: i32 = toI32(out.length)
|
|
557
|
+
const n: i32 = toI32(target.length)
|
|
558
|
+
while (i >= 0 && i < end && i < n && at >= 0 && at < room) {
|
|
559
|
+
const c: i32 = toI32(target[i])
|
|
560
|
+
if (c === 37 && i + 2 < n) {
|
|
561
|
+
out[at] = toU8(routerHex(toI32(target[i + 1])) * 16 + routerHex(toI32(target[i + 2])))
|
|
562
|
+
i += 3
|
|
563
|
+
} else {
|
|
564
|
+
out[at] = target[i]
|
|
565
|
+
i += 1
|
|
566
|
+
}
|
|
567
|
+
at += 1
|
|
568
|
+
}
|
|
569
|
+
return length
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
/** Panics, naming `RouterLimits.<field>`, unless `value` is in `low .. high`. */
|
|
573
|
+
const routerLimit = (field: string, value: i32, low: i32, high: i32): void => {
|
|
574
|
+
if (value < low || value > high) {
|
|
575
|
+
panic(`RouterLimits.${field}: ${value} is outside ${low} to ${high}`)
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/** Whether `c` is a token character (RFC 9110 §5.6.2), what a method is made of. */
|
|
580
|
+
const routerTchar = (c: i32): boolean => {
|
|
581
|
+
if ((c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122)) {
|
|
582
|
+
return true
|
|
583
|
+
}
|
|
584
|
+
return (
|
|
585
|
+
c === 33 ||
|
|
586
|
+
c === 35 ||
|
|
587
|
+
c === 36 ||
|
|
588
|
+
c === 37 ||
|
|
589
|
+
c === 38 ||
|
|
590
|
+
c === 39 ||
|
|
591
|
+
c === 42 ||
|
|
592
|
+
c === 43 ||
|
|
593
|
+
c === 45 ||
|
|
594
|
+
c === 46 ||
|
|
595
|
+
c === 94 ||
|
|
596
|
+
c === 95 ||
|
|
597
|
+
c === 96 ||
|
|
598
|
+
c === 124 ||
|
|
599
|
+
c === 126
|
|
600
|
+
)
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* The kind of the pattern segment `pattern[start .. end)`, or
|
|
605
|
+
* `ROUTER_BAD_PATTERN`: a parameter is `:` and a name of letters, digits and
|
|
606
|
+
* `_`; `*` is the whole segment or nowhere in it; anything else is a literal.
|
|
607
|
+
*/
|
|
608
|
+
const routerSegmentKind = (pattern: string, start: i32, end: i32): i32 => {
|
|
609
|
+
if (end - start === 1 && toI32(pattern.charCodeAt(start)) === 42) {
|
|
610
|
+
return ROUTER_WILD
|
|
611
|
+
}
|
|
612
|
+
const param: boolean = end > start && toI32(pattern.charCodeAt(start)) === 58
|
|
613
|
+
if (param && end - start === 1) {
|
|
614
|
+
return ROUTER_BAD_PATTERN
|
|
615
|
+
}
|
|
616
|
+
const n: i32 = toI32(pattern.length)
|
|
617
|
+
for (let i: i32 = start; i >= 0 && i < end && i < n; i++) {
|
|
618
|
+
const c: i32 = toI32(pattern.charCodeAt(i))
|
|
619
|
+
if (c === 42) {
|
|
620
|
+
return ROUTER_BAD_PATTERN
|
|
621
|
+
}
|
|
622
|
+
const nameByte: boolean =
|
|
623
|
+
(c >= 48 && c <= 57) || (c >= 65 && c <= 90) || (c >= 97 && c <= 122) || c === 95
|
|
624
|
+
if (param && i > start && !nameByte) {
|
|
625
|
+
return ROUTER_BAD_PATTERN
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return param ? ROUTER_PARAM : ROUTER_LITERAL
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/** Whether the names `pattern[a .. aEnd)` and `pattern[b .. bEnd)` are the same. */
|
|
632
|
+
const routerSameName = (pattern: string, a: i32, aEnd: i32, b: i32, bEnd: i32): boolean => {
|
|
633
|
+
if (aEnd - a !== bEnd - b) {
|
|
634
|
+
return false
|
|
635
|
+
}
|
|
636
|
+
for (let k: i32 = 0; k < aEnd - a; k++) {
|
|
637
|
+
if (pattern.charCodeAt(a + k) !== pattern.charCodeAt(b + k)) {
|
|
638
|
+
return false
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return true
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/** Whether `bytes[at .. at + length)` spells `text[from .. from + length)`. */
|
|
645
|
+
const routerLabelIsText = (bytes: u8[], at: i32, text: string, from: i32, length: i32): boolean => {
|
|
646
|
+
for (let k: i32 = 0; k < length; k++) {
|
|
647
|
+
if (toI32(bytes[at + k]) !== toI32(text.charCodeAt(from + k))) {
|
|
648
|
+
return false
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
return true
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/** Whether `a[at .. at + length)` and `b[from .. from + length)` hold the same bytes. */
|
|
655
|
+
const routerSameBytes = (a: u8[], at: i32, b: u8[], from: i32, length: i32): boolean => {
|
|
656
|
+
for (let k: i32 = 0; k < length; k++) {
|
|
657
|
+
if (a[at + k] !== b[from + k]) {
|
|
658
|
+
return false
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
return true
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/** The value of the hex digit `c`, in either case, or -1. */
|
|
665
|
+
const routerHex = (c: i32): i32 => {
|
|
666
|
+
if (c >= 48 && c <= 57) {
|
|
667
|
+
return c - 48
|
|
668
|
+
}
|
|
669
|
+
if (c >= 65 && c <= 70) {
|
|
670
|
+
return c - 55
|
|
671
|
+
}
|
|
672
|
+
if (c >= 97 && c <= 102) {
|
|
673
|
+
return c - 87
|
|
674
|
+
}
|
|
675
|
+
return -1
|
|
676
|
+
}
|