@amritk/nish-x86_64-linux 0.18.0 → 0.21.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 +58 -28
- package/bin/nish +0 -0
- package/docs/AI.md +2759 -0
- package/package.json +3 -2
- package/runtime/browser.d.ts +14 -0
- package/runtime/browser.mjs +88 -0
- package/runtime/core/arrays.d.ts +18 -0
- package/runtime/core/arrays.mjs +125 -0
- package/runtime/core/checks.mjs +20 -0
- package/runtime/core/host.d.ts +70 -0
- package/runtime/core/host.mjs +311 -0
- package/runtime/core/numbers.d.ts +53 -0
- package/runtime/core/numbers.mjs +316 -0
- package/runtime/core/result.d.ts +41 -0
- package/runtime/core/result.mjs +127 -0
- package/runtime/core/strings.d.ts +14 -0
- package/runtime/core/strings.mjs +169 -0
- package/runtime/core.d.ts +9 -0
- package/runtime/core.mjs +55 -0
- package/runtime/host.d.ts +6 -0
- package/runtime/nish.d.ts +99 -5
- package/runtime/nish.h +289 -19
- package/runtime/nish.mjs +39 -3
- package/runtime/node.mjs +383 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +8 -7
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +214 -16
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +235 -11
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +5 -14
- package/runtime/runtime.c +257 -17
- package/runtime/shim.mjs +6 -915
- package/runtime/sqlite/LICENSE.md +85 -0
- package/runtime/sqlite/README.md +45 -0
- package/runtime/sqlite/nish-sqlite.c +359 -0
- package/runtime/sqlite/sqlite3.c +271131 -0
- package/runtime/sqlite/sqlite3.h +14447 -0
- package/scripts/build.sh +437 -28
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +108 -58
- package/std/base64.ts +116 -0
- package/std/bytes.ts +419 -0
- package/std/cli.ts +328 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +760 -0
- 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 +382 -54
- package/std/crypto/p256.ts +932 -61
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +20 -131
- package/std/json-tokenizer.ts +4237 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1182 -0
- package/std/json.ts +1202 -34
- package/std/math.ts +608 -0
- 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 +501 -62
- 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 +261 -43
- package/std/net/http3.ts +187 -28
- package/std/net/postgres.ts +1875 -0
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +108 -11
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- 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 +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/regex.ts +4274 -0
- package/std/sqlite.ts +391 -0
- package/std/term-io.ts +978 -0
- package/std/term-pixels.ts +874 -0
- package/std/term-probe.ts +459 -0
- package/std/term-rate.ts +993 -0
- package/std/term.ts +2453 -0
- package/std/text.ts +90 -7
- package/std/threads.ts +438 -14
- package/std/yaml-lines.ts +141 -0
- package/std/yaml.ts +4675 -0
package/std/net/url.ts
ADDED
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/net/url` — the request target's path and query, split and
|
|
3
|
+
* percent-decoded in place, and its query string read as key/value pairs.
|
|
4
|
+
*
|
|
5
|
+
* A handler that routes on a path or reads `?name=…` from a query otherwise
|
|
6
|
+
* writes its own byte scan, and each such scan decides differently what `%zz`,
|
|
7
|
+
* `%00`, `+` or a hundred thousand `&` mean. This module decides once:
|
|
8
|
+
*
|
|
9
|
+
* import { UrlQuery, UrlQueryLimits, URL_DECODE_FORM, URL_QUERY_PAIR, percentDecode, urlQueryFind, urlQueryStart } from "nish/net/url";
|
|
10
|
+
*
|
|
11
|
+
* const q: i32 = urlQueryStart(target, 0, len) // -1: no `?`, so no query
|
|
12
|
+
* if (q >= 0) {
|
|
13
|
+
* const query: UrlQuery = new UrlQuery(new UrlQueryLimits())
|
|
14
|
+
* query.reset(target, q + 1, len - q - 1)
|
|
15
|
+
* if (urlQueryFind(query, "name") === URL_QUERY_PAIR) {
|
|
16
|
+
* const n: i32 = percentDecode(target, query.valueStart, query.valueLength, out, 0, URL_DECODE_FORM)
|
|
17
|
+
* if (n >= 0) {
|
|
18
|
+
* // out[0, n) is the value
|
|
19
|
+
* } // else n is a URL_DECODE_ERR_* code: a peer's `%zz`, never read out
|
|
20
|
+
* } // else no such key, or URL_QUERY_ERR_PAIRS past the cap
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* **The byte API allocates nothing.** `urlQueryStart`, `percentDecode`,
|
|
24
|
+
* `UrlPathSegments` and `UrlQuery` read a window `bytes[off, off + len)` and
|
|
25
|
+
* answer offsets into it, or write into the caller's array; `reset` reuses an
|
|
26
|
+
* iterator, so a server that holds one per connection allocates nothing per
|
|
27
|
+
* request. No byte outside the window is read. A window outside its array, or
|
|
28
|
+
* an `at` outside `out`, is a bug in the caller and panics; nothing a peer
|
|
29
|
+
* sends does.
|
|
30
|
+
*
|
|
31
|
+
* **Decoding is strict unless asked otherwise.** With no flags,
|
|
32
|
+
* `percentDecode` refuses a `%` not followed by two hex digits
|
|
33
|
+
* (`URL_DECODE_ERR_ESCAPE`) and `%00` (`URL_DECODE_ERR_NUL`), and copies `+`
|
|
34
|
+
* as it stands. `URL_DECODE_FORM` reads `+` as a space, as
|
|
35
|
+
* `application/x-www-form-urlencoded` does; `URL_DECODE_LENIENT` copies a
|
|
36
|
+
* malformed escape as it stands, as the WHATWG URL Standard's percent-decode
|
|
37
|
+
* and `URLSearchParams` do; `URL_DECODE_ALLOW_NUL` decodes `%00` to a 0 byte.
|
|
38
|
+
* For valid UTF-8, `URLSearchParams` is `URL_DECODE_FORM | URL_DECODE_LENIENT |
|
|
39
|
+
* URL_DECODE_ALLOW_NUL`; it replaces an invalid sequence with U+FFFD.
|
|
40
|
+
*
|
|
41
|
+
* **Decoded bytes are bytes.** The byte API does not check that what it
|
|
42
|
+
* decodes is UTF-8, where `decodeURIComponent` throws on `%FF`: a path segment
|
|
43
|
+
* or a value may be binary, and whatever reads it as text checks it.
|
|
44
|
+
* `urlDecode` and `urlQueryGet`, which answer a string, refuse anything that is
|
|
45
|
+
* not valid UTF-8.
|
|
46
|
+
*
|
|
47
|
+
* **A path is split before it is decoded.** `UrlPathSegments` splits on the
|
|
48
|
+
* raw `/` only, and `percentDecodePathSegment` decodes one segment, so `%2F`
|
|
49
|
+
* is a 0x2F byte inside its segment and never starts a new one: `/a%2Fb/c` is
|
|
50
|
+
* the two segments `a/b` and `c`. `+` is literal in a path.
|
|
51
|
+
*
|
|
52
|
+
* **A query yields at most `UrlQueryLimits.maxPairs` pairs** (100 by default),
|
|
53
|
+
* and past it `next()` answers `URL_QUERY_ERR_PAIRS` and stays spent. Empty
|
|
54
|
+
* pairs (`a&&b`, a leading or trailing `&`) are skipped and not counted, as
|
|
55
|
+
* `URLSearchParams` skips them.
|
|
56
|
+
*
|
|
57
|
+
* **A fragment is not part of a request target** (RFC 9112 §3.2), so nothing
|
|
58
|
+
* here looks for a `#`: in a target that holds one, it is a byte of the path
|
|
59
|
+
* or of the last value.
|
|
60
|
+
*
|
|
61
|
+
* The record of what this module refuses and why is `docs/security/url.md`.
|
|
62
|
+
*/
|
|
63
|
+
|
|
64
|
+
/** `+` is a space, as `application/x-www-form-urlencoded` writes one. */
|
|
65
|
+
export const URL_DECODE_FORM: i32 = 1
|
|
66
|
+
/** A `%` not followed by two hex digits is copied as it stands rather than refused (WHATWG percent-decode). */
|
|
67
|
+
export const URL_DECODE_LENIENT: i32 = 2
|
|
68
|
+
/** `%00` decodes to a 0 byte rather than being refused. */
|
|
69
|
+
export const URL_DECODE_ALLOW_NUL: i32 = 4
|
|
70
|
+
|
|
71
|
+
/** A `%` not followed by two hex digits: `%zz`, or `%4` or `%` at the end of the window. */
|
|
72
|
+
export const URL_DECODE_ERR_ESCAPE: i32 = -1
|
|
73
|
+
/** `%00` without `URL_DECODE_ALLOW_NUL`. */
|
|
74
|
+
export const URL_DECODE_ERR_NUL: i32 = -2
|
|
75
|
+
/** `out` has no room for the next byte; nothing is written at or past `out.length`. */
|
|
76
|
+
export const URL_DECODE_ERR_SPACE: i32 = -3
|
|
77
|
+
|
|
78
|
+
/** `next()` found a pair; the iterator's windows bound it. */
|
|
79
|
+
export const URL_QUERY_PAIR: i32 = 1
|
|
80
|
+
/** The window holds no more pairs; every later `next()` answers the same. */
|
|
81
|
+
export const URL_QUERY_END: i32 = 0
|
|
82
|
+
/** The query holds more than `maxPairs` pairs; sticky in `failed`. */
|
|
83
|
+
export const URL_QUERY_ERR_PAIRS: i32 = -4
|
|
84
|
+
|
|
85
|
+
/** The default for `UrlQueryLimits.maxPairs`. */
|
|
86
|
+
export const URL_DEFAULT_MAX_PAIRS: i32 = 100
|
|
87
|
+
|
|
88
|
+
const URL_SPACE: i32 = 32
|
|
89
|
+
const URL_PERCENT: i32 = 37
|
|
90
|
+
const URL_AMPERSAND: i32 = 38
|
|
91
|
+
const URL_PLUS: i32 = 43
|
|
92
|
+
const URL_SLASH: i32 = 47
|
|
93
|
+
const URL_EQUALS: i32 = 61
|
|
94
|
+
const URL_QUESTION: i32 = 63
|
|
95
|
+
|
|
96
|
+
/** Panics unless `bytes[off, off + len)` lies inside `bytes`. */
|
|
97
|
+
const urlCheckWindow = (bytes: u8[], off: i32, len: i32, who: string): void => {
|
|
98
|
+
const size: i32 = toI32(bytes.length)
|
|
99
|
+
if (off < 0 || len < 0 || off > size || len > size - off) {
|
|
100
|
+
panic(who + ": the window is outside the array")
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** The value of one hex digit, either case, or -1. */
|
|
105
|
+
const urlHexValue = (b: i32): i32 => {
|
|
106
|
+
if (b >= 48 && b <= 57) {
|
|
107
|
+
return b - 48
|
|
108
|
+
}
|
|
109
|
+
if (b >= 65 && b <= 70) {
|
|
110
|
+
return b - 55
|
|
111
|
+
}
|
|
112
|
+
if (b >= 97 && b <= 102) {
|
|
113
|
+
return b - 87
|
|
114
|
+
}
|
|
115
|
+
return -1
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** The byte the escape at `bytes[i]` (a `%`) stands for, or -1 when two hex digits do not follow it before `end`. */
|
|
119
|
+
const urlEscapeAt = (bytes: u8[], i: i32, end: i32): i32 => {
|
|
120
|
+
if (end - i < 3) {
|
|
121
|
+
return -1
|
|
122
|
+
}
|
|
123
|
+
const hi: i32 = urlHexValue(toI32(bytes[i + 1]))
|
|
124
|
+
const lo: i32 = urlHexValue(toI32(bytes[i + 2]))
|
|
125
|
+
if (hi < 0 || lo < 0) {
|
|
126
|
+
return -1
|
|
127
|
+
}
|
|
128
|
+
return hi * 16 + lo
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* `percentDecode`'s loop, shared with `percentDecodeErrorAt` so the two agree
|
|
133
|
+
* on where an error is: with `wantPos` it answers the input offset of the
|
|
134
|
+
* error, or -1 when there is none, rather than the count or the code.
|
|
135
|
+
*/
|
|
136
|
+
const urlDecodeRun = (
|
|
137
|
+
src: u8[],
|
|
138
|
+
off: i32,
|
|
139
|
+
len: i32,
|
|
140
|
+
out: u8[],
|
|
141
|
+
at: i32,
|
|
142
|
+
flags: i32,
|
|
143
|
+
wantPos: boolean
|
|
144
|
+
): i32 => {
|
|
145
|
+
urlCheckWindow(src, off, len, "percentDecode")
|
|
146
|
+
const size: i32 = toI32(out.length)
|
|
147
|
+
if (at < 0 || at > size) {
|
|
148
|
+
panic("percentDecode: at is outside out")
|
|
149
|
+
}
|
|
150
|
+
const form: boolean = (flags & URL_DECODE_FORM) !== 0
|
|
151
|
+
const lenient: boolean = (flags & URL_DECODE_LENIENT) !== 0
|
|
152
|
+
const allowNul: boolean = (flags & URL_DECODE_ALLOW_NUL) !== 0
|
|
153
|
+
const end: i32 = off + len
|
|
154
|
+
let i: i32 = off
|
|
155
|
+
let w: i32 = at
|
|
156
|
+
const n: i32 = toI32(src.length)
|
|
157
|
+
while (i >= 0 && i < end && i < n) {
|
|
158
|
+
let b: i32 = toI32(src[i])
|
|
159
|
+
let step: i32 = 1
|
|
160
|
+
if (b === URL_PERCENT) {
|
|
161
|
+
const e: i32 = urlEscapeAt(src, i, end)
|
|
162
|
+
if (e >= 0) {
|
|
163
|
+
if (e === 0 && !allowNul) {
|
|
164
|
+
return wantPos ? i : URL_DECODE_ERR_NUL
|
|
165
|
+
}
|
|
166
|
+
b = e
|
|
167
|
+
step = 3
|
|
168
|
+
} else if (!lenient) {
|
|
169
|
+
return wantPos ? i : URL_DECODE_ERR_ESCAPE
|
|
170
|
+
}
|
|
171
|
+
} else if (form && b === URL_PLUS) {
|
|
172
|
+
b = URL_SPACE
|
|
173
|
+
}
|
|
174
|
+
if (w >= size) {
|
|
175
|
+
return wantPos ? i : URL_DECODE_ERR_SPACE
|
|
176
|
+
}
|
|
177
|
+
out[w] = toU8(b)
|
|
178
|
+
w += 1
|
|
179
|
+
i += step
|
|
180
|
+
}
|
|
181
|
+
return wantPos ? -1 : w - at
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* The index of the first `?` in `bytes[off, off + len)`, or -1 when there is
|
|
186
|
+
* none. The path is `[off, q)` and the query `(q, off + len)`; nothing is
|
|
187
|
+
* copied. A `#` is not looked for: a request target holds no fragment.
|
|
188
|
+
*/
|
|
189
|
+
export const urlQueryStart = (bytes: u8[], off: i32, len: i32): i32 => {
|
|
190
|
+
urlCheckWindow(bytes, off, len, "urlQueryStart")
|
|
191
|
+
const end: i32 = off + len
|
|
192
|
+
const n: i32 = toI32(bytes.length)
|
|
193
|
+
let i: i32 = off
|
|
194
|
+
while (i >= 0 && i < end && i < n) {
|
|
195
|
+
if (toI32(bytes[i]) === URL_QUESTION) {
|
|
196
|
+
return i
|
|
197
|
+
}
|
|
198
|
+
i += 1
|
|
199
|
+
}
|
|
200
|
+
return -1
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Percent-decodes `src[off, off + len)` into `out` from `out[at]` on, and
|
|
205
|
+
* answers the number of bytes written, or a negative `URL_DECODE_ERR_*` code;
|
|
206
|
+
* `flags` is a sum of `URL_DECODE_*` (0 is strict: malformed escapes and `%00`
|
|
207
|
+
* refused, `+` kept). Decoding never makes the input longer, so `len` bytes
|
|
208
|
+
* of room always suffice; with less, `URL_DECODE_ERR_SPACE` is answered before
|
|
209
|
+
* any write at or past `out.length`. On an error the bytes before it have been
|
|
210
|
+
* written. What is decoded is not checked as UTF-8.
|
|
211
|
+
*/
|
|
212
|
+
export const percentDecode = (src: u8[], off: i32, len: i32, out: u8[], at: i32, flags: i32): i32 =>
|
|
213
|
+
urlDecodeRun(src, off, len, out, at, flags, false)
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* The offset in `src` of the byte `percentDecode` with the same arguments
|
|
217
|
+
* fails at — the `%` of a malformed escape or of `%00`, or the first byte
|
|
218
|
+
* `out` has no room for — or -1 when it does not fail. It writes what
|
|
219
|
+
* `percentDecode` would have written before the error.
|
|
220
|
+
*/
|
|
221
|
+
export const percentDecodeErrorAt = (src: u8[], off: i32, len: i32, out: u8[], at: i32, flags: i32): i32 =>
|
|
222
|
+
urlDecodeRun(src, off, len, out, at, flags, true)
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* `percentDecode` for one path segment: `+` is literal whatever `flags` say,
|
|
226
|
+
* and `%2F` is a 0x2F byte in the segment. Split the path with
|
|
227
|
+
* `UrlPathSegments` first, then decode each segment.
|
|
228
|
+
*/
|
|
229
|
+
export const percentDecodePathSegment = (
|
|
230
|
+
src: u8[],
|
|
231
|
+
off: i32,
|
|
232
|
+
len: i32,
|
|
233
|
+
out: u8[],
|
|
234
|
+
at: i32,
|
|
235
|
+
flags: i32
|
|
236
|
+
): i32 => urlDecodeRun(src, off, len, out, at, (flags | URL_DECODE_FORM) ^ URL_DECODE_FORM, false)
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* The segments of a path, split on the raw `/`. A leading `/` starts the
|
|
240
|
+
* path rather than an empty segment, so `/a//b/` is `a`, ``, `b`, ``, `/` is
|
|
241
|
+
* one empty segment and an empty window has none. Each segment's raw bytes
|
|
242
|
+
* are `bytes[start, start + length)`; decode one with
|
|
243
|
+
* `percentDecodePathSegment`.
|
|
244
|
+
*/
|
|
245
|
+
export class UrlPathSegments {
|
|
246
|
+
bytes: u8[]
|
|
247
|
+
/** The current segment's first byte. */
|
|
248
|
+
start: i32 = 0
|
|
249
|
+
/** The current segment's length in raw bytes. */
|
|
250
|
+
length: i32 = 0
|
|
251
|
+
pos: i32 = 0
|
|
252
|
+
end: i32 = 0
|
|
253
|
+
done: boolean = true
|
|
254
|
+
|
|
255
|
+
/** An iterator over nothing until `reset`. */
|
|
256
|
+
constructor() {
|
|
257
|
+
this.bytes = new Array<u8>(0)
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/** Starts over on the path `bytes[off, off + len)`; panics when the window is outside the array. */
|
|
261
|
+
reset(bytes: u8[], off: i32, len: i32): void {
|
|
262
|
+
urlCheckWindow(bytes, off, len, "UrlPathSegments")
|
|
263
|
+
this.bytes = bytes
|
|
264
|
+
this.pos = off
|
|
265
|
+
this.end = off + len
|
|
266
|
+
this.start = off
|
|
267
|
+
this.length = 0
|
|
268
|
+
this.done = len === 0
|
|
269
|
+
if (len > 0 && toI32(bytes[off]) === URL_SLASH) {
|
|
270
|
+
this.pos = off + 1
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Moves to the next segment and answers true, or answers false when the path has no more. */
|
|
275
|
+
next(): boolean {
|
|
276
|
+
if (this.done) {
|
|
277
|
+
return false
|
|
278
|
+
}
|
|
279
|
+
const bytes: u8[] = this.bytes
|
|
280
|
+
let j: i32 = this.pos
|
|
281
|
+
const n: i32 = toI32(bytes.length)
|
|
282
|
+
while (j >= 0 && j < this.end && j < n && toI32(bytes[j]) !== URL_SLASH) {
|
|
283
|
+
j += 1
|
|
284
|
+
}
|
|
285
|
+
this.start = this.pos
|
|
286
|
+
this.length = j - this.pos
|
|
287
|
+
if (j < this.end) {
|
|
288
|
+
this.pos = j + 1
|
|
289
|
+
} else {
|
|
290
|
+
this.done = true
|
|
291
|
+
}
|
|
292
|
+
return true
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* What a `UrlQuery` refuses to yield. The iterator copies the number when it
|
|
298
|
+
* is made; a negative cap is read as zero, so every query with a pair fails.
|
|
299
|
+
*/
|
|
300
|
+
export class UrlQueryLimits {
|
|
301
|
+
/** The most pairs one query yields, `URL_DEFAULT_MAX_PAIRS` (a field starts at a literal); past it `next()` answers `URL_QUERY_ERR_PAIRS` and stays spent. */
|
|
302
|
+
maxPairs: i32 = 100
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* The pairs of a query string, one per `next()`, in order. The window is the
|
|
307
|
+
* query without its `?`. `&` separates pairs and the first `=` in a pair ends
|
|
308
|
+
* its key: `a&b=&c=d=e` is `a` with no value, `b` with an empty one and `c`
|
|
309
|
+
* with `d=e`. A key's raw bytes are `bytes[keyStart, keyStart + keyLength)`
|
|
310
|
+
* and a value's `bytes[valueStart, valueStart + valueLength)`; decode them
|
|
311
|
+
* with `percentDecode` and `URL_DECODE_FORM`.
|
|
312
|
+
*/
|
|
313
|
+
export class UrlQuery {
|
|
314
|
+
bytes: u8[]
|
|
315
|
+
keyStart: i32 = 0
|
|
316
|
+
keyLength: i32 = 0
|
|
317
|
+
valueStart: i32 = 0
|
|
318
|
+
valueLength: i32 = 0
|
|
319
|
+
/** 0, or the `URL_QUERY_ERR_*` code every later `next()` answers. */
|
|
320
|
+
failed: i32 = 0
|
|
321
|
+
maxPairs: i32
|
|
322
|
+
pos: i32 = 0
|
|
323
|
+
end: i32 = 0
|
|
324
|
+
count: i32 = 0
|
|
325
|
+
/** False for a pair written without `=` (`a`), true for `a=` with `valueLength` 0. */
|
|
326
|
+
hasValue: boolean = false
|
|
327
|
+
|
|
328
|
+
/** An iterator over nothing until `reset`, with the cap in `limits`. */
|
|
329
|
+
constructor(limits: UrlQueryLimits) {
|
|
330
|
+
this.bytes = new Array<u8>(0)
|
|
331
|
+
this.maxPairs = limits.maxPairs < 0 ? 0 : limits.maxPairs
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Starts over on the query `bytes[off, off + len)`, clearing `failed`; panics when the window is outside the array. */
|
|
335
|
+
reset(bytes: u8[], off: i32, len: i32): void {
|
|
336
|
+
urlCheckWindow(bytes, off, len, "UrlQuery")
|
|
337
|
+
this.bytes = bytes
|
|
338
|
+
this.pos = off
|
|
339
|
+
this.end = off + len
|
|
340
|
+
this.count = 0
|
|
341
|
+
this.failed = 0
|
|
342
|
+
this.keyStart = off
|
|
343
|
+
this.keyLength = 0
|
|
344
|
+
this.valueStart = off
|
|
345
|
+
this.valueLength = 0
|
|
346
|
+
this.hasValue = false
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** `URL_QUERY_PAIR` with the windows set to the next pair, `URL_QUERY_END`, or `failed`. */
|
|
350
|
+
next(): i32 {
|
|
351
|
+
if (this.failed !== 0) {
|
|
352
|
+
return this.failed
|
|
353
|
+
}
|
|
354
|
+
const bytes: u8[] = this.bytes
|
|
355
|
+
const end: i32 = this.end
|
|
356
|
+
let i: i32 = this.pos
|
|
357
|
+
const n: i32 = toI32(bytes.length)
|
|
358
|
+
while (i >= 0 && i < end && i < n && toI32(bytes[i]) === URL_AMPERSAND) {
|
|
359
|
+
i += 1
|
|
360
|
+
}
|
|
361
|
+
this.pos = i
|
|
362
|
+
if (i >= end) {
|
|
363
|
+
return URL_QUERY_END
|
|
364
|
+
}
|
|
365
|
+
if (this.count >= this.maxPairs) {
|
|
366
|
+
this.failed = URL_QUERY_ERR_PAIRS
|
|
367
|
+
return this.failed
|
|
368
|
+
}
|
|
369
|
+
let eq: i32 = -1
|
|
370
|
+
while (i >= 0 && i < end && i < n) {
|
|
371
|
+
const b: i32 = toI32(bytes[i])
|
|
372
|
+
if (b === URL_AMPERSAND) {
|
|
373
|
+
break
|
|
374
|
+
}
|
|
375
|
+
if (b === URL_EQUALS && eq < 0) {
|
|
376
|
+
eq = i
|
|
377
|
+
}
|
|
378
|
+
i += 1
|
|
379
|
+
}
|
|
380
|
+
this.keyStart = this.pos
|
|
381
|
+
if (eq < 0) {
|
|
382
|
+
this.keyLength = i - this.pos
|
|
383
|
+
this.valueStart = i
|
|
384
|
+
this.valueLength = 0
|
|
385
|
+
this.hasValue = false
|
|
386
|
+
} else {
|
|
387
|
+
this.keyLength = eq - this.pos
|
|
388
|
+
this.valueStart = eq + 1
|
|
389
|
+
this.valueLength = i - eq - 1
|
|
390
|
+
this.hasValue = true
|
|
391
|
+
}
|
|
392
|
+
this.pos = i
|
|
393
|
+
this.count += 1
|
|
394
|
+
return URL_QUERY_PAIR
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Whether `bytes[off, off + len)`, decoded strictly in form mode, is `name`'s
|
|
400
|
+
* bytes. Nothing is written: a malformed escape or `%00` in the key matches
|
|
401
|
+
* no name.
|
|
402
|
+
*/
|
|
403
|
+
const urlKeyEquals = (bytes: u8[], off: i32, len: i32, name: string): boolean => {
|
|
404
|
+
const n: i32 = toI32(name.length)
|
|
405
|
+
const end: i32 = off + len
|
|
406
|
+
let i: i32 = off
|
|
407
|
+
let o: i32 = 0
|
|
408
|
+
const size: i32 = toI32(bytes.length)
|
|
409
|
+
while (i >= 0 && i < end && i < size) {
|
|
410
|
+
let b: i32 = toI32(bytes[i])
|
|
411
|
+
let step: i32 = 1
|
|
412
|
+
if (b === URL_PERCENT) {
|
|
413
|
+
b = urlEscapeAt(bytes, i, end)
|
|
414
|
+
if (b <= 0) {
|
|
415
|
+
return false
|
|
416
|
+
}
|
|
417
|
+
step = 3
|
|
418
|
+
} else if (b === URL_PLUS) {
|
|
419
|
+
b = URL_SPACE
|
|
420
|
+
}
|
|
421
|
+
if (o >= n || b !== toI32(name.charCodeAt(o))) {
|
|
422
|
+
return false
|
|
423
|
+
}
|
|
424
|
+
o += 1
|
|
425
|
+
i += step
|
|
426
|
+
}
|
|
427
|
+
return o === n
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Moves `q` on to the first of its remaining pairs whose key, decoded in form
|
|
432
|
+
* mode, equals `name`, and answers `URL_QUERY_PAIR` with `q`'s windows on it;
|
|
433
|
+
* or `URL_QUERY_END`, or the error `next()` answered. Nothing is allocated.
|
|
434
|
+
* `?na%6De=x` holds the key `name`; a key with a malformed escape or `%00`
|
|
435
|
+
* matches nothing. Call it again for the next pair with the same key.
|
|
436
|
+
* `name` is ASCII: natively it is compared with the key's bytes, under Node
|
|
437
|
+
* with its UTF-16 code units, so a non-ASCII name answers differently.
|
|
438
|
+
*/
|
|
439
|
+
export const urlQueryFind = (q: UrlQuery, name: string): i32 => {
|
|
440
|
+
let k: i32 = q.next()
|
|
441
|
+
while (k === URL_QUERY_PAIR) {
|
|
442
|
+
if (urlKeyEquals(q.bytes, q.keyStart, q.keyLength, name)) {
|
|
443
|
+
return k
|
|
444
|
+
}
|
|
445
|
+
k = q.next()
|
|
446
|
+
}
|
|
447
|
+
return k
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/** Whether `buf[off, off + len)` is well-formed UTF-8 (RFC 3629: no overlong form, no surrogate, nothing past U+10FFFF, nothing cut short). */
|
|
451
|
+
const urlIsUtf8 = (buf: u8[], off: i32, len: i32): boolean => {
|
|
452
|
+
const end: i32 = off + len
|
|
453
|
+
let i: i32 = off
|
|
454
|
+
const n: i32 = toI32(buf.length)
|
|
455
|
+
while (i >= 0 && i < end && i < n) {
|
|
456
|
+
const b: i32 = toI32(buf[i])
|
|
457
|
+
if (b < 0x80) {
|
|
458
|
+
i += 1
|
|
459
|
+
continue
|
|
460
|
+
}
|
|
461
|
+
let need: i32 = 0
|
|
462
|
+
let lo: i32 = 0x80
|
|
463
|
+
let hi: i32 = 0xbf
|
|
464
|
+
if (b >= 0xc2 && b <= 0xdf) {
|
|
465
|
+
need = 1
|
|
466
|
+
} else if (b === 0xe0) {
|
|
467
|
+
need = 2
|
|
468
|
+
lo = 0xa0
|
|
469
|
+
} else if (b === 0xed) {
|
|
470
|
+
need = 2
|
|
471
|
+
hi = 0x9f
|
|
472
|
+
} else if (b >= 0xe1 && b <= 0xef) {
|
|
473
|
+
need = 2
|
|
474
|
+
} else if (b === 0xf0) {
|
|
475
|
+
need = 3
|
|
476
|
+
lo = 0x90
|
|
477
|
+
} else if (b === 0xf4) {
|
|
478
|
+
need = 3
|
|
479
|
+
hi = 0x8f
|
|
480
|
+
} else if (b >= 0xf1 && b <= 0xf3) {
|
|
481
|
+
need = 3
|
|
482
|
+
} else {
|
|
483
|
+
return false
|
|
484
|
+
}
|
|
485
|
+
if (end - i - 1 < need || i + 1 >= n) {
|
|
486
|
+
return false
|
|
487
|
+
}
|
|
488
|
+
const second: i32 = toI32(buf[i + 1])
|
|
489
|
+
if (second < lo || second > hi) {
|
|
490
|
+
return false
|
|
491
|
+
}
|
|
492
|
+
let k: i32 = 2
|
|
493
|
+
while (k <= need) {
|
|
494
|
+
const c: i32 = toI32(buf[i + k])
|
|
495
|
+
if (c < 0x80 || c > 0xbf) {
|
|
496
|
+
return false
|
|
497
|
+
}
|
|
498
|
+
k += 1
|
|
499
|
+
}
|
|
500
|
+
i += need + 1
|
|
501
|
+
}
|
|
502
|
+
return true
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/** `s`'s bytes, or null when it holds a code unit past 0xFF (only possible under Node, where a string is UTF-16). */
|
|
506
|
+
const urlBytesOf = (s: string): u8[] | null => {
|
|
507
|
+
const n: i32 = toI32(s.length)
|
|
508
|
+
const bytes: u8[] = new Array<u8>(n)
|
|
509
|
+
let i: i32 = 0
|
|
510
|
+
while (i < n && i < toI32(bytes.length)) {
|
|
511
|
+
const c: i32 = toI32(s.charCodeAt(i))
|
|
512
|
+
if (c > 0xff) {
|
|
513
|
+
return null
|
|
514
|
+
}
|
|
515
|
+
bytes[i] = toU8(c)
|
|
516
|
+
i += 1
|
|
517
|
+
}
|
|
518
|
+
return bytes
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/** `percentDecode` of `src[off, off + len)` as a new string, or null on an error or when the result is not UTF-8. */
|
|
522
|
+
const urlDecodeString = (src: u8[], off: i32, len: i32, flags: i32): string | null => {
|
|
523
|
+
const out: u8[] = new Array<u8>(len)
|
|
524
|
+
const n: i32 = percentDecode(src, off, len, out, 0, flags)
|
|
525
|
+
if (n < 0 || !urlIsUtf8(out, 0, n)) {
|
|
526
|
+
return null
|
|
527
|
+
}
|
|
528
|
+
return String.fromBytes(out, 0, n)
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* `s` percent-decoded with `flags`, or null when `percentDecode` refuses it
|
|
533
|
+
* or what it decodes to is not valid UTF-8, which is where
|
|
534
|
+
* `decodeURIComponent` throws. `s` is expected to be ASCII, as a URL on the
|
|
535
|
+
* wire is. It allocates its answer.
|
|
536
|
+
*/
|
|
537
|
+
export const urlDecode = (s: string, flags: i32): string | null => {
|
|
538
|
+
const bytes: u8[] | null = urlBytesOf(s)
|
|
539
|
+
if (bytes === null) {
|
|
540
|
+
return null
|
|
541
|
+
}
|
|
542
|
+
return urlDecodeString(bytes, 0, toI32(bytes.length), flags)
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* The first value for `name` in the query of the request target `target`,
|
|
547
|
+
* form-decoded strictly: `""` for a key written without `=`, as
|
|
548
|
+
* `URLSearchParams.get` answers, and null when the target has no query, the
|
|
549
|
+
* key is absent, the value is malformed or not UTF-8, or the key comes after
|
|
550
|
+
* `URL_DEFAULT_MAX_PAIRS` pairs. It allocates its answer.
|
|
551
|
+
*/
|
|
552
|
+
export const urlQueryGet = (target: string, name: string): string | null => {
|
|
553
|
+
const bytes: u8[] | null = urlBytesOf(target)
|
|
554
|
+
if (bytes === null) {
|
|
555
|
+
return null
|
|
556
|
+
}
|
|
557
|
+
const len: i32 = toI32(bytes.length)
|
|
558
|
+
const q: i32 = urlQueryStart(bytes, 0, len)
|
|
559
|
+
if (q < 0) {
|
|
560
|
+
return null
|
|
561
|
+
}
|
|
562
|
+
const query: UrlQuery = new UrlQuery(new UrlQueryLimits())
|
|
563
|
+
query.reset(bytes, q + 1, len - q - 1)
|
|
564
|
+
if (urlQueryFind(query, name) !== URL_QUERY_PAIR) {
|
|
565
|
+
return null
|
|
566
|
+
}
|
|
567
|
+
if (!query.hasValue) {
|
|
568
|
+
return ""
|
|
569
|
+
}
|
|
570
|
+
return urlDecodeString(bytes, query.valueStart, query.valueLength, URL_DECODE_FORM)
|
|
571
|
+
}
|
package/std/random.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `std/random` — a seeded generator of pseudo-random numbers whose every
|
|
3
|
+
* answer is the same natively and under Node.
|
|
4
|
+
*
|
|
5
|
+
* import { Rng } from "nish/random";
|
|
6
|
+
*
|
|
7
|
+
* const r = new Rng(42);
|
|
8
|
+
* const die: i32 = 1 + (r.nextI32() >>> 1) % 6;
|
|
9
|
+
* const u: f64 = r.nextF64(); // in [0, 1)
|
|
10
|
+
*
|
|
11
|
+
* The state is one 32-bit integer. Each step adds the odd constant
|
|
12
|
+
* 0x9E3779B9 (2^32 divided by the golden ratio) to it, wrapping, so the
|
|
13
|
+
* states walk all 2^32 values before any repeats, whatever the seed — 0
|
|
14
|
+
* included. Each answer is MurmurHash3's 32-bit finaliser applied to the new
|
|
15
|
+
* state (Austin Appleby's `fmix32`; MurmurHash3 is public domain, and these
|
|
16
|
+
* are its constants): two multiplies and three shifted exclusive ors that mix
|
|
17
|
+
* every bit of the state into every bit of the answer, so seeds 1, 2 and 3
|
|
18
|
+
* answer three unrelated sequences. That pairing of a Weyl sequence with a
|
|
19
|
+
* mixing function is the shape of SplitMix (Steele, Lea and Flood, "Fast
|
|
20
|
+
* splittable pseudorandom number generators", OOPSLA 2014), on 32 bits.
|
|
21
|
+
*
|
|
22
|
+
* **The same bits under Node.** The two operations that wrap are spelled
|
|
23
|
+
* `wrappingAdd` and `wrappingMul` from `nish:unsafe`, which define the
|
|
24
|
+
* two's-complement wrap natively and answer `(a + b) | 0` and `Math.imul(a, b)`
|
|
25
|
+
* under Node (docs/LANGUAGE.md, "nish:unsafe"); every shift has a constant
|
|
26
|
+
* count, and `^` and `>>> 16` answer the same 32 bits in JavaScript as in the
|
|
27
|
+
* compiled program. `nextF64` builds its double from integers below 2^53, so
|
|
28
|
+
* its products and sums are exact both ways. The program a test prints under
|
|
29
|
+
* Node is the one it prints compiled (docs/RUN_UNDER_NODE.md).
|
|
30
|
+
*
|
|
31
|
+
* It is not for cryptography: a key or a nonce comes from
|
|
32
|
+
* `crypto.getRandomValues`.
|
|
33
|
+
*
|
|
34
|
+
* An `Rng` is an ordinary object, so a parallel body or a task that makes one
|
|
35
|
+
* of its own may step it as it likes: what a function allocates is its own
|
|
36
|
+
* memory (docs/LANGUAGE.md, "Data parallelism"). One shared by several tasks
|
|
37
|
+
* would be a write their caller can see, and is refused as any such write is;
|
|
38
|
+
* give each task its own, seeded from its index.
|
|
39
|
+
*/
|
|
40
|
+
import { wrappingAdd, wrappingMul } from "nish:unsafe"
|
|
41
|
+
|
|
42
|
+
/** 0x9E3779B9 as an `i32`: the Weyl increment, odd, so the walk visits every state. */
|
|
43
|
+
const GOLDEN: i32 = -1640531527
|
|
44
|
+
|
|
45
|
+
/** 0x85EBCA6B as an `i32`: MurmurHash3's first finaliser multiplier (public domain). */
|
|
46
|
+
const MIX_1: i32 = -2048144789
|
|
47
|
+
|
|
48
|
+
/** 0xC2B2AE35 as an `i32`: MurmurHash3's second finaliser multiplier (public domain). */
|
|
49
|
+
const MIX_2: i32 = -1028477387
|
|
50
|
+
|
|
51
|
+
/** 2^26, the weight of the high draw in `nextF64`'s 53 bits. */
|
|
52
|
+
const TWO_26: f64 = 67108864.0
|
|
53
|
+
|
|
54
|
+
/** 2^53, which scales a 53-bit integer into [0, 1). */
|
|
55
|
+
const TWO_53: f64 = 9007199254740992.0
|
|
56
|
+
|
|
57
|
+
/** MurmurHash3's `fmix32`: every bit of `h` into every bit of the answer, a bijection on 32 bits. */
|
|
58
|
+
const mix = (h: i32): i32 => {
|
|
59
|
+
let x: i32 = h
|
|
60
|
+
x = x ^ (x >>> 16)
|
|
61
|
+
x = wrappingMul(x, MIX_1)
|
|
62
|
+
x = x ^ (x >>> 13)
|
|
63
|
+
x = wrappingMul(x, MIX_2)
|
|
64
|
+
x = x ^ (x >>> 16)
|
|
65
|
+
return x
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** A generator of pseudo-random numbers: a Weyl sequence from its seed, each state mixed. */
|
|
69
|
+
export class Rng {
|
|
70
|
+
/** Where the walk is: the seed plus the increment times the steps taken, wrapped. */
|
|
71
|
+
state: i32
|
|
72
|
+
|
|
73
|
+
/** A generator whose sequence `seed` decides: two made from one seed answer the same numbers. */
|
|
74
|
+
constructor(seed: i32) {
|
|
75
|
+
this.state = seed
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The next 32 bits, as an `i32`: over 2^32 steps each value is answered exactly once. */
|
|
79
|
+
nextI32(): i32 {
|
|
80
|
+
this.state = wrappingAdd(this.state, GOLDEN)
|
|
81
|
+
return mix(this.state)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* The next number in [0, 1), from two steps: the high 27 bits of one and
|
|
86
|
+
* the high 26 of the next make a 53-bit integer, which a double holds
|
|
87
|
+
* exactly, scaled by 2^-53.
|
|
88
|
+
*/
|
|
89
|
+
nextF64(): f64 {
|
|
90
|
+
const hi: i32 = this.nextI32() >>> 5
|
|
91
|
+
const lo: i32 = this.nextI32() >>> 6
|
|
92
|
+
return (toF64(hi) * TWO_26 + toF64(lo)) / TWO_53
|
|
93
|
+
}
|
|
94
|
+
}
|