@amritk/nish-x86_64-linux 0.23.1 → 0.25.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.
Files changed (51) hide show
  1. package/bin/nish +0 -0
  2. package/docs/AI.md +50 -23
  3. package/package.json +1 -1
  4. package/runtime/browser.mjs +4 -3
  5. package/runtime/core/arrays.d.ts +0 -2
  6. package/runtime/core/arrays.mjs +0 -2
  7. package/runtime/core/host.d.ts +8 -0
  8. package/runtime/core/host.mjs +50 -2
  9. package/runtime/core/numbers.mjs +2 -3
  10. package/runtime/nish.d.ts +100 -4
  11. package/runtime/nish.h +34 -0
  12. package/runtime/nish.mjs +23 -5
  13. package/runtime/node.mjs +57 -6
  14. package/runtime/runtime-net.c +45 -4
  15. package/runtime/runtime-os.c +115 -12
  16. package/runtime/runtime-term.c +2 -2
  17. package/runtime/runtime.c +1 -1
  18. package/scripts/build.sh +13 -3
  19. package/std/README.md +44 -7
  20. package/std/cli-args.ts +1216 -0
  21. package/std/cli-flags.ts +585 -0
  22. package/std/cli.ts +308 -19
  23. package/std/compress/checksum.ts +290 -0
  24. package/std/compress/deflate.ts +2790 -0
  25. package/std/compress/gzip.ts +761 -0
  26. package/std/compress/zlib.ts +168 -0
  27. package/std/crypto/LICENSE-fiat-crypto-p384 +21 -0
  28. package/std/crypto/p256.ts +63 -1
  29. package/std/crypto/p384.ts +6044 -0
  30. package/std/crypto/pki-roots.ts +322 -0
  31. package/std/crypto/pki.ts +1207 -0
  32. package/std/crypto/rsa.ts +663 -0
  33. package/std/crypto/sha256.ts +2 -3
  34. package/std/crypto/x25519.ts +3 -4
  35. package/std/crypto/x509.ts +1054 -58
  36. package/std/net/dns-config.ts +734 -0
  37. package/std/net/dns-message.ts +517 -0
  38. package/std/net/dns-resolver.ts +1015 -0
  39. package/std/net/dns.ts +478 -0
  40. package/std/net/http-compress.ts +393 -0
  41. package/std/net/http1-client.ts +1227 -0
  42. package/std/net/http1-server.ts +101 -1
  43. package/std/net/http1.ts +9 -9
  44. package/std/net/tls/client.ts +1094 -0
  45. package/std/net/tls/codec.ts +585 -3
  46. package/std/net/tls/record-client.ts +1192 -0
  47. package/std/net/tls/verify.ts +221 -0
  48. package/std/net/tls-tcp.ts +642 -11
  49. package/std/net/tls.ts +2 -2
  50. package/std/net/url.ts +559 -4
  51. package/std/yaml-write.ts +415 -0
