@amritk/nish-x86_64-linux 0.16.0 → 0.18.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 -34
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +117 -8
- package/runtime/nish.h +50 -13
- package/runtime/nish.mjs +71 -7
- package/runtime/runtime-host.c +43 -24
- package/runtime/runtime-net.c +57 -10
- package/runtime/runtime-os.c +62 -20
- package/runtime/runtime-simd.c +203 -0
- package/runtime/runtime-wasm.c +12 -0
- package/runtime/runtime.c +66 -18
- package/runtime/shim.mjs +114 -5
- package/scripts/build.sh +14 -12
- package/std/README.md +168 -36
- package/std/crypto/aes.ts +51 -8
- package/std/crypto/base64url.ts +25 -12
- package/std/crypto/chacha20poly1305.ts +43 -29
- package/std/crypto/ct.ts +6 -1
- package/std/crypto/hkdf.ts +364 -3
- package/std/crypto/hmac.ts +307 -7
- package/std/crypto/p256.ts +618 -34
- package/std/crypto/sha1.ts +147 -0
- package/std/crypto/sha256.ts +3 -2
- package/std/crypto/x25519.ts +88 -36
- package/std/crypto/x509.ts +130 -47
- package/std/json.ts +297 -41
- package/std/net/hpack.ts +1094 -0
- package/std/net/http-fields.ts +604 -0
- package/std/net/http1-server.ts +1389 -0
- package/std/net/http1.ts +1492 -0
- package/std/net/http2-frame.ts +745 -0
- package/std/net/http2-tls.ts +216 -0
- package/std/net/http2.ts +1433 -0
- package/std/net/http3-frame.ts +457 -0
- package/std/net/http3-server.ts +911 -0
- package/std/net/http3.ts +2145 -0
- package/std/net/qpack.ts +1163 -0
- package/std/net/quic-conn-ack.ts +206 -0
- package/std/net/quic-conn-cid.ts +425 -0
- package/std/net/quic-conn-params.ts +462 -0
- package/std/net/quic-datagram.ts +130 -0
- package/std/net/quic-frame.ts +1206 -0
- package/std/net/quic-listener.ts +929 -0
- package/std/net/quic-packet.ts +1698 -0
- package/std/net/quic-recovery.ts +849 -0
- package/std/net/quic-stream.ts +1609 -0
- package/std/net/quic.ts +3578 -0
- package/std/net/tls/codec.ts +1089 -0
- package/std/net/tls/record-server.ts +1173 -0
- package/std/net/tls/record.ts +621 -0
- package/std/net/tls/schedule.ts +429 -0
- package/std/net/tls-tcp.ts +414 -0
- package/std/net/tls.ts +1437 -0
- package/std/net/websocket.ts +823 -0
- package/std/net/webtransport.ts +1334 -0
- package/std/secret.ts +62 -0
- package/std/text.ts +77 -0
|
@@ -0,0 +1,604 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/http-fields` — the header model HTTP/2 and HTTP/3 share: what a
|
|
3
|
+
* field name and a field value may hold, the pseudo-header fields and the
|
|
4
|
+
* order and count they come in, the connection-specific fields neither
|
|
5
|
+
* version carries, and the shapes of a request, a response and a trailer
|
|
6
|
+
* section.
|
|
7
|
+
*
|
|
8
|
+
* import { HttpFields, HTTP_FIELDS_OK } from "nish/net/http-fields";
|
|
9
|
+
*
|
|
10
|
+
* const request = new HttpFields();
|
|
11
|
+
* if (request.readRequest(decoder.names, decoder.values, extendedConnect) === HTTP_FIELDS_OK) {
|
|
12
|
+
* // request.method, request.path, request.get("content-type"), ...
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* It is version-neutral on purpose: RFC 9113 §8 and RFC 9114 §4 state the
|
|
16
|
+
* same rules for a field section, and each version answers a section that
|
|
17
|
+
* breaks one in its own words — HTTP/2 with a stream error of type
|
|
18
|
+
* PROTOCOL_ERROR (RFC 9113 §8.1.1), HTTP/3 with H3_MESSAGE_ERROR (RFC 9114
|
|
19
|
+
* §4.1.2). So every check here answers one of the `HTTP_FIELDS_*` codes and
|
|
20
|
+
* nothing more, and nothing here knows about frames, streams or a codec.
|
|
21
|
+
*
|
|
22
|
+
* **What it refuses** (RFC 9110 §5, RFC 9113 §8.2 and §8.3, RFC 9114 §4.2
|
|
23
|
+
* and §4.3, RFC 8441 §4):
|
|
24
|
+
*
|
|
25
|
+
* - a field name that is empty, holds a character outside RFC 9110's `tchar`,
|
|
26
|
+
* or holds an uppercase letter — both versions send names in lowercase and
|
|
27
|
+
* call an uppercase one malformed (`HTTP_FIELDS_BAD_NAME`);
|
|
28
|
+
* - a field value with a NUL, CR or LF anywhere, or a space or tab at either
|
|
29
|
+
* end (`HTTP_FIELDS_BAD_VALUE`) — the first three are what lets a value end
|
|
30
|
+
* a line once the message is turned back into HTTP/1.1;
|
|
31
|
+
* - `connection`, `proxy-connection`, `keep-alive`, `transfer-encoding`,
|
|
32
|
+
* `upgrade`, and `te` with any value but `trailers`
|
|
33
|
+
* (`HTTP_FIELDS_CONNECTION_SPECIFIC`);
|
|
34
|
+
* - a pseudo-header that is not one of the five a request has or the one a
|
|
35
|
+
* response has (`HTTP_FIELDS_PSEUDO_UNKNOWN`), one that appears twice
|
|
36
|
+
* (`_PSEUDO_REPEATED`), one after a regular field (`_PSEUDO_AFTER_FIELD`),
|
|
37
|
+
* one the shape does not allow — `:status` in a request, `:path` in a
|
|
38
|
+
* plain CONNECT, any in a trailer section, `:protocol` where extended
|
|
39
|
+
* CONNECT was not enabled or with a method but CONNECT
|
|
40
|
+
* (`_PSEUDO_FORBIDDEN`) — and a required one that is absent
|
|
41
|
+
* (`_PSEUDO_MISSING`);
|
|
42
|
+
* - a pseudo-header value of the wrong shape: a `:method` or `:protocol`
|
|
43
|
+
* that is not a token, a `:scheme` that is not RFC 3986's, an empty
|
|
44
|
+
* `:path` or one that starts with neither `/` nor is OPTIONS's `*`, an
|
|
45
|
+
* `:authority` with userinfo, a `:status` that is not three digits from
|
|
46
|
+
* 100 to 599 (`HTTP_FIELDS_BAD_PSEUDO_VALUE`);
|
|
47
|
+
* - a `host` field that names another authority than `:authority`, letters
|
|
48
|
+
* compared without case (`HTTP_FIELDS_AUTHORITY_MISMATCH`);
|
|
49
|
+
* - a `content-length` that is not one run of at most eighteen digits, or two
|
|
50
|
+
* that disagree (`HTTP_FIELDS_BAD_CONTENT_LENGTH`). Matching it against
|
|
51
|
+
* the body is the version's job, since only it sees the body.
|
|
52
|
+
*
|
|
53
|
+
* Names and values are octets (`u8[]`), as HPACK and QPACK answer them. A
|
|
54
|
+
* section that passes keeps references to the caller's arrays, not copies, so
|
|
55
|
+
* it is valid for as long as they are.
|
|
56
|
+
*
|
|
57
|
+
* Private helpers share the importing program's flat symbol namespace
|
|
58
|
+
* (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
|
|
59
|
+
*
|
|
60
|
+
* Written from RFC 9110, RFC 9113, RFC 9114 and RFC 8441, not ported from
|
|
61
|
+
* another implementation.
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
/** A field section that has the shape asked for. */
|
|
65
|
+
export const HTTP_FIELDS_OK: i32 = 0
|
|
66
|
+
|
|
67
|
+
/** A field name that is empty, not a token, or not in lowercase. */
|
|
68
|
+
export const HTTP_FIELDS_BAD_NAME: i32 = -1
|
|
69
|
+
|
|
70
|
+
/** A field value with a NUL, CR or LF, or whitespace at either end. */
|
|
71
|
+
export const HTTP_FIELDS_BAD_VALUE: i32 = -2
|
|
72
|
+
|
|
73
|
+
/** A connection-specific field (RFC 9113 §8.2.2, RFC 9114 §4.2). */
|
|
74
|
+
export const HTTP_FIELDS_CONNECTION_SPECIFIC: i32 = -3
|
|
75
|
+
|
|
76
|
+
/** A pseudo-header that neither a request nor a response defines. */
|
|
77
|
+
export const HTTP_FIELDS_PSEUDO_UNKNOWN: i32 = -4
|
|
78
|
+
|
|
79
|
+
/** A pseudo-header that appears more than once. */
|
|
80
|
+
export const HTTP_FIELDS_PSEUDO_REPEATED: i32 = -5
|
|
81
|
+
|
|
82
|
+
/** A pseudo-header after a regular field. */
|
|
83
|
+
export const HTTP_FIELDS_PSEUDO_AFTER_FIELD: i32 = -6
|
|
84
|
+
|
|
85
|
+
/** A pseudo-header the shape requires and the section does not have. */
|
|
86
|
+
export const HTTP_FIELDS_PSEUDO_MISSING: i32 = -7
|
|
87
|
+
|
|
88
|
+
/** A pseudo-header the shape does not allow. */
|
|
89
|
+
export const HTTP_FIELDS_PSEUDO_FORBIDDEN: i32 = -8
|
|
90
|
+
|
|
91
|
+
/** A pseudo-header whose value does not have its field's syntax. */
|
|
92
|
+
export const HTTP_FIELDS_BAD_PSEUDO_VALUE: i32 = -9
|
|
93
|
+
|
|
94
|
+
/** A `host` field that names another authority than `:authority`. */
|
|
95
|
+
export const HTTP_FIELDS_AUTHORITY_MISMATCH: i32 = -10
|
|
96
|
+
|
|
97
|
+
/** A `content-length` that is not a run of digits, or two that disagree. */
|
|
98
|
+
export const HTTP_FIELDS_BAD_CONTENT_LENGTH: i32 = -11
|
|
99
|
+
|
|
100
|
+
/** `contentLength` when the section has no `content-length`. */
|
|
101
|
+
export const HTTP_FIELDS_NO_LENGTH: i64 = -1
|
|
102
|
+
|
|
103
|
+
/** The pseudo-headers, as bits of a mask while a section is read. */
|
|
104
|
+
const HTTP_FIELDS_METHOD: i32 = 1
|
|
105
|
+
const HTTP_FIELDS_SCHEME: i32 = 2
|
|
106
|
+
const HTTP_FIELDS_AUTHORITY: i32 = 4
|
|
107
|
+
const HTTP_FIELDS_PATH: i32 = 8
|
|
108
|
+
const HTTP_FIELDS_PROTOCOL: i32 = 16
|
|
109
|
+
const HTTP_FIELDS_STATUS: i32 = 32
|
|
110
|
+
|
|
111
|
+
/** The longest `content-length` read: eighteen digits stay inside an `i64`. */
|
|
112
|
+
const HTTP_FIELDS_MAX_LENGTH_DIGITS: i32 = 18
|
|
113
|
+
|
|
114
|
+
/** A typed zero, so that a bare literal does not become an `f64` under `--number-mode f64`. */
|
|
115
|
+
const HTTP_FIELDS_ZERO: i32 = 0
|
|
116
|
+
|
|
117
|
+
/** Whether `c` is a `tchar` of RFC 9110 §5.6.2, uppercase letters left out. */
|
|
118
|
+
const httpFieldsLowerTchar = (c: i32): boolean => {
|
|
119
|
+
if ((c >= 97 && c <= 122) || (c >= 48 && c <= 57)) {
|
|
120
|
+
return true
|
|
121
|
+
}
|
|
122
|
+
switch (c) {
|
|
123
|
+
case 33: // !
|
|
124
|
+
case 35: // #
|
|
125
|
+
case 36: // $
|
|
126
|
+
case 37: // %
|
|
127
|
+
case 38: // &
|
|
128
|
+
case 39: // '
|
|
129
|
+
case 42: // *
|
|
130
|
+
case 43: // +
|
|
131
|
+
case 45: // -
|
|
132
|
+
case 46: // .
|
|
133
|
+
case 94: // ^
|
|
134
|
+
case 95: // _
|
|
135
|
+
case 96: // `
|
|
136
|
+
case 124: // |
|
|
137
|
+
case 126: // ~
|
|
138
|
+
return true
|
|
139
|
+
default:
|
|
140
|
+
return false
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Whether `bytes` is a non-empty token, with uppercase letters in it only when `upper`. */
|
|
145
|
+
const httpFieldsAllTchar = (bytes: u8[], upper: boolean): boolean => {
|
|
146
|
+
if (toI32(bytes.length) === 0) {
|
|
147
|
+
return false
|
|
148
|
+
}
|
|
149
|
+
for (const b of bytes) {
|
|
150
|
+
const c: i32 = toI32(b)
|
|
151
|
+
if (!httpFieldsLowerTchar(c) && !(upper && c >= 65 && c <= 90)) {
|
|
152
|
+
return false
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return true
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Whether `name` may be sent as a regular field name in HTTP/2 or HTTP/3: a
|
|
160
|
+
* non-empty token in lowercase (RFC 9110 §5.1, RFC 9113 §8.2.1, RFC 9114
|
|
161
|
+
* §4.2). A pseudo-header's name starts with a colon and is not one.
|
|
162
|
+
*/
|
|
163
|
+
export const httpFieldNameValid = (name: u8[]): boolean => httpFieldsAllTchar(name, false)
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Whether `value` may be sent as a field value: no NUL, CR or LF, and no
|
|
167
|
+
* space or tab as its first or last octet (RFC 9113 §8.2.1, RFC 9114 §4.2).
|
|
168
|
+
* Other octets, obs-text included, pass as they are.
|
|
169
|
+
*/
|
|
170
|
+
export const httpFieldValueValid = (value: u8[]): boolean => {
|
|
171
|
+
const n: i32 = toI32(value.length)
|
|
172
|
+
for (const b of value) {
|
|
173
|
+
const c: i32 = toI32(b)
|
|
174
|
+
if (c === 0 || c === 10 || c === 13) {
|
|
175
|
+
return false
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
if (n === 0) {
|
|
179
|
+
return true
|
|
180
|
+
}
|
|
181
|
+
const first: i32 = toI32(value[0])
|
|
182
|
+
const last: i32 = toI32(value[n - 1])
|
|
183
|
+
return first !== 32 && first !== 9 && last !== 32 && last !== 9
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Whether the octets of `bytes` spell `text` exactly. */
|
|
187
|
+
export const httpFieldIs = (bytes: u8[], text: string): boolean => {
|
|
188
|
+
const n: i32 = toI32(bytes.length)
|
|
189
|
+
if (n !== toI32(text.length)) {
|
|
190
|
+
return false
|
|
191
|
+
}
|
|
192
|
+
for (let k: i32 = 0; k < n && k < toI32(bytes.length) && k < toI32(text.length); k++) {
|
|
193
|
+
if (toI32(bytes[k]) !== toI32(text.charCodeAt(k))) {
|
|
194
|
+
return false
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return true
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** The octets of `text`, one a character: what a field name or value is sent as. */
|
|
201
|
+
export const httpFieldBytes = (text: string): u8[] => {
|
|
202
|
+
const out: u8[] = []
|
|
203
|
+
const n: i32 = toI32(text.length)
|
|
204
|
+
for (let k: i32 = 0; k < n; k++) {
|
|
205
|
+
out.push(toU8(text.charCodeAt(k)))
|
|
206
|
+
}
|
|
207
|
+
return out
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Whether a field named `name` with `value` is connection-specific, which
|
|
212
|
+
* HTTP/2 and HTTP/3 refuse in a message (RFC 9113 §8.2.2, RFC 9114 §4.2):
|
|
213
|
+
* the fields RFC 9110 §7.6.1 names, `keep-alive`, `proxy-connection`,
|
|
214
|
+
* `transfer-encoding` and `upgrade`, and `te` with any value but
|
|
215
|
+
* `trailers`.
|
|
216
|
+
*/
|
|
217
|
+
export const httpFieldConnectionSpecific = (name: u8[], value: u8[]): boolean => {
|
|
218
|
+
if (httpFieldIs(name, "te")) {
|
|
219
|
+
return !httpFieldIs(value, "trailers")
|
|
220
|
+
}
|
|
221
|
+
return (
|
|
222
|
+
httpFieldIs(name, "connection") ||
|
|
223
|
+
httpFieldIs(name, "proxy-connection") ||
|
|
224
|
+
httpFieldIs(name, "keep-alive") ||
|
|
225
|
+
httpFieldIs(name, "transfer-encoding") ||
|
|
226
|
+
httpFieldIs(name, "upgrade")
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Whether a field named `name` carries a credential an encoder should never
|
|
232
|
+
* put in a compression table, here or at any intermediary: `authorization`,
|
|
233
|
+
* `proxy-authorization`, `cookie` and `set-cookie` (RFC 7541 §7.1.3, RFC 9204
|
|
234
|
+
* §7.1.3). A table entry can be probed by a peer that controls part of what
|
|
235
|
+
* is compressed beside it, so a secret sent indexed can leak through the
|
|
236
|
+
* compressed length (CRIME's shape).
|
|
237
|
+
*/
|
|
238
|
+
export const httpFieldSensitive = (name: u8[]): boolean =>
|
|
239
|
+
httpFieldIs(name, "authorization") ||
|
|
240
|
+
httpFieldIs(name, "proxy-authorization") ||
|
|
241
|
+
httpFieldIs(name, "cookie") ||
|
|
242
|
+
httpFieldIs(name, "set-cookie")
|
|
243
|
+
|
|
244
|
+
/** An ASCII letter in lowercase, any other octet as it is. */
|
|
245
|
+
const httpFieldsLower = (c: i32): i32 => (c >= 65 && c <= 90 ? c + 32 : c)
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Whether `bytes` and `other` hold the same octets, ASCII letters compared
|
|
249
|
+
* without case, as a host name is (RFC 3986 §3.2.2).
|
|
250
|
+
*/
|
|
251
|
+
const httpFieldsSameHost = (bytes: u8[], other: u8[]): boolean => {
|
|
252
|
+
const n: i32 = toI32(bytes.length)
|
|
253
|
+
if (n !== toI32(other.length)) {
|
|
254
|
+
return false
|
|
255
|
+
}
|
|
256
|
+
for (let k: i32 = 0; k < n && k < toI32(bytes.length) && k < toI32(other.length); k++) {
|
|
257
|
+
if (httpFieldsLower(toI32(bytes[k])) !== httpFieldsLower(toI32(other[k]))) {
|
|
258
|
+
return false
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return true
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** The pseudo-header bit `name` is, 0 for a regular field, -1 for a name that starts with a colon and is none of them. */
|
|
265
|
+
const httpFieldsPseudoOf = (name: u8[]): i32 => {
|
|
266
|
+
if (toI32(name.length) === 0 || toI32(name[0]) !== 58) {
|
|
267
|
+
return 0
|
|
268
|
+
}
|
|
269
|
+
if (httpFieldIs(name, ":method")) {
|
|
270
|
+
return HTTP_FIELDS_METHOD
|
|
271
|
+
}
|
|
272
|
+
if (httpFieldIs(name, ":scheme")) {
|
|
273
|
+
return HTTP_FIELDS_SCHEME
|
|
274
|
+
}
|
|
275
|
+
if (httpFieldIs(name, ":authority")) {
|
|
276
|
+
return HTTP_FIELDS_AUTHORITY
|
|
277
|
+
}
|
|
278
|
+
if (httpFieldIs(name, ":path")) {
|
|
279
|
+
return HTTP_FIELDS_PATH
|
|
280
|
+
}
|
|
281
|
+
if (httpFieldIs(name, ":protocol")) {
|
|
282
|
+
return HTTP_FIELDS_PROTOCOL
|
|
283
|
+
}
|
|
284
|
+
if (httpFieldIs(name, ":status")) {
|
|
285
|
+
return HTTP_FIELDS_STATUS
|
|
286
|
+
}
|
|
287
|
+
return -1
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Whether `scheme` has RFC 3986 §3.1's syntax: a letter, then letters, digits, `+`, `-` and `.`. */
|
|
291
|
+
const httpFieldsSchemeValid = (scheme: u8[]): boolean => {
|
|
292
|
+
const n: i32 = toI32(scheme.length)
|
|
293
|
+
for (let k: i32 = 0; k < n && k < toI32(scheme.length); k++) {
|
|
294
|
+
const c: i32 = toI32(scheme[k])
|
|
295
|
+
const letter: boolean = (c >= 97 && c <= 122) || (c >= 65 && c <= 90)
|
|
296
|
+
const rest: boolean = (c >= 48 && c <= 57) || c === 43 || c === 45 || c === 46
|
|
297
|
+
if (!letter && (k === 0 || !rest)) {
|
|
298
|
+
return false
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
return n > 0
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** The status code three ASCII digits from 100 to 599 spell, or -1. */
|
|
305
|
+
const httpFieldsStatusOf = (value: u8[]): i32 => {
|
|
306
|
+
if (toI32(value.length) !== 3) {
|
|
307
|
+
return -1
|
|
308
|
+
}
|
|
309
|
+
let code: i32 = 0
|
|
310
|
+
for (const b of value) {
|
|
311
|
+
const digit: i32 = toI32(b) - 48
|
|
312
|
+
if (digit < 0 || digit > 9) {
|
|
313
|
+
return -1
|
|
314
|
+
}
|
|
315
|
+
code = code * 10 + digit
|
|
316
|
+
}
|
|
317
|
+
return code >= 100 && code <= 599 ? code : -1
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** The length one run of at most eighteen ASCII digits spells, or -1. */
|
|
321
|
+
const httpFieldsLengthOf = (value: u8[]): i64 => {
|
|
322
|
+
const n: i32 = toI32(value.length)
|
|
323
|
+
if (n === 0 || n > HTTP_FIELDS_MAX_LENGTH_DIGITS) {
|
|
324
|
+
return toI64(-1)
|
|
325
|
+
}
|
|
326
|
+
let length: i64 = 0
|
|
327
|
+
for (const b of value) {
|
|
328
|
+
const digit: i32 = toI32(b) - 48
|
|
329
|
+
if (digit < 0 || digit > 9) {
|
|
330
|
+
return toI64(-1)
|
|
331
|
+
}
|
|
332
|
+
length = length * toI64(10) + toI64(digit)
|
|
333
|
+
}
|
|
334
|
+
return length
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/** Whether `authority` holds an `@`, which only userinfo would put there (RFC 9113 §8.3.1). */
|
|
338
|
+
const httpFieldsHasUserinfo = (authority: u8[]): boolean => {
|
|
339
|
+
for (const b of authority) {
|
|
340
|
+
if (toI32(b) === 64) {
|
|
341
|
+
return true
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return false
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Checks a field section a program is about to send, pseudo-headers left
|
|
349
|
+
* out: every name valid and in lowercase, every value valid, nothing
|
|
350
|
+
* connection-specific, and as many values as names. Answers `HTTP_FIELDS_OK`
|
|
351
|
+
* or the first refusal. A version's writer refuses what this refuses, so
|
|
352
|
+
* that a program's string can never reach the wire as a field it was not.
|
|
353
|
+
*/
|
|
354
|
+
export const httpFieldsCheckOutgoing = (names: u8[][], values: u8[][]): i32 => {
|
|
355
|
+
const n: i32 = toI32(names.length)
|
|
356
|
+
if (n !== toI32(values.length)) {
|
|
357
|
+
return HTTP_FIELDS_BAD_VALUE
|
|
358
|
+
}
|
|
359
|
+
for (let k: i32 = 0; k < n && k < toI32(names.length) && k < toI32(values.length); k++) {
|
|
360
|
+
const name: u8[] = names[k]
|
|
361
|
+
const value: u8[] = values[k]
|
|
362
|
+
if (!httpFieldNameValid(name)) {
|
|
363
|
+
return HTTP_FIELDS_BAD_NAME
|
|
364
|
+
}
|
|
365
|
+
if (!httpFieldValueValid(value)) {
|
|
366
|
+
return HTTP_FIELDS_BAD_VALUE
|
|
367
|
+
}
|
|
368
|
+
if (httpFieldConnectionSpecific(name, value)) {
|
|
369
|
+
return HTTP_FIELDS_CONNECTION_SPECIFIC
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return HTTP_FIELDS_OK
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* One field section read into its shape: the pseudo-headers by name, and the
|
|
377
|
+
* regular fields in order. `readRequest`, `readResponse` and `readTrailers`
|
|
378
|
+
* each clear it and fill it from a decoded section, and answer
|
|
379
|
+
* `HTTP_FIELDS_OK` or the first refusal; after a refusal what it holds is
|
|
380
|
+
* partial and not to be used. Every read empties and refills the same two
|
|
381
|
+
* lists rather than making new ones, so a caller that keeps a section past
|
|
382
|
+
* the next read copies it.
|
|
383
|
+
*/
|
|
384
|
+
export class HttpFields {
|
|
385
|
+
/** `:method`, empty in a response or a trailer section. */
|
|
386
|
+
method: u8[]
|
|
387
|
+
/** `:scheme`, empty when absent. */
|
|
388
|
+
scheme: u8[]
|
|
389
|
+
/** `:authority`, empty when absent. */
|
|
390
|
+
authority: u8[]
|
|
391
|
+
/** `:path`, empty when absent. */
|
|
392
|
+
path: u8[]
|
|
393
|
+
/** `:protocol` of an extended CONNECT (RFC 8441), empty when absent. */
|
|
394
|
+
protocol: u8[]
|
|
395
|
+
/** `:status` of a response, or 0. */
|
|
396
|
+
status: i32 = 0
|
|
397
|
+
/** The regular fields' names, in order. */
|
|
398
|
+
names: u8[][]
|
|
399
|
+
/** The regular fields' values, beside their names. */
|
|
400
|
+
values: u8[][]
|
|
401
|
+
/** The `content-length`, or `HTTP_FIELDS_NO_LENGTH`. */
|
|
402
|
+
contentLength: i64 = -1
|
|
403
|
+
/** The empty value an absent pseudo-header reads as, made once. */
|
|
404
|
+
none: u8[]
|
|
405
|
+
|
|
406
|
+
constructor() {
|
|
407
|
+
this.method = []
|
|
408
|
+
this.scheme = []
|
|
409
|
+
this.authority = []
|
|
410
|
+
this.path = []
|
|
411
|
+
this.protocol = []
|
|
412
|
+
this.none = []
|
|
413
|
+
this.names = []
|
|
414
|
+
this.values = []
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/** Empties every field, so the next read starts from nothing. */
|
|
418
|
+
clear(): void {
|
|
419
|
+
this.method = this.none
|
|
420
|
+
this.scheme = this.none
|
|
421
|
+
this.authority = this.none
|
|
422
|
+
this.path = this.none
|
|
423
|
+
this.protocol = this.none
|
|
424
|
+
this.status = 0
|
|
425
|
+
while (toI32(this.names.length) > 0) {
|
|
426
|
+
this.names.pop()
|
|
427
|
+
}
|
|
428
|
+
while (toI32(this.values.length) > 0) {
|
|
429
|
+
this.values.pop()
|
|
430
|
+
}
|
|
431
|
+
this.contentLength = HTTP_FIELDS_NO_LENGTH
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/** Whether the method is CONNECT, plain (RFC 9113 §8.5) or extended (RFC 8441). */
|
|
435
|
+
isConnect(): boolean {
|
|
436
|
+
return httpFieldIs(this.method, "CONNECT")
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/** The value of the first regular field named `name` (lowercase), or `null`. */
|
|
440
|
+
get(name: string): u8[] | null {
|
|
441
|
+
const n: i32 = toI32(this.names.length)
|
|
442
|
+
for (let k: i32 = 0; k < n && k < toI32(this.names.length) && k < toI32(this.values.length); k++) {
|
|
443
|
+
if (httpFieldIs(this.names[k], name)) {
|
|
444
|
+
return this.values[k]
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return null
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Reads `names` and `values` as a section whose pseudo-headers are those
|
|
452
|
+
* `allowed` names, recording them and the regular fields. The shape's own
|
|
453
|
+
* rules — which pseudo-headers are required — are the caller's. Answers
|
|
454
|
+
* the mask of pseudo-headers seen, or a negative refusal.
|
|
455
|
+
*/
|
|
456
|
+
readSection(names: u8[][], values: u8[][], allowed: i32): i32 {
|
|
457
|
+
this.clear()
|
|
458
|
+
const n: i32 = toI32(names.length)
|
|
459
|
+
if (n !== toI32(values.length)) {
|
|
460
|
+
return HTTP_FIELDS_BAD_VALUE
|
|
461
|
+
}
|
|
462
|
+
let seen: i32 = 0
|
|
463
|
+
let regular: boolean = false
|
|
464
|
+
for (let k: i32 = 0; k < n && k < toI32(names.length) && k < toI32(values.length); k++) {
|
|
465
|
+
const name: u8[] = names[k]
|
|
466
|
+
const value: u8[] = values[k]
|
|
467
|
+
if (!httpFieldValueValid(value)) {
|
|
468
|
+
return HTTP_FIELDS_BAD_VALUE
|
|
469
|
+
}
|
|
470
|
+
const pseudo: i32 = httpFieldsPseudoOf(name)
|
|
471
|
+
if (pseudo < 0) {
|
|
472
|
+
return HTTP_FIELDS_PSEUDO_UNKNOWN
|
|
473
|
+
}
|
|
474
|
+
if (pseudo > 0) {
|
|
475
|
+
if (regular) {
|
|
476
|
+
return HTTP_FIELDS_PSEUDO_AFTER_FIELD
|
|
477
|
+
}
|
|
478
|
+
if ((seen & pseudo) !== 0) {
|
|
479
|
+
return HTTP_FIELDS_PSEUDO_REPEATED
|
|
480
|
+
}
|
|
481
|
+
if ((allowed & pseudo) === 0) {
|
|
482
|
+
return HTTP_FIELDS_PSEUDO_FORBIDDEN
|
|
483
|
+
}
|
|
484
|
+
seen = seen | pseudo
|
|
485
|
+
const result: i32 = this.takePseudo(pseudo, value)
|
|
486
|
+
if (result !== HTTP_FIELDS_OK) {
|
|
487
|
+
return result
|
|
488
|
+
}
|
|
489
|
+
continue
|
|
490
|
+
}
|
|
491
|
+
regular = true
|
|
492
|
+
if (!httpFieldNameValid(name)) {
|
|
493
|
+
return HTTP_FIELDS_BAD_NAME
|
|
494
|
+
}
|
|
495
|
+
if (httpFieldConnectionSpecific(name, value)) {
|
|
496
|
+
return HTTP_FIELDS_CONNECTION_SPECIFIC
|
|
497
|
+
}
|
|
498
|
+
if (httpFieldIs(name, "content-length")) {
|
|
499
|
+
const length: i64 = httpFieldsLengthOf(value)
|
|
500
|
+
if (length < toI64(0) || (this.contentLength >= toI64(0) && this.contentLength !== length)) {
|
|
501
|
+
return HTTP_FIELDS_BAD_CONTENT_LENGTH
|
|
502
|
+
}
|
|
503
|
+
this.contentLength = length
|
|
504
|
+
}
|
|
505
|
+
this.names.push(name)
|
|
506
|
+
this.values.push(value)
|
|
507
|
+
}
|
|
508
|
+
return seen
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** Records the pseudo-header `pseudo` as `value`, checking its syntax. */
|
|
512
|
+
takePseudo(pseudo: i32, value: u8[]): i32 {
|
|
513
|
+
switch (pseudo) {
|
|
514
|
+
case HTTP_FIELDS_METHOD:
|
|
515
|
+
this.method = value
|
|
516
|
+
return httpFieldsAllTchar(value, true) ? HTTP_FIELDS_OK : HTTP_FIELDS_BAD_PSEUDO_VALUE
|
|
517
|
+
case HTTP_FIELDS_SCHEME:
|
|
518
|
+
this.scheme = value
|
|
519
|
+
return httpFieldsSchemeValid(value) ? HTTP_FIELDS_OK : HTTP_FIELDS_BAD_PSEUDO_VALUE
|
|
520
|
+
case HTTP_FIELDS_AUTHORITY:
|
|
521
|
+
this.authority = value
|
|
522
|
+
return httpFieldsHasUserinfo(value) ? HTTP_FIELDS_BAD_PSEUDO_VALUE : HTTP_FIELDS_OK
|
|
523
|
+
case HTTP_FIELDS_PATH:
|
|
524
|
+
this.path = value
|
|
525
|
+
return toI32(value.length) > 0 ? HTTP_FIELDS_OK : HTTP_FIELDS_BAD_PSEUDO_VALUE
|
|
526
|
+
case HTTP_FIELDS_PROTOCOL:
|
|
527
|
+
this.protocol = value
|
|
528
|
+
return httpFieldsAllTchar(value, true) ? HTTP_FIELDS_OK : HTTP_FIELDS_BAD_PSEUDO_VALUE
|
|
529
|
+
default:
|
|
530
|
+
this.status = httpFieldsStatusOf(value)
|
|
531
|
+
return this.status > 0 ? HTTP_FIELDS_OK : HTTP_FIELDS_BAD_PSEUDO_VALUE
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Reads a request's field section (RFC 9113 §8.3.1, RFC 9114 §4.3.1).
|
|
537
|
+
* Every request has `:method`. A CONNECT without `:protocol` has
|
|
538
|
+
* `:authority` and neither `:scheme` nor `:path` (RFC 9113 §8.5). An
|
|
539
|
+
* extended CONNECT — `:protocol` present, which only `extendedConnect`
|
|
540
|
+
* allows, the version having sent SETTINGS_ENABLE_CONNECT_PROTOCOL — has
|
|
541
|
+
* all four of the others (RFC 8441 §4). Any other request has `:scheme`
|
|
542
|
+
* and `:path`. A `:path` that does not start with `/` is OPTIONS's `*` or
|
|
543
|
+
* refused, and a `host` field must name `:authority` when both are there.
|
|
544
|
+
*/
|
|
545
|
+
readRequest(names: u8[][], values: u8[][], extendedConnect: boolean): i32 {
|
|
546
|
+
const all: i32 =
|
|
547
|
+
HTTP_FIELDS_METHOD |
|
|
548
|
+
HTTP_FIELDS_SCHEME |
|
|
549
|
+
HTTP_FIELDS_AUTHORITY |
|
|
550
|
+
HTTP_FIELDS_PATH |
|
|
551
|
+
HTTP_FIELDS_PROTOCOL
|
|
552
|
+
const seen: i32 = this.readSection(names, values, all)
|
|
553
|
+
if (seen < 0) {
|
|
554
|
+
return seen
|
|
555
|
+
}
|
|
556
|
+
if ((seen & HTTP_FIELDS_METHOD) === 0) {
|
|
557
|
+
return HTTP_FIELDS_PSEUDO_MISSING
|
|
558
|
+
}
|
|
559
|
+
const connect: boolean = this.isConnect()
|
|
560
|
+
if ((seen & HTTP_FIELDS_PROTOCOL) !== 0) {
|
|
561
|
+
if (!extendedConnect || !connect) {
|
|
562
|
+
return HTTP_FIELDS_PSEUDO_FORBIDDEN
|
|
563
|
+
}
|
|
564
|
+
const tunnel: i32 = HTTP_FIELDS_SCHEME | HTTP_FIELDS_AUTHORITY | HTTP_FIELDS_PATH
|
|
565
|
+
if ((seen & tunnel) !== tunnel) {
|
|
566
|
+
return HTTP_FIELDS_PSEUDO_MISSING
|
|
567
|
+
}
|
|
568
|
+
} else if (connect) {
|
|
569
|
+
if ((seen & (HTTP_FIELDS_SCHEME | HTTP_FIELDS_PATH)) !== 0) {
|
|
570
|
+
return HTTP_FIELDS_PSEUDO_FORBIDDEN
|
|
571
|
+
}
|
|
572
|
+
if ((seen & HTTP_FIELDS_AUTHORITY) === 0) {
|
|
573
|
+
return HTTP_FIELDS_PSEUDO_MISSING
|
|
574
|
+
}
|
|
575
|
+
} else if ((seen & (HTTP_FIELDS_SCHEME | HTTP_FIELDS_PATH)) !== (HTTP_FIELDS_SCHEME | HTTP_FIELDS_PATH)) {
|
|
576
|
+
return HTTP_FIELDS_PSEUDO_MISSING
|
|
577
|
+
}
|
|
578
|
+
if (toI32(this.path.length) > 0 && toI32(this.path[0]) !== 47) {
|
|
579
|
+
if (!httpFieldIs(this.path, "*") || !httpFieldIs(this.method, "OPTIONS")) {
|
|
580
|
+
return HTTP_FIELDS_BAD_PSEUDO_VALUE
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
const host: u8[] | null = this.get("host")
|
|
584
|
+
if (host !== null && (seen & HTTP_FIELDS_AUTHORITY) !== 0 && !httpFieldsSameHost(host, this.authority)) {
|
|
585
|
+
return HTTP_FIELDS_AUTHORITY_MISMATCH
|
|
586
|
+
}
|
|
587
|
+
return HTTP_FIELDS_OK
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/** Reads a response's field section: exactly one `:status`, and no other pseudo-header (RFC 9113 §8.3.2). */
|
|
591
|
+
readResponse(names: u8[][], values: u8[][]): i32 {
|
|
592
|
+
const seen: i32 = this.readSection(names, values, HTTP_FIELDS_STATUS)
|
|
593
|
+
if (seen < 0) {
|
|
594
|
+
return seen
|
|
595
|
+
}
|
|
596
|
+
return (seen & HTTP_FIELDS_STATUS) !== 0 ? HTTP_FIELDS_OK : HTTP_FIELDS_PSEUDO_MISSING
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** Reads a trailer section: regular fields only (RFC 9113 §8.1). */
|
|
600
|
+
readTrailers(names: u8[][], values: u8[][]): i32 {
|
|
601
|
+
const seen: i32 = this.readSection(names, values, HTTP_FIELDS_ZERO)
|
|
602
|
+
return seen < 0 ? seen : HTTP_FIELDS_OK
|
|
603
|
+
}
|
|
604
|
+
}
|