@amritk/nish-x86_64-linux 0.24.0 → 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 (42) hide show
  1. package/bin/nish +0 -0
  2. package/docs/AI.md +24 -4
  3. package/package.json +1 -1
  4. package/runtime/browser.mjs +4 -3
  5. package/runtime/core/host.d.ts +8 -0
  6. package/runtime/core/host.mjs +50 -2
  7. package/runtime/nish.d.ts +100 -0
  8. package/runtime/nish.h +34 -0
  9. package/runtime/nish.mjs +21 -0
  10. package/runtime/node.mjs +57 -6
  11. package/runtime/runtime-net.c +44 -3
  12. package/runtime/runtime-os.c +115 -12
  13. package/scripts/build.sh +13 -3
  14. package/std/README.md +25 -8
  15. package/std/cli-args.ts +1216 -0
  16. package/std/cli-flags.ts +585 -0
  17. package/std/cli.ts +308 -19
  18. package/std/compress/checksum.ts +132 -20
  19. package/std/compress/deflate.ts +638 -248
  20. package/std/compress/gzip.ts +20 -6
  21. package/std/crypto/LICENSE-fiat-crypto-p384 +21 -0
  22. package/std/crypto/p256.ts +63 -1
  23. package/std/crypto/p384.ts +6044 -0
  24. package/std/crypto/pki-roots.ts +322 -0
  25. package/std/crypto/pki.ts +1207 -0
  26. package/std/crypto/rsa.ts +663 -0
  27. package/std/crypto/x509.ts +1054 -58
  28. package/std/net/dns-config.ts +734 -0
  29. package/std/net/dns-message.ts +517 -0
  30. package/std/net/dns-resolver.ts +1015 -0
  31. package/std/net/dns.ts +478 -0
  32. package/std/net/http1-client.ts +1227 -0
  33. package/std/net/http1-server.ts +7 -2
  34. package/std/net/http1.ts +9 -9
  35. package/std/net/tls/client.ts +1094 -0
  36. package/std/net/tls/codec.ts +585 -3
  37. package/std/net/tls/record-client.ts +1192 -0
  38. package/std/net/tls/verify.ts +221 -0
  39. package/std/net/tls-tcp.ts +642 -11
  40. package/std/net/tls.ts +2 -2
  41. package/std/net/url.ts +559 -4
  42. package/std/yaml-write.ts +415 -0
@@ -0,0 +1,734 @@
1
+ /**
2
+ * `nish/net/dns-config` — `/etc/resolv.conf` and `/etc/hosts`, read from bytes
3
+ * the caller already has into fixed tables.
4
+ *
5
+ * Both files are local, but neither is trusted to be small or well formed: a
6
+ * container image, a DHCP client or a VPN writes them, and a resolver that
7
+ * believed a 40,000-entry search list or a line a megabyte long would spend a
8
+ * lookup on it. This module reads each into tables of fixed, documented size,
9
+ * and counts what did not fit rather than growing:
10
+ *
11
+ * import { DnsConfig, DnsHosts, dnsHostsFind, dnsParseHosts, dnsParseResolvConf } from "nish/net/dns-config"
12
+ * import { DNS_TYPE_A } from "nish/net/dns-message"
13
+ *
14
+ * const config: DnsConfig = new DnsConfig()
15
+ * dnsParseResolvConf(resolvBytes, 0, resolvLen, config)
16
+ * // config.servers[i * 18, +18) for i < config.serverCount, on port 53
17
+ * const hosts: DnsHosts = new DnsHosts()
18
+ * dnsParseHosts(hostsBytes, 0, hostsLen, hosts)
19
+ * const found: i32 = dnsHostsFind(hosts, "localhost", DNS_TYPE_A, 80, out, 8)
20
+ *
21
+ * **resolv.conf** is read as glibc reads it, minus what a stub resolver does
22
+ * not use. `nameserver` takes one IPv4 or IPv6 literal (no zone), and the first
23
+ * `DNS_MAX_SERVERS` (3) are kept; `search` and `domain` each replace the list,
24
+ * so the last one wins, and it holds at most `DNS_MAX_SEARCH` (6) domains and
25
+ * `DNS_MAX_SEARCH_BYTES` (256) bytes; `options` reads `ndots:n` (clamped to 0
26
+ * to 15), `timeout:n` (1 to 30 seconds) and `attempts:n` (1 to 5) and ignores
27
+ * every other option. With no nameserver line, the servers are 127.0.0.1 and
28
+ * ::1, as glibc's are.
29
+ *
30
+ * **hosts** is an address and then its names, one entry a line. At most
31
+ * `DNS_HOSTS_MAX_ENTRIES` entries and `DNS_HOSTS_MAX_NAMES` names are kept,
32
+ * in at most `DNS_HOSTS_MAX_NAME_BYTES` bytes; a name past a cap is dropped and
33
+ * counted. Names are kept in lowercase and found without case, with one
34
+ * trailing `.` ignored on either side; an IPv4 address answers only an A
35
+ * lookup and an IPv6 address only an AAAA one, `::ffff:a.b.c.d` being IPv4.
36
+ *
37
+ * **In both**, `#` and `;` start a comment that runs to the end of the line, a
38
+ * line no directive names is ignored, and a line longer than
39
+ * `DNS_CONFIG_MAX_LINE` bytes is skipped whole and counted in
40
+ * `skippedLines`, never cut to a shorter line that means something else.
41
+ * A window outside its array is a bug in the caller and panics; nothing in a
42
+ * file does.
43
+ *
44
+ * Addresses are the 18-byte form of `docs/LANGUAGE.md`, which
45
+ * `dnsParseAddress` writes from a literal. The record of what this module
46
+ * refuses and why is `docs/security/dns.md`.
47
+ */
48
+ import { setU16BE, skipBlank } from "nish/bytes"
49
+ import { DNS_ADDRESS_BYTES, DNS_TYPE_A, DNS_TYPE_AAAA } from "nish/net/dns-message"
50
+
51
+ /** How many `nameserver` lines are kept; glibc's MAXNS. */
52
+ export const DNS_MAX_SERVERS: i32 = 3
53
+ /** How many search domains are kept. */
54
+ export const DNS_MAX_SEARCH: i32 = 6
55
+ /** How many bytes the search domains may take together. */
56
+ export const DNS_MAX_SEARCH_BYTES: i32 = 256
57
+ /** The longest line either parser reads; a longer one is skipped whole. */
58
+ export const DNS_CONFIG_MAX_LINE: i32 = 1024
59
+ /** The port every `nameserver` address carries. */
60
+ export const DNS_PORT: i32 = 53
61
+ /** The `ndots` without an option: a name with a dot is tried as it stands first. */
62
+ export const DNS_DEFAULT_NDOTS: i32 = 1
63
+ /** The highest `ndots:n` kept; a higher one is clamped to it, as glibc clamps. */
64
+ export const DNS_MAX_NDOTS: i32 = 15
65
+ /** The `timeout` without an option, in seconds. */
66
+ export const DNS_DEFAULT_TIMEOUT: i32 = 5
67
+ /** The highest `timeout:n` kept, in seconds; the lowest is 1. */
68
+ export const DNS_MAX_TIMEOUT: i32 = 30
69
+ /** The `attempts` without an option. */
70
+ export const DNS_DEFAULT_ATTEMPTS: i32 = 2
71
+ /** The highest `attempts:n` kept; the lowest is 1. */
72
+ export const DNS_MAX_ATTEMPTS: i32 = 5
73
+
74
+ /** How many hosts lines are kept. */
75
+ export const DNS_HOSTS_MAX_ENTRIES: i32 = 256
76
+ /** How many names all hosts entries may hold together. */
77
+ export const DNS_HOSTS_MAX_NAMES: i32 = 1024
78
+ /** How many bytes those names may take together. */
79
+ export const DNS_HOSTS_MAX_NAME_BYTES: i32 = 16384
80
+ /** The longest hosts name kept, the longest a DNS name can be. */
81
+ export const DNS_HOSTS_MAX_NAME: i32 = 253
82
+
83
+ const DNS_TAB: i32 = 9
84
+ const DNS_NEWLINE: i32 = 10
85
+ const DNS_RETURN: i32 = 13
86
+ const DNS_SPACE: i32 = 32
87
+ const DNS_HASH: i32 = 35
88
+ const DNS_DOT: i32 = 46
89
+ const DNS_COLON: i32 = 58
90
+ const DNS_SEMICOLON: i32 = 59
91
+
92
+ /** One resolv.conf, read by `dnsParseResolvConf`, made once and reusable. */
93
+ export class DnsConfig {
94
+ /** `serverCount` addresses of `DNS_ADDRESS_BYTES` each, on `DNS_PORT`. */
95
+ servers: u8[]
96
+ /** Sixteen bytes a literal is parsed into before it is kept, so a parse allocates nothing. */
97
+ scratch: u8[]
98
+ serverCount: i32 = 0
99
+ /** Nameserver lines past `DNS_MAX_SERVERS`, read and not kept. */
100
+ droppedServers: i32 = 0
101
+ /** The search domains, back to back, lowercase, without a trailing dot. */
102
+ search: u8[]
103
+ /** Where each of the `searchCount` domains starts in `search`, and its length. */
104
+ searchStart: i32[]
105
+ searchLength: i32[]
106
+ searchCount: i32 = 0
107
+ /** Search domains past `DNS_MAX_SEARCH` or `DNS_MAX_SEARCH_BYTES`, read and not kept. */
108
+ droppedSearch: i32 = 0
109
+ /** Dots a name needs to be tried as it stands before the search list. */
110
+ ndots: i32
111
+ /** Seconds to wait for one server's reply. */
112
+ timeout: i32
113
+ /** Rounds through the servers before giving up. */
114
+ attempts: i32
115
+ /** Lines longer than `DNS_CONFIG_MAX_LINE`, skipped whole. */
116
+ skippedLines: i32 = 0
117
+
118
+ constructor() {
119
+ this.servers = new Array<u8>(DNS_MAX_SERVERS * DNS_ADDRESS_BYTES)
120
+ this.scratch = new Array<u8>(16)
121
+ this.search = new Array<u8>(DNS_MAX_SEARCH_BYTES)
122
+ this.searchStart = new Array<i32>(DNS_MAX_SEARCH)
123
+ this.searchLength = new Array<i32>(DNS_MAX_SEARCH)
124
+ this.ndots = DNS_DEFAULT_NDOTS
125
+ this.timeout = DNS_DEFAULT_TIMEOUT
126
+ this.attempts = DNS_DEFAULT_ATTEMPTS
127
+ }
128
+ }
129
+
130
+ /** One hosts file, read by `dnsParseHosts`, made once and reusable. */
131
+ export class DnsHosts {
132
+ /** `entryCount` addresses of `DNS_ADDRESS_BYTES` each, port 0. */
133
+ addresses: u8[]
134
+ /** Every kept name, back to back, lowercase, without a trailing dot. */
135
+ names: u8[]
136
+ /** Sixteen bytes a literal is parsed into before it is kept, so a parse allocates nothing. */
137
+ scratch: u8[]
138
+ /** Where each of the `nameCount` names starts in `names`, its length, and its entry. */
139
+ nameStart: i32[]
140
+ nameLength: i32[]
141
+ nameEntry: i32[]
142
+ entryCount: i32 = 0
143
+ nameCount: i32 = 0
144
+ nameBytes: i32 = 0
145
+ /** Lines with an address past `DNS_HOSTS_MAX_ENTRIES`, read and not kept. */
146
+ droppedEntries: i32 = 0
147
+ /** Names past a cap, or longer than `DNS_HOSTS_MAX_NAME`, read and not kept. */
148
+ droppedNames: i32 = 0
149
+ /** Lines longer than `DNS_CONFIG_MAX_LINE`, skipped whole. */
150
+ skippedLines: i32 = 0
151
+
152
+ constructor() {
153
+ this.addresses = new Array<u8>(DNS_HOSTS_MAX_ENTRIES * DNS_ADDRESS_BYTES)
154
+ this.names = new Array<u8>(DNS_HOSTS_MAX_NAME_BYTES)
155
+ this.scratch = new Array<u8>(16)
156
+ this.nameStart = new Array<i32>(DNS_HOSTS_MAX_NAMES)
157
+ this.nameLength = new Array<i32>(DNS_HOSTS_MAX_NAMES)
158
+ this.nameEntry = new Array<i32>(DNS_HOSTS_MAX_NAMES)
159
+ }
160
+ }
161
+
162
+ /** Panics unless `bytes[off, off + len)` lies inside `bytes`. */
163
+ const dnsConfigCheckWindow = (bytes: u8[], off: i32, len: i32, who: string): void => {
164
+ const size: i32 = toI32(bytes.length)
165
+ if (off < 0 || len < 0 || off > size || len > size - off) {
166
+ panic(who + ": the window is outside the array")
167
+ }
168
+ }
169
+
170
+ /** Whether `b` separates words on a line. */
171
+ const dnsIsBlank = (b: i32): boolean => b === DNS_SPACE || b === DNS_TAB || b === DNS_RETURN
172
+
173
+ /** The value of one hex digit, either case, or -1. */
174
+ const dnsHexValue = (b: i32): i32 => {
175
+ if (b >= 48 && b <= 57) {
176
+ return b - 48
177
+ }
178
+ if (b >= 65 && b <= 70) {
179
+ return b - 55
180
+ }
181
+ if (b >= 97 && b <= 102) {
182
+ return b - 87
183
+ }
184
+ return -1
185
+ }
186
+
187
+ /**
188
+ * Writes the dotted quad `b[from, to)` as its four bytes at `out[at]`, and
189
+ * answers whether it was one: four decimal parts of 0 to 255, each without a
190
+ * leading zero, so `010` is never read as octal 8 the way `inet_aton` reads it.
191
+ */
192
+ const dnsParseIpv4 = (b: u8[], from: i32, to: i32, out: u8[], at: i32): boolean => {
193
+ if (from < 0 || from > to || to > toI32(b.length)) {
194
+ panic("dnsParseIpv4: the window is outside the array")
195
+ }
196
+ let i: i32 = from
197
+ let part: i32 = 0
198
+ while (part < 4) {
199
+ const start: i32 = i
200
+ let value: i32 = 0
201
+ while (i < to && i - start < 3 && toI32(b[i]) >= 48 && toI32(b[i]) <= 57) {
202
+ value = value * 10 + toI32(b[i]) - 48
203
+ i += 1
204
+ }
205
+ const digits: i32 = i - start
206
+ // A part of two or three digits worth less than 10 or 100 has a leading zero.
207
+ if (digits === 0 || value > 255 || (digits === 2 && value < 10) || (digits === 3 && value < 100)) {
208
+ return false
209
+ }
210
+ out[at + part] = toU8(value)
211
+ part += 1
212
+ if (part < 4) {
213
+ if (i >= to || toI32(b[i]) !== DNS_DOT) {
214
+ return false
215
+ }
216
+ i += 1
217
+ }
218
+ }
219
+ return i === to
220
+ }
221
+
222
+ /**
223
+ * Writes the IPv6 literal `b[from, to)` as sixteen bytes at `out[at]`, and
224
+ * answers whether it was one: eight groups of one to four hex digits, at most
225
+ * one `::` standing for one or more zero groups, and a dotted quad allowed in
226
+ * place of the last two groups.
227
+ */
228
+ const dnsParseIpv6 = (b: u8[], from: i32, to: i32, out: u8[], at: i32): boolean => {
229
+ if (from < 0 || from > to || to > toI32(b.length)) {
230
+ panic("dnsParseIpv6: the window is outside the array")
231
+ }
232
+ let i: i32 = from
233
+ let groups: i32 = 0
234
+ let gap: i32 = -1
235
+ if (to - i >= 2 && toI32(b[i]) === DNS_COLON && toI32(b[i + 1]) === DNS_COLON) {
236
+ gap = 0
237
+ i += 2
238
+ }
239
+ while (i < to) {
240
+ if (groups === 8 || i < 0 || i >= toI32(b.length)) {
241
+ return false
242
+ }
243
+ let j: i32 = i
244
+ let value: i32 = 0
245
+ while (j < to && j - i < 4 && dnsHexValue(toI32(b[j])) >= 0) {
246
+ value = value * 16 + dnsHexValue(toI32(b[j]))
247
+ j += 1
248
+ }
249
+ if (j < to && toI32(b[j]) === DNS_DOT) {
250
+ if (groups > 6 || !dnsParseIpv4(b, i, to, out, at + groups * 2)) {
251
+ return false
252
+ }
253
+ groups += 2
254
+ i = to
255
+ } else {
256
+ if (j === i) {
257
+ return false
258
+ }
259
+ out[at + groups * 2] = toU8(value >> 8)
260
+ out[at + groups * 2 + 1] = toU8(value & 255)
261
+ groups += 1
262
+ i = j
263
+ if (i < to) {
264
+ if (i < 0 || i >= toI32(b.length) || toI32(b[i]) !== DNS_COLON) {
265
+ return false
266
+ }
267
+ i += 1
268
+ if (i < to && toI32(b[i]) === DNS_COLON) {
269
+ if (gap >= 0) {
270
+ return false
271
+ }
272
+ gap = groups
273
+ i += 1
274
+ } else if (i === to) {
275
+ return false
276
+ }
277
+ }
278
+ }
279
+ }
280
+ if (gap < 0) {
281
+ return groups === 8
282
+ }
283
+ if (groups > 7) {
284
+ return false
285
+ }
286
+ // The groups after the gap move to the end, and the gap fills with zeros.
287
+ const shift: i32 = (8 - groups) * 2
288
+ let k: i32 = groups * 2 - 1
289
+ while (k >= gap * 2) {
290
+ out[at + k + shift] = out[at + k]
291
+ k -= 1
292
+ }
293
+ k = gap * 2
294
+ while (k < gap * 2 + shift) {
295
+ out[at + k] = toU8(0)
296
+ k += 1
297
+ }
298
+ return true
299
+ }
300
+
301
+ /**
302
+ * Writes the IPv4 or IPv6 literal `b[off, off + len)` at `out[at, at + 18)`,
303
+ * IPv4 as `::ffff:a.b.c.d`, with `port` big-endian after it, and answers
304
+ * whether it was one. `out` is written only when it was. A zone (`%eth0`),
305
+ * brackets and a port are not part of a literal.
306
+ *
307
+ * Each call allocates the sixteen bytes it parses into, released when it
308
+ * returns; the two file parsers reuse a buffer of their own instead.
309
+ */
310
+ export const dnsParseAddress = (b: u8[], off: i32, len: i32, out: u8[], at: i32, port: i32): boolean =>
311
+ dnsParseAddressVia(b, off, len, out, at, port, new Array<u8>(16))
312
+
313
+ /** `dnsParseAddress`, parsing into `parsed`, sixteen bytes the caller owns, so it allocates nothing. */
314
+ const dnsParseAddressVia = (
315
+ b: u8[],
316
+ off: i32,
317
+ len: i32,
318
+ out: u8[],
319
+ at: i32,
320
+ port: i32,
321
+ parsed: u8[]
322
+ ): boolean => {
323
+ dnsConfigCheckWindow(b, off, len, "dnsParseAddress")
324
+ if (at < 0 || at > toI32(out.length) - DNS_ADDRESS_BYTES) {
325
+ panic("dnsParseAddress: at is outside out")
326
+ }
327
+ if (port < 0 || port > 0xffff) {
328
+ panic(`dnsParseAddress: the port ${port} is not 16 bits`)
329
+ }
330
+ if (toI32(parsed.length) < 16) {
331
+ panic("dnsParseAddress: parsed is shorter than 16 bytes")
332
+ }
333
+ let ok: boolean = false
334
+ const end: i32 = off + len
335
+ let i: i32 = off
336
+ while (i >= 0 && i < end && i < toI32(b.length) && toI32(b[i]) !== DNS_COLON) {
337
+ i += 1
338
+ }
339
+ if (i === end) {
340
+ let z: i32 = 0
341
+ while (z < 10) {
342
+ parsed[z] = toU8(0)
343
+ z += 1
344
+ }
345
+ ok = dnsParseIpv4(b, off, end, parsed, 12)
346
+ parsed[10] = toU8(255)
347
+ parsed[11] = toU8(255)
348
+ } else {
349
+ ok = dnsParseIpv6(b, off, end, parsed, 0)
350
+ }
351
+ if (!ok) {
352
+ return false
353
+ }
354
+ let k: i32 = 0
355
+ while (k < 16) {
356
+ out[at + k] = parsed[k]
357
+ k += 1
358
+ }
359
+ setU16BE(out, at + 16, toU16(port))
360
+ return true
361
+ }
362
+
363
+ /** Whether the address at `a[at]` is `::ffff:a.b.c.d`, an IPv4 one. */
364
+ const dnsIsIpv4 = (a: u8[], at: i32): boolean => {
365
+ let k: i32 = 0
366
+ while (k < 10) {
367
+ if (toI32(a[at + k]) !== 0) {
368
+ return false
369
+ }
370
+ k += 1
371
+ }
372
+ return toI32(a[at + 10]) === 255 && toI32(a[at + 11]) === 255
373
+ }
374
+
375
+ /**
376
+ * The end of the line that starts at `from`, before `to`: the offset of its
377
+ * newline, or `to`.
378
+ */
379
+ const dnsLineEnd = (b: u8[], from: i32, to: i32): i32 => {
380
+ if (from < 0 || from > to || to > toI32(b.length)) {
381
+ panic("dnsLineEnd: the window is outside the array")
382
+ }
383
+ let i: i32 = from
384
+ while (i < to && toI32(b[i]) !== DNS_NEWLINE) {
385
+ i += 1
386
+ }
387
+ return i
388
+ }
389
+
390
+ /** Where the line `b[from, to)` ends once a `#` or `;` comment is cut from it. */
391
+ const dnsCommentCut = (b: u8[], from: i32, to: i32): i32 => {
392
+ if (from < 0 || from > to || to > toI32(b.length)) {
393
+ panic("dnsCommentCut: the window is outside the array")
394
+ }
395
+ let i: i32 = from
396
+ while (i < to && toI32(b[i]) !== DNS_HASH && toI32(b[i]) !== DNS_SEMICOLON) {
397
+ i += 1
398
+ }
399
+ return i
400
+ }
401
+
402
+ /** The end of the word that starts at `from`, before `to`. */
403
+ const dnsWordEnd = (b: u8[], from: i32, to: i32): i32 => {
404
+ if (from < 0 || from > to || to > toI32(b.length)) {
405
+ panic("dnsWordEnd: the window is outside the array")
406
+ }
407
+ let i: i32 = from
408
+ while (i < to && !dnsIsBlank(toI32(b[i]))) {
409
+ i += 1
410
+ }
411
+ return i
412
+ }
413
+
414
+ /** Whether `b[from, to)` is the ASCII word `word`. */
415
+ const dnsWordIs = (b: u8[], from: i32, to: i32, word: string): boolean => {
416
+ const n: i32 = toI32(word.length)
417
+ if (to - from !== n) {
418
+ return false
419
+ }
420
+ let k: i32 = 0
421
+ while (k < n) {
422
+ if (toI32(b[from + k]) !== toI32(word.charCodeAt(k))) {
423
+ return false
424
+ }
425
+ k += 1
426
+ }
427
+ return true
428
+ }
429
+
430
+ /**
431
+ * The option `b[from, to)` read as `prefix` and a decimal count, clamped to
432
+ * `[low, high]`, or `fallback` when it is not that option: a digit past the
433
+ * count's fourth stops counting, so a hundred digits clamp rather than wrap.
434
+ */
435
+ const dnsOption = (b: u8[], from: i32, to: i32, prefix: string, low: i32, high: i32, fallback: i32): i32 => {
436
+ if (from < 0 || from > to || to > toI32(b.length)) {
437
+ panic("dnsOption: the window is outside the array")
438
+ }
439
+ const n: i32 = toI32(prefix.length)
440
+ if (to - from <= n || !dnsWordIs(b, from, from + n, prefix)) {
441
+ return fallback
442
+ }
443
+ let value: i32 = 0
444
+ let i: i32 = from + n
445
+ while (i < to) {
446
+ const d: i32 = toI32(b[i]) - 48
447
+ if (d < 0 || d > 9) {
448
+ return fallback
449
+ }
450
+ if (value < 10000) {
451
+ value = value * 10 + d
452
+ }
453
+ i += 1
454
+ }
455
+ if (value < low) {
456
+ return low
457
+ }
458
+ if (value > high) {
459
+ return high
460
+ }
461
+ return value
462
+ }
463
+
464
+ /** Appends `b[from, to)` to `config`'s search list, lowercase, one trailing dot dropped, or counts it dropped. */
465
+ const dnsAddSearch = (b: u8[], from: i32, to: i32, config: DnsConfig): void => {
466
+ let end: i32 = to
467
+ if (end > from && toI32(b[end - 1]) === DNS_DOT) {
468
+ end -= 1
469
+ }
470
+ const n: i32 = end - from
471
+ const used: i32 =
472
+ config.searchCount === 0
473
+ ? 0
474
+ : config.searchStart[config.searchCount - 1] + config.searchLength[config.searchCount - 1]
475
+ if (n === 0 || config.searchCount === DNS_MAX_SEARCH || n > DNS_MAX_SEARCH_BYTES - used) {
476
+ config.droppedSearch += 1
477
+ return
478
+ }
479
+ let k: i32 = 0
480
+ while (k < n) {
481
+ const c: i32 = toI32(b[from + k])
482
+ config.search[used + k] = toU8(c >= 65 && c <= 90 ? c + 32 : c)
483
+ k += 1
484
+ }
485
+ config.searchStart[config.searchCount] = used
486
+ config.searchLength[config.searchCount] = n
487
+ config.searchCount += 1
488
+ }
489
+
490
+ /** Reads one resolv.conf line, `b[from, to)` with its comment already cut, into `config`. */
491
+ const dnsResolvLine = (b: u8[], from: i32, to: i32, config: DnsConfig): void => {
492
+ const keyStart: i32 = skipBlank(b, from, to)
493
+ const keyEnd: i32 = dnsWordEnd(b, keyStart, to)
494
+ let i: i32 = skipBlank(b, keyEnd, to)
495
+ if (dnsWordIs(b, keyStart, keyEnd, "nameserver")) {
496
+ const end: i32 = dnsWordEnd(b, i, to)
497
+ if (end === i) {
498
+ return
499
+ }
500
+ if (config.serverCount === DNS_MAX_SERVERS) {
501
+ config.droppedServers += 1
502
+ return
503
+ }
504
+ if (
505
+ dnsParseAddressVia(
506
+ b,
507
+ i,
508
+ end - i,
509
+ config.servers,
510
+ config.serverCount * DNS_ADDRESS_BYTES,
511
+ DNS_PORT,
512
+ config.scratch
513
+ )
514
+ ) {
515
+ config.serverCount += 1
516
+ }
517
+ } else if (dnsWordIs(b, keyStart, keyEnd, "search") || dnsWordIs(b, keyStart, keyEnd, "domain")) {
518
+ const single: boolean = dnsWordIs(b, keyStart, keyEnd, "domain")
519
+ config.searchCount = 0
520
+ config.droppedSearch = 0
521
+ while (i < to) {
522
+ const end: i32 = dnsWordEnd(b, i, to)
523
+ dnsAddSearch(b, i, end, config)
524
+ i = skipBlank(b, end, to)
525
+ if (single) {
526
+ return
527
+ }
528
+ }
529
+ } else if (dnsWordIs(b, keyStart, keyEnd, "options")) {
530
+ while (i < to) {
531
+ const end: i32 = dnsWordEnd(b, i, to)
532
+ config.ndots = dnsOption(b, i, end, "ndots:", 0, DNS_MAX_NDOTS, config.ndots)
533
+ config.timeout = dnsOption(b, i, end, "timeout:", 1, DNS_MAX_TIMEOUT, config.timeout)
534
+ config.attempts = dnsOption(b, i, end, "attempts:", 1, DNS_MAX_ATTEMPTS, config.attempts)
535
+ i = skipBlank(b, end, to)
536
+ }
537
+ }
538
+ }
539
+
540
+ /**
541
+ * Reads the resolv.conf `b[off, off + len)` into `config`, which starts over
542
+ * from the defaults, and gives it 127.0.0.1 and ::1 on port 53 when no
543
+ * nameserver line held an address.
544
+ */
545
+ export const dnsParseResolvConf = (b: u8[], off: i32, len: i32, config: DnsConfig): void => {
546
+ dnsConfigCheckWindow(b, off, len, "dnsParseResolvConf")
547
+ config.serverCount = 0
548
+ config.droppedServers = 0
549
+ config.searchCount = 0
550
+ config.droppedSearch = 0
551
+ config.ndots = DNS_DEFAULT_NDOTS
552
+ config.timeout = DNS_DEFAULT_TIMEOUT
553
+ config.attempts = DNS_DEFAULT_ATTEMPTS
554
+ config.skippedLines = 0
555
+ const to: i32 = off + len
556
+ let i: i32 = off
557
+ while (i < to) {
558
+ const end: i32 = dnsLineEnd(b, i, to)
559
+ if (end - i > DNS_CONFIG_MAX_LINE) {
560
+ config.skippedLines += 1
561
+ } else {
562
+ dnsResolvLine(b, i, dnsCommentCut(b, i, end), config)
563
+ }
564
+ i = end + 1
565
+ }
566
+ if (config.serverCount === 0) {
567
+ dnsFallbackServers(config.servers)
568
+ config.serverCount = 2
569
+ }
570
+ }
571
+
572
+ /** Writes 127.0.0.1 and ::1, each on `DNS_PORT`, as the first two of `servers`, allocating nothing. */
573
+ const dnsFallbackServers = (servers: u8[]): void => {
574
+ if (toI32(servers.length) < 2 * DNS_ADDRESS_BYTES) {
575
+ panic("dnsFallbackServers: servers has no room for two addresses")
576
+ }
577
+ let k: i32 = 0
578
+ while (k < 2 * DNS_ADDRESS_BYTES && k < toI32(servers.length)) {
579
+ servers[k] = toU8(0)
580
+ k += 1
581
+ }
582
+ servers[10] = toU8(255)
583
+ servers[11] = toU8(255)
584
+ servers[12] = toU8(127)
585
+ servers[15] = toU8(1)
586
+ setU16BE(servers, 16, toU16(DNS_PORT))
587
+ servers[DNS_ADDRESS_BYTES + 15] = toU8(1)
588
+ setU16BE(servers, DNS_ADDRESS_BYTES + 16, toU16(DNS_PORT))
589
+ }
590
+
591
+ /** Adds `b[from, to)` as a name of entry `entry`, lowercase, one trailing dot dropped, or counts it dropped. */
592
+ const dnsAddHostName = (b: u8[], from: i32, to: i32, entry: i32, hosts: DnsHosts): void => {
593
+ let end: i32 = to
594
+ if (end > from && toI32(b[end - 1]) === DNS_DOT) {
595
+ end -= 1
596
+ }
597
+ const n: i32 = end - from
598
+ if (
599
+ n === 0 ||
600
+ n > DNS_HOSTS_MAX_NAME ||
601
+ hosts.nameCount === DNS_HOSTS_MAX_NAMES ||
602
+ n > DNS_HOSTS_MAX_NAME_BYTES - hosts.nameBytes
603
+ ) {
604
+ hosts.droppedNames += 1
605
+ return
606
+ }
607
+ let k: i32 = 0
608
+ while (k < n) {
609
+ const c: i32 = toI32(b[from + k])
610
+ hosts.names[hosts.nameBytes + k] = toU8(c >= 65 && c <= 90 ? c + 32 : c)
611
+ k += 1
612
+ }
613
+ hosts.nameStart[hosts.nameCount] = hosts.nameBytes
614
+ hosts.nameLength[hosts.nameCount] = n
615
+ hosts.nameEntry[hosts.nameCount] = entry
616
+ hosts.nameCount += 1
617
+ hosts.nameBytes += n
618
+ }
619
+
620
+ /** Reads one hosts line, `b[from, to)` with its comment already cut, into `hosts`. */
621
+ const dnsHostsLine = (b: u8[], from: i32, to: i32, hosts: DnsHosts): void => {
622
+ const addrStart: i32 = skipBlank(b, from, to)
623
+ const addrEnd: i32 = dnsWordEnd(b, addrStart, to)
624
+ let i: i32 = skipBlank(b, addrEnd, to)
625
+ if (addrEnd === addrStart || i === to) {
626
+ return
627
+ }
628
+ if (hosts.entryCount === DNS_HOSTS_MAX_ENTRIES) {
629
+ hosts.droppedEntries += 1
630
+ return
631
+ }
632
+ const at: i32 = hosts.entryCount * DNS_ADDRESS_BYTES
633
+ if (!dnsParseAddressVia(b, addrStart, addrEnd - addrStart, hosts.addresses, at, 0, hosts.scratch)) {
634
+ return
635
+ }
636
+ const entry: i32 = hosts.entryCount
637
+ hosts.entryCount += 1
638
+ while (i < to) {
639
+ const end: i32 = dnsWordEnd(b, i, to)
640
+ dnsAddHostName(b, i, end, entry, hosts)
641
+ i = skipBlank(b, end, to)
642
+ }
643
+ }
644
+
645
+ /** Reads the hosts file `b[off, off + len)` into `hosts`, which starts over empty. */
646
+ export const dnsParseHosts = (b: u8[], off: i32, len: i32, hosts: DnsHosts): void => {
647
+ dnsConfigCheckWindow(b, off, len, "dnsParseHosts")
648
+ hosts.entryCount = 0
649
+ hosts.nameCount = 0
650
+ hosts.nameBytes = 0
651
+ hosts.droppedEntries = 0
652
+ hosts.droppedNames = 0
653
+ hosts.skippedLines = 0
654
+ const to: i32 = off + len
655
+ let i: i32 = off
656
+ while (i < to) {
657
+ const end: i32 = dnsLineEnd(b, i, to)
658
+ if (end - i > DNS_CONFIG_MAX_LINE) {
659
+ hosts.skippedLines += 1
660
+ } else {
661
+ dnsHostsLine(b, i, dnsCommentCut(b, i, end), hosts)
662
+ }
663
+ i = end + 1
664
+ }
665
+ }
666
+
667
+ /** Whether kept name `index` of `hosts` is `name[0, n)`, without case. */
668
+ const dnsHostNameIs = (hosts: DnsHosts, index: i32, name: string, n: i32): boolean => {
669
+ if (hosts.nameLength[index] !== n) {
670
+ return false
671
+ }
672
+ const start: i32 = hosts.nameStart[index]
673
+ let k: i32 = 0
674
+ while (k < n) {
675
+ const c: i32 = toI32(name.charCodeAt(k))
676
+ if (toI32(hosts.names[start + k]) !== (c >= 65 && c <= 90 ? c + 32 : c)) {
677
+ return false
678
+ }
679
+ k += 1
680
+ }
681
+ return true
682
+ }
683
+
684
+ /**
685
+ * Writes the addresses `hosts` gives `name`, of `qtype` (`DNS_TYPE_A` or
686
+ * `DNS_TYPE_AAAA`), into `out` from offset 0 with `port`, in file order and at
687
+ * most `max` of them, and answers how many it wrote. An entry that names
688
+ * `name` twice is written once.
689
+ */
690
+ export const dnsHostsFind = (
691
+ hosts: DnsHosts,
692
+ name: string,
693
+ qtype: i32,
694
+ port: i32,
695
+ out: u8[],
696
+ max: i32
697
+ ): i32 => {
698
+ if (qtype !== DNS_TYPE_A && qtype !== DNS_TYPE_AAAA) {
699
+ panic(`dnsHostsFind: the type ${qtype} is not DNS_TYPE_A or DNS_TYPE_AAAA`)
700
+ }
701
+ if (max < 0 || max > toI32(out.length) / DNS_ADDRESS_BYTES) {
702
+ panic("dnsHostsFind: out has no room for max addresses")
703
+ }
704
+ if (port < 0 || port > 0xffff) {
705
+ panic(`dnsHostsFind: the port ${port} is not 16 bits`)
706
+ }
707
+ let n: i32 = toI32(name.length)
708
+ if (n > 0 && toI32(name.charCodeAt(n - 1)) === DNS_DOT) {
709
+ n -= 1
710
+ }
711
+ let found: i32 = 0
712
+ let last: i32 = -1
713
+ let i: i32 = 0
714
+ while (i < hosts.nameCount && found < max) {
715
+ const entry: i32 = hosts.nameEntry[i]
716
+ const at: i32 = entry * DNS_ADDRESS_BYTES
717
+ if (
718
+ entry !== last &&
719
+ dnsIsIpv4(hosts.addresses, at) === (qtype === DNS_TYPE_A) &&
720
+ dnsHostNameIs(hosts, i, name, n)
721
+ ) {
722
+ let k: i32 = 0
723
+ while (k < 16) {
724
+ out[found * DNS_ADDRESS_BYTES + k] = hosts.addresses[at + k]
725
+ k += 1
726
+ }
727
+ setU16BE(out, found * DNS_ADDRESS_BYTES + 16, toU16(port))
728
+ found += 1
729
+ last = entry
730
+ }
731
+ i += 1
732
+ }
733
+ return found
734
+ }