@@ -0,0 +1,393 @@
1
+ /**
2
+ * `nish/net/http-compress` — content coding for HTTP responses (RFC 9110
3
+ * §8.4 and §12.5.3): reading a request's `Accept-Encoding` and compressing a
4
+ * body with `Content-Encoding: gzip`, for any of the three servers.
5
+ *
6
+ * import { DEFLATE_FAST } from "nish/compress/deflate";
7
+ * import { HTTP_CODING_GZIP, HttpCompressor } from "nish/net/http-compress";
8
+ *
9
+ * const enc = new HttpCompressor(DEFLATE_FAST, 1024, 65536); // once per thread
10
+ * // HTTP/1.1, in one call:
11
+ * conn.respondEncoded(200, names, values, body, 0, n, enc, conn.acceptEncoding());
12
+ * // HTTP/2 or HTTP/3, by hand:
13
+ * const coding: i32 = httpAcceptEncoding(value, 0, toI32(value.length), HTTP_OFFER_GZIP);
14
+ * const m: i32 = enc.compress(coding, body, 0, n);
15
+ * if (m >= 0) {
16
+ * enc.addFields(names, values);
17
+ * h2.respond(id, 200, enc.names, enc.values, false);
18
+ * h2.write(id, enc.output, 0, m);
19
+ * }
20
+ *
21
+ * **Opt-in.** Nothing here runs unless the program asks: it decides which
22
+ * responses to compress, and `HttpCompressor` declines any body shorter than
23
+ * its `minLength`, longer than its `maxLength`, or that gzip would not make
24
+ * smaller, answering -1 so the program sends the bytes as they are.
25
+ *
26
+ * **Negotiation** (`httpAcceptEncoding`) follows §12.5.3: each listed coding
27
+ * with its q-value, `x-gzip` as `gzip`, `*` for every coding not listed, and
28
+ * `identity` acceptable unless it is given q=0, directly or through `*`. Of
29
+ * the codings the program offers, the most preferred with q above 0 wins, the
30
+ * program's order (gzip, then deflate) breaking ties; it beats `identity` at
31
+ * an equal q, and beats an `identity` the field does not mention at any q. With no field at all the answer is
32
+ * `HTTP_CODING_IDENTITY` (the RFC allows any coding then, but a client that
33
+ * says nothing is safest sent nothing it may not read); an empty field asks
34
+ * for identity too. `HTTP_CODING_NONE` means nothing offered is acceptable
35
+ * and identity is refused: a server may answer 406 or send identity anyway.
36
+ * A malformed element is skipped. The parser allocates nothing.
37
+ *
38
+ * **Compressing allocates nothing**: `HttpCompressor` holds a
39
+ * `GzipCompressor`, an output buffer of `compressBound(maxLength)` bytes and
40
+ * the field arrays `addFields` fills, all built once.
41
+ * `HTTP_CODING_DEFLATE` (zlib framing) is negotiated but not produced: a
42
+ * client that accepts only `deflate` is sent identity, which every client
43
+ * reads.
44
+ */
45
+ import { DEFLATE_FAST, compressBound } from "nish/compress/deflate"
46
+ import { GzipCompressor } from "nish/compress/gzip"
47
+
48
+ /** No content coding: the body as it is. */
49
+ export const HTTP_CODING_IDENTITY: i32 = 0
50
+
51
+ /** `gzip` (RFC 9110 §8.4.1.3), or its alias `x-gzip`. */
52
+ export const HTTP_CODING_GZIP: i32 = 1
53
+
54
+ /** `deflate`: the zlib format (RFC 9110 §8.4.1.2). */
55
+ export const HTTP_CODING_DEFLATE: i32 = 2
56
+
57
+ /** Nothing offered is acceptable, identity included (§12.5.3: 406, or identity anyway). */
58
+ export const HTTP_CODING_NONE: i32 = -1
59
+
60
+ /** `httpAcceptEncoding`'s `offered` bits: the program can send gzip, deflate. */
61
+ export const HTTP_OFFER_GZIP: i32 = 1
62
+ export const HTTP_OFFER_DEFLATE: i32 = 2
63
+
64
+ /** A q-value of 1, in thousandths. */
65
+ const HTTP_Q_ONE: i32 = 1000
66
+
67
+ /** Whether `c` is an ASCII letter, digit or one of the token characters a coding uses. */
68
+ const httpCodingChar = (c: i32): boolean =>
69
+ (c >= 97 && c <= 122) ||
70
+ (c >= 65 && c <= 90) ||
71
+ (c >= 48 && c <= 57) ||
72
+ c === 45 ||
73
+ c === 46 ||
74
+ c === 95 ||
75
+ c === 42 ||
76
+ c === 33 ||
77
+ c === 35 ||
78
+ c === 36 ||
79
+ c === 37 ||
80
+ c === 38 ||
81
+ c === 39 ||
82
+ c === 43 ||
83
+ c === 94 ||
84
+ c === 96 ||
85
+ c === 124 ||
86
+ c === 126
87
+
88
+ /** Whether `buf[start .. end)` is `word`, ignoring ASCII case; `word` is lower case. */
89
+ const httpCodingIs = (buf: u8[], start: i32, end: i32, word: string): boolean => {
90
+ if (end - start !== toI32(word.length)) {
91
+ return false
92
+ }
93
+ for (let i: i32 = 0; i < end - start && i < toI32(word.length); i += 1) {
94
+ let c: i32 = toI32(buf[start + i])
95
+ if (c >= 65 && c <= 90) {
96
+ c += 32
97
+ }
98
+ if (c !== toI32(word.charCodeAt(i))) {
99
+ return false
100
+ }
101
+ }
102
+ return true
103
+ }
104
+
105
+ /**
106
+ * The q-value of `buf[start .. end)`, in thousandths (RFC 9110 §12.4.2:
107
+ * `0`, `0.` and up to three digits, `1`, `1.` and up to three zeros), or -1
108
+ * when it is not one.
109
+ */
110
+ const httpQValue = (buf: u8[], start: i32, end: i32): i32 => {
111
+ if (start >= end) {
112
+ return -1
113
+ }
114
+ const lead: i32 = toI32(buf[start])
115
+ if (lead !== 48 && lead !== 49) {
116
+ return -1
117
+ }
118
+ let q: i32 = lead === 49 ? HTTP_Q_ONE : 0
119
+ if (start + 1 === end) {
120
+ return q
121
+ }
122
+ if (buf[start + 1] !== 46 || end - start > 5) {
123
+ return -1
124
+ }
125
+ let scale: i32 = 100
126
+ for (let i: i32 = start + 2; i < end && i >= 0 && i < toI32(buf.length); i += 1) {
127
+ const d: i32 = toI32(buf[i]) - 48
128
+ if (d < 0 || d > 9 || (lead === 49 && d !== 0)) {
129
+ return -1
130
+ }
131
+ q += d * scale
132
+ scale = scale / 10
133
+ }
134
+ return q
135
+ }
136
+
137
+ /** A coding's q: its own when listed, else `*`'s when that is, else 0. */
138
+ const httpQOrStar = (own: i32, star: i32): i32 => {
139
+ if (own >= 0) {
140
+ return own
141
+ }
142
+ return star >= 0 ? star : 0
143
+ }
144
+
145
+ /** Whether `c` is optional whitespace (SP or HTAB). */
146
+ const httpOws = (c: i32): boolean => c === 32 || c === 9
147
+
148
+ /**
149
+ * The content coding to send for the `Accept-Encoding` field value
150
+ * `buf[start .. end)` among the codings `offered` (`HTTP_OFFER_GZIP |
151
+ * HTTP_OFFER_DEFLATE`): `HTTP_CODING_GZIP`, `HTTP_CODING_DEFLATE`,
152
+ * `HTTP_CODING_IDENTITY` or `HTTP_CODING_NONE`, by the rules in the module
153
+ * comment. A window outside `buf` panics.
154
+ */
155
+ export const httpAcceptEncoding = (buf: u8[], start: i32, end: i32, offered: i32): i32 => {
156
+ if (start < 0 || start > end || end > toI32(buf.length)) {
157
+ panic("httpAcceptEncoding: the window lies outside its buffer")
158
+ }
159
+ let qGzip: i32 = -1
160
+ let qDeflate: i32 = -1
161
+ let qIdentity: i32 = -1
162
+ let qStar: i32 = -1
163
+ let p: i32 = start
164
+ while (p < end) {
165
+ // One element: OWS coding OWS *( ";" OWS name "=" value ) OWS ","
166
+ while (p < end && (httpOws(toI32(buf[p])) || buf[p] === 44)) {
167
+ p += 1
168
+ }
169
+ const codingStart: i32 = p
170
+ while (p < end && httpCodingChar(toI32(buf[p]))) {
171
+ p += 1
172
+ }
173
+ const codingEnd: i32 = p
174
+ let q: i32 = HTTP_Q_ONE
175
+ let valid: boolean = codingEnd > codingStart
176
+ while (p < end && buf[p] !== 44) {
177
+ while (p < end && httpOws(toI32(buf[p]))) {
178
+ p += 1
179
+ }
180
+ if (p >= end || buf[p] === 44) {
181
+ break
182
+ }
183
+ if (buf[p] !== 59) {
184
+ valid = false
185
+ while (p < end && buf[p] !== 44) {
186
+ p += 1
187
+ }
188
+ break
189
+ }
190
+ p += 1
191
+ while (p < end && httpOws(toI32(buf[p]))) {
192
+ p += 1
193
+ }
194
+ const nameStart: i32 = p
195
+ while (p < end && httpCodingChar(toI32(buf[p]))) {
196
+ p += 1
197
+ }
198
+ const nameEnd: i32 = p
199
+ if (p >= end || buf[p] !== 61) {
200
+ valid = false
201
+ continue
202
+ }
203
+ p += 1
204
+ const valueStart: i32 = p
205
+ while (p < end && httpCodingChar(toI32(buf[p]))) {
206
+ p += 1
207
+ }
208
+ if (httpCodingIs(buf, nameStart, nameEnd, "q")) {
209
+ q = httpQValue(buf, valueStart, p)
210
+ if (q < 0) {
211
+ valid = false
212
+ }
213
+ }
214
+ }
215
+ if (valid) {
216
+ if (
217
+ httpCodingIs(buf, codingStart, codingEnd, "gzip") ||
218
+ httpCodingIs(buf, codingStart, codingEnd, "x-gzip")
219
+ ) {
220
+ qGzip = q
221
+ } else if (httpCodingIs(buf, codingStart, codingEnd, "deflate")) {
222
+ qDeflate = q
223
+ } else if (httpCodingIs(buf, codingStart, codingEnd, "identity")) {
224
+ qIdentity = q
225
+ } else if (httpCodingIs(buf, codingStart, codingEnd, "*")) {
226
+ qStar = q
227
+ }
228
+ }
229
+ }
230
+ const gzip: i32 = (offered & HTTP_OFFER_GZIP) === 0 ? 0 : httpQOrStar(qGzip, qStar)
231
+ const deflate: i32 = (offered & HTTP_OFFER_DEFLATE) === 0 ? 0 : httpQOrStar(qDeflate, qStar)
232
+ // `identity` unlisted (and not reached by `*`) is acceptable but stated no
233
+ // preference, so any offered coding with q above 0 is sent before it.
234
+ let identity: i32 = -1
235
+ if (qIdentity >= 0) {
236
+ identity = qIdentity
237
+ } else if (qStar >= 0) {
238
+ identity = qStar
239
+ }
240
+ let coding: i32 = HTTP_CODING_NONE
241
+ let codingQ: i32 = 0
242
+ if (deflate > gzip) {
243
+ coding = HTTP_CODING_DEFLATE
244
+ codingQ = deflate
245
+ } else if (gzip > 0) {
246
+ coding = HTTP_CODING_GZIP
247
+ codingQ = gzip
248
+ }
249
+ if (coding !== HTTP_CODING_NONE && codingQ >= identity) {
250
+ return coding
251
+ }
252
+ if (identity !== 0) {
253
+ return HTTP_CODING_IDENTITY
254
+ }
255
+ return HTTP_CODING_NONE
256
+ }
257
+
258
+ /** The bytes of an ASCII string. */
259
+ const httpCompressBytes = (text: string): u8[] => {
260
+ const out: u8[] = new Array<u8>(toI32(text.length))
261
+ for (let i: i32 = 0; i < toI32(text.length) && i < toI32(out.length); i += 1) {
262
+ out[i] = toU8(text.charCodeAt(i))
263
+ }
264
+ return out
265
+ }
266
+
267
+ /**
268
+ * Compresses response bodies with gzip into a buffer of its own, and builds
269
+ * the response fields that say so. One per thread; allocated once.
270
+ */
271
+ export class HttpCompressor {
272
+ readonly gzip: GzipCompressor
273
+ /** Where `compress` writes; valid until the next call. */
274
+ readonly output: u8[]
275
+ /** The fields `addFields` built: the program's, then `Content-Encoding` and `Vary`. */
276
+ readonly names: u8[][]
277
+ readonly values: u8[][]
278
+ readonly contentEncodingName: u8[]
279
+ readonly gzipValue: u8[]
280
+ readonly varyName: u8[]
281
+ readonly acceptEncodingValue: u8[]
282
+ /** The shortest body worth compressing, and the longest the output can hold. */
283
+ readonly minLength: i32
284
+ readonly maxLength: i32
285
+ /**
286
+ * Whether `addFields` adds `Vary: Accept-Encoding` (RFC 9110 §12.5.5), which
287
+ * a cache needs whenever the same resource may be sent either way. On by
288
+ * default; turn it off when the program sets its own `Vary`.
289
+ */
290
+ vary: boolean
291
+
292
+ /**
293
+ * A compressor at `level` (`DEFLATE_FAST` suits a server) for bodies of
294
+ * `minLength` to `maxLength` bytes. A level outside 0 to 9, a negative
295
+ * `minLength` or a `maxLength` below it panics.
296
+ */
297
+ constructor(level: i32, minLength: i32, maxLength: i32) {
298
+ if (minLength < 0 || maxLength < minLength) {
299
+ panic("HttpCompressor: the lengths are out of order")
300
+ }
301
+ this.gzip = new GzipCompressor(level)
302
+ this.output = new Array<u8>(compressBound(maxLength))
303
+ this.names = []
304
+ this.values = []
305
+ this.contentEncodingName = httpCompressBytes("content-encoding")
306
+ this.gzipValue = httpCompressBytes("gzip")
307
+ this.varyName = httpCompressBytes("vary")
308
+ this.acceptEncodingValue = httpCompressBytes("accept-encoding")
309
+ this.minLength = minLength
310
+ this.maxLength = maxLength
311
+ this.vary = true
312
+ }
313
+
314
+ /**
315
+ * Compresses `body[off .. off + len)` into `output` when `coding` is
316
+ * `HTTP_CODING_GZIP`, the length is in `[minLength, maxLength]` and gzip
317
+ * makes it smaller; answers the compressed length, or -1 to send the body
318
+ * as it is. A window outside `body` panics.
319
+ */
320
+ compress(coding: i32, body: u8[], off: i32, len: i32): i32 {
321
+ if (off < 0 || len < 0 || off > toI32(body.length) - len) {
322
+ panic("HttpCompressor.compress: the window lies outside its body")
323
+ }
324
+ if (coding !== HTTP_CODING_GZIP || len < this.minLength || len > this.maxLength) {
325
+ return -1
326
+ }
327
+ const n: i32 = this.gzip.compress(body, off, len, this.output, toI32(0))
328
+ if (n < 0 || n >= len) {
329
+ return -1
330
+ }
331
+ return n
332
+ }
333
+
334
+ /**
335
+ * Sets `names` / `values` to the program's fields followed by
336
+ * `Content-Encoding: gzip` and, when `vary`, `Vary: Accept-Encoding`. The
337
+ * arrays are reused, so after the first few calls this allocates nothing;
338
+ * they hold until the next call.
339
+ */
340
+ addFields(names: u8[][], values: u8[][]): void {
341
+ while (this.names.length > 0) {
342
+ this.names.pop()
343
+ }
344
+ while (this.values.length > 0) {
345
+ this.values.pop()
346
+ }
347
+ for (let i: i32 = 0; i < toI32(values.length); i += 1) {
348
+ if (i < toI32(names.length)) {
349
+ const name: u8[] = names[i]
350
+ const value: u8[] = values[i]
351
+ this.names.push(name)
352
+ this.values.push(value)
353
+ }
354
+ }
355
+ this.names.push(this.contentEncodingName)
356
+ this.values.push(this.gzipValue)
357
+ this.addVary()
358
+ }
359
+
360
+ /** Appends `Vary: Accept-Encoding` to the arrays `addFields` built when `vary` is set. */
361
+ addVary(): void {
362
+ if (this.vary) {
363
+ this.names.push(this.varyName)
364
+ this.values.push(this.acceptEncodingValue)
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Sets `names` / `values` to the program's fields and, when `vary`,
370
+ * `Vary: Accept-Encoding`: the fields of a body sent as it is from a
371
+ * resource that is sometimes compressed.
372
+ */
373
+ identityFields(names: u8[][], values: u8[][]): void {
374
+ while (this.names.length > 0) {
375
+ this.names.pop()
376
+ }
377
+ while (this.values.length > 0) {
378
+ this.values.pop()
379
+ }
380
+ for (let i: i32 = 0; i < toI32(values.length); i += 1) {
381
+ if (i < toI32(names.length)) {
382
+ const name: u8[] = names[i]
383
+ const value: u8[] = values[i]
384
+ this.names.push(name)
385
+ this.values.push(value)
386
+ }
387
+ }
388
+ this.addVary()
389
+ }
390
+ }
391
+
392
+ /** A compressor for the usual server: `DEFLATE_FAST`, bodies of 1 KB to 1 MB. */
393
+ export const httpCompressorDefault = (): HttpCompressor => new HttpCompressor(DEFLATE_FAST, 1024, 1048576)