@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.
- package/bin/nish +0 -0
- package/docs/AI.md +50 -23
- package/package.json +1 -1
- package/runtime/browser.mjs +4 -3
- package/runtime/core/arrays.d.ts +0 -2
- package/runtime/core/arrays.mjs +0 -2
- package/runtime/core/host.d.ts +8 -0
- package/runtime/core/host.mjs +50 -2
- package/runtime/core/numbers.mjs +2 -3
- package/runtime/nish.d.ts +100 -4
- package/runtime/nish.h +34 -0
- package/runtime/nish.mjs +23 -5
- package/runtime/node.mjs +57 -6
- package/runtime/runtime-net.c +45 -4
- package/runtime/runtime-os.c +115 -12
- package/runtime/runtime-term.c +2 -2
- package/runtime/runtime.c +1 -1
- package/scripts/build.sh +13 -3
- package/std/README.md +44 -7
- package/std/cli-args.ts +1216 -0
- package/std/cli-flags.ts +585 -0
- package/std/cli.ts +308 -19
- package/std/compress/checksum.ts +290 -0
- package/std/compress/deflate.ts +2790 -0
- package/std/compress/gzip.ts +761 -0
- package/std/compress/zlib.ts +168 -0
- package/std/crypto/LICENSE-fiat-crypto-p384 +21 -0
- package/std/crypto/p256.ts +63 -1
- package/std/crypto/p384.ts +6044 -0
- package/std/crypto/pki-roots.ts +322 -0
- package/std/crypto/pki.ts +1207 -0
- package/std/crypto/rsa.ts +663 -0
- package/std/crypto/sha256.ts +2 -3
- package/std/crypto/x25519.ts +3 -4
- package/std/crypto/x509.ts +1054 -58
- package/std/net/dns-config.ts +734 -0
- package/std/net/dns-message.ts +517 -0
- package/std/net/dns-resolver.ts +1015 -0
- package/std/net/dns.ts +478 -0
- package/std/net/http-compress.ts +393 -0
- package/std/net/http1-client.ts +1227 -0
- package/std/net/http1-server.ts +101 -1
- package/std/net/http1.ts +9 -9
- package/std/net/tls/client.ts +1094 -0
- package/std/net/tls/codec.ts +585 -3
- package/std/net/tls/record-client.ts +1192 -0
- package/std/net/tls/verify.ts +221 -0
- package/std/net/tls-tcp.ts +642 -11
- package/std/net/tls.ts +2 -2
- package/std/net/url.ts +559 -4
- package/std/yaml-write.ts +415 -0
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `nish/compress/gzip` — the gzip file format (RFC 1952) around
|
|
3
|
+
* `nish/compress/deflate`: a ten-byte header, the DEFLATE stream, and a
|
|
4
|
+
* trailer of the CRC-32 and the length of the data mod 2^32. What an HTTP
|
|
5
|
+
* `Content-Encoding: gzip` body is.
|
|
6
|
+
*
|
|
7
|
+
* import { DEFLATE_FAST, compressBound } from "nish/compress/deflate";
|
|
8
|
+
* import { GzipCompressor, GzipDecompressor } from "nish/compress/gzip";
|
|
9
|
+
*
|
|
10
|
+
* const gz = new GzipCompressor(DEFLATE_FAST); // once per thread
|
|
11
|
+
* const out: u8[] = new Array<u8>(compressBound(8192)); // once
|
|
12
|
+
* const end: i32 = gz.compress(body, 0, n, out, 0); // -1: no room
|
|
13
|
+
*
|
|
14
|
+
* const gunzip = new GzipDecompressor();
|
|
15
|
+
* const got: i32 = gunzip.decompress(src, 0, len, back, 0, 1 << 20); // < 0: an error
|
|
16
|
+
*
|
|
17
|
+
* **One shot.** `GzipCompressor` and `GzipDecompressor` own every table a
|
|
18
|
+
* call needs and allocate nothing per call; `gzipCompress` and
|
|
19
|
+
* `gzipDecompress` build one for a single call. The header written carries no
|
|
20
|
+
* name, no comment and a zero modification time, `XFL` 4 at level 1 and 2 at
|
|
21
|
+
* 9, and `OS` 255 ("unknown"), so the same input gives the same bytes on
|
|
22
|
+
* every machine.
|
|
23
|
+
*
|
|
24
|
+
* **Reading is strict.** A header with a method other than 8 or a reserved
|
|
25
|
+
* flag bit is `GZIP_ERR_HEADER`; `FEXTRA`, `FNAME`, `FCOMMENT` and `FHCRC` are
|
|
26
|
+
* read and skipped, the header CRC checked. A wrong CRC-32 is `GZIP_ERR_CRC`,
|
|
27
|
+
* a wrong length `GZIP_ERR_SIZE`. Members may follow one another (§2.2) and
|
|
28
|
+
* are decompressed one after another into the same output; anything after the
|
|
29
|
+
* last member that is not another member is `GZIP_ERR_HEADER`, so trailing
|
|
30
|
+
* garbage is refused, not ignored. The `limit` bounds the whole output.
|
|
31
|
+
*
|
|
32
|
+
* **Streaming.** `GzipWriter` compresses a body that arrives in pieces and
|
|
33
|
+
* `GzipReader` decompresses one: see each class.
|
|
34
|
+
*
|
|
35
|
+
* Written from RFC 1952.
|
|
36
|
+
*/
|
|
37
|
+
import { Crc32 } from "nish/compress/checksum"
|
|
38
|
+
import {
|
|
39
|
+
DEFLATE_ERR_OUTPUT_LIMIT,
|
|
40
|
+
DEFLATE_ERR_TRUNCATED,
|
|
41
|
+
DEFLATE_NO_ROOM,
|
|
42
|
+
Deflater,
|
|
43
|
+
INFLATE_DONE,
|
|
44
|
+
INFLATE_NEED_INPUT,
|
|
45
|
+
INFLATE_NEED_OUTPUT,
|
|
46
|
+
Inflater,
|
|
47
|
+
deflateBound,
|
|
48
|
+
} from "nish/compress/deflate"
|
|
49
|
+
import { copyBytes } from "nish/bytes"
|
|
50
|
+
|
|
51
|
+
/** A gzip header that is not one: the wrong magic, a method other than DEFLATE, a reserved flag, or a bad header CRC. */
|
|
52
|
+
export const GZIP_ERR_HEADER: i32 = -9
|
|
53
|
+
|
|
54
|
+
/** The trailer's CRC-32 is not the CRC-32 of the data. */
|
|
55
|
+
export const GZIP_ERR_CRC: i32 = -10
|
|
56
|
+
|
|
57
|
+
/** The trailer's length is not the data's length mod 2^32. */
|
|
58
|
+
export const GZIP_ERR_SIZE: i32 = -11
|
|
59
|
+
|
|
60
|
+
/** `GzipReader`: no more output yet; give it input. */
|
|
61
|
+
export const GZIP_NEED_INPUT: i32 = 0
|
|
62
|
+
|
|
63
|
+
/** `GzipReader`: the stream has ended and every byte of it was read. */
|
|
64
|
+
export const GZIP_END: i32 = -100
|
|
65
|
+
|
|
66
|
+
/** The gzip header's and trailer's bytes. */
|
|
67
|
+
const GZIP_HEADER_SIZE: i32 = 10
|
|
68
|
+
const GZIP_TRAILER_SIZE: i32 = 8
|
|
69
|
+
|
|
70
|
+
/** The bytes a `GzipWriter` holds: 32 KB of history, and up to 64 KB of input before it compresses them. */
|
|
71
|
+
const GZIP_WRITER_HISTORY: i32 = 32768
|
|
72
|
+
const GZIP_WRITER_INPUT: i32 = 65536
|
|
73
|
+
|
|
74
|
+
/** The bytes of decoded output a `GzipReader` keeps: the 32 KB window and 32 KB to hand out. */
|
|
75
|
+
const GZIP_READER_WINDOW: i32 = 32768
|
|
76
|
+
const GZIP_READER_OUTPUT: i32 = 65536
|
|
77
|
+
|
|
78
|
+
/** The bytes a `GzipReader` buffers of input. */
|
|
79
|
+
const GZIP_READER_INPUT: i32 = 16384
|
|
80
|
+
|
|
81
|
+
/** Panics unless `buf[off .. off + len)` lies inside `buf`. */
|
|
82
|
+
const gzipWindow = (what: string, buf: u8[], off: i32, len: i32): void => {
|
|
83
|
+
if (off < 0 || len < 0 || off > toI32(buf.length) - len) {
|
|
84
|
+
panic(`${what}: the window [${off}, ${off} + ${len}) is outside a buffer of ${toI32(buf.length)} bytes`)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Writes `v` little-endian into `out[at .. at + 4)`; the caller has checked the room. */
|
|
89
|
+
const gzipPut32 = (out: u8[], at: i32, v: u32): void => {
|
|
90
|
+
out[at] = toU8(v & 255)
|
|
91
|
+
out[at + 1] = toU8((v >>> 8) & 255)
|
|
92
|
+
out[at + 2] = toU8((v >>> 16) & 255)
|
|
93
|
+
out[at + 3] = toU8(v >>> 24)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The little-endian word at `b[at .. at + 4)`; the caller has checked the range. */
|
|
97
|
+
const gzipGet32 = (b: u8[], at: i32): u32 =>
|
|
98
|
+
toU32(b[at]) | (toU32(b[at + 1]) << 8) | (toU32(b[at + 2]) << 16) | (toU32(b[at + 3]) << 24)
|
|
99
|
+
|
|
100
|
+
/** Writes the ten-byte header for `level` at `out[at]`; the caller has checked the room. */
|
|
101
|
+
const gzipWriteHeader = (out: u8[], at: i32, level: i32): void => {
|
|
102
|
+
out[at] = 0x1f
|
|
103
|
+
out[at + 1] = 0x8b
|
|
104
|
+
out[at + 2] = 8
|
|
105
|
+
out[at + 3] = 0
|
|
106
|
+
gzipPut32(out, at + 4, toU32(0))
|
|
107
|
+
// XFL (§2.3.1): 2 for the slowest level, 4 for the fastest.
|
|
108
|
+
let xfl: i32 = 0
|
|
109
|
+
if (level === 1) {
|
|
110
|
+
xfl = 4
|
|
111
|
+
} else if (level === 9) {
|
|
112
|
+
xfl = 2
|
|
113
|
+
}
|
|
114
|
+
out[at + 8] = toU8(xfl)
|
|
115
|
+
out[at + 9] = 255
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Reads a member header at `src[p .. end)`: answers the index of the DEFLATE
|
|
120
|
+
* stream after it, `DEFLATE_ERR_TRUNCATED` when the input ends inside it,
|
|
121
|
+
* or `GZIP_ERR_HEADER`.
|
|
122
|
+
*/
|
|
123
|
+
const gzipReadHeader = (crc: Crc32, src: u8[], p: i32, end: i32): i32 => {
|
|
124
|
+
if (end - p < GZIP_HEADER_SIZE) {
|
|
125
|
+
return end - p >= 2 && (src[p] !== 0x1f || src[p + 1] !== 0x8b) ? GZIP_ERR_HEADER : DEFLATE_ERR_TRUNCATED
|
|
126
|
+
}
|
|
127
|
+
if (src[p] !== 0x1f || src[p + 1] !== 0x8b || src[p + 2] !== 8) {
|
|
128
|
+
return GZIP_ERR_HEADER
|
|
129
|
+
}
|
|
130
|
+
const flags: i32 = toI32(src[p + 3])
|
|
131
|
+
if ((flags & 0xe0) !== 0) {
|
|
132
|
+
return GZIP_ERR_HEADER
|
|
133
|
+
}
|
|
134
|
+
let q: i32 = p + GZIP_HEADER_SIZE
|
|
135
|
+
if ((flags & 4) !== 0) {
|
|
136
|
+
if (end - q < 2) {
|
|
137
|
+
return DEFLATE_ERR_TRUNCATED
|
|
138
|
+
}
|
|
139
|
+
const xlen: i32 = toI32(src[q]) | (toI32(src[q + 1]) << 8)
|
|
140
|
+
if (end - q - 2 < xlen) {
|
|
141
|
+
return DEFLATE_ERR_TRUNCATED
|
|
142
|
+
}
|
|
143
|
+
q += 2 + xlen
|
|
144
|
+
}
|
|
145
|
+
for (let field: i32 = 8; field <= 16; field += 8) {
|
|
146
|
+
if ((flags & field) !== 0) {
|
|
147
|
+
while (q >= 0 && q < end && q < toI32(src.length) && src[q] !== 0) {
|
|
148
|
+
q += 1
|
|
149
|
+
}
|
|
150
|
+
if (q >= end) {
|
|
151
|
+
return DEFLATE_ERR_TRUNCATED
|
|
152
|
+
}
|
|
153
|
+
q += 1
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if ((flags & 2) !== 0) {
|
|
157
|
+
if (end - q < 2) {
|
|
158
|
+
return DEFLATE_ERR_TRUNCATED
|
|
159
|
+
}
|
|
160
|
+
const want: i32 = toI32(src[q]) | (toI32(src[q + 1]) << 8)
|
|
161
|
+
if (toI32(crc.update(toU32(0), src, p, q - p) & 0xffff) !== want) {
|
|
162
|
+
return GZIP_ERR_HEADER
|
|
163
|
+
}
|
|
164
|
+
q += 2
|
|
165
|
+
}
|
|
166
|
+
return q
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* A gzip compressor at one level: a `Deflater` and the CRC-32 tables,
|
|
171
|
+
* allocated once and reused by every call. Not shared between threads.
|
|
172
|
+
*/
|
|
173
|
+
export class GzipCompressor {
|
|
174
|
+
readonly deflater: Deflater
|
|
175
|
+
readonly crc: Crc32
|
|
176
|
+
|
|
177
|
+
/** A compressor at `level`, 0 to 9 (`DEFLATE_FAST`, `DEFLATE_DEFAULT`, ...); another level panics. */
|
|
178
|
+
constructor(level: i32) {
|
|
179
|
+
this.deflater = new Deflater(level)
|
|
180
|
+
this.crc = new Crc32()
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Compresses `src[off .. off + len)` into `out` from `at` as one gzip
|
|
185
|
+
* member, and answers where it ends, or `DEFLATE_NO_ROOM` (-1) when `out`
|
|
186
|
+
* cannot hold it; `compressBound(len)` bytes always can. A window outside
|
|
187
|
+
* its array panics. Allocates nothing.
|
|
188
|
+
*/
|
|
189
|
+
compress(src: u8[], off: i32, len: i32, out: u8[], at: i32): i32 {
|
|
190
|
+
gzipWindow("GzipCompressor.compress", src, off, len)
|
|
191
|
+
gzipWindow("GzipCompressor.compress", out, at, 0)
|
|
192
|
+
if (toI32(out.length) - at < GZIP_HEADER_SIZE + GZIP_TRAILER_SIZE) {
|
|
193
|
+
return DEFLATE_NO_ROOM
|
|
194
|
+
}
|
|
195
|
+
gzipWriteHeader(out, at, this.deflater.level)
|
|
196
|
+
const end: i32 = this.deflater.compress(src, off, len, out, at + GZIP_HEADER_SIZE)
|
|
197
|
+
if (end < 0 || toI32(out.length) - end < GZIP_TRAILER_SIZE) {
|
|
198
|
+
return DEFLATE_NO_ROOM
|
|
199
|
+
}
|
|
200
|
+
gzipPut32(out, end, this.crc.update(toU32(0), src, off, len))
|
|
201
|
+
gzipPut32(out, end + 4, toU32(len))
|
|
202
|
+
return end + GZIP_TRAILER_SIZE
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* A gzip decompressor: an `Inflater` and the CRC-32 tables, allocated once.
|
|
208
|
+
* Not shared between threads.
|
|
209
|
+
*/
|
|
210
|
+
export class GzipDecompressor {
|
|
211
|
+
readonly inflater: Inflater
|
|
212
|
+
readonly crc: Crc32
|
|
213
|
+
|
|
214
|
+
/** Builds the tables. */
|
|
215
|
+
constructor() {
|
|
216
|
+
this.inflater = new Inflater()
|
|
217
|
+
this.crc = new Crc32()
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Decompresses every member of `src[off .. off + len)` into `out` from
|
|
222
|
+
* `at`, writing at most `limit` bytes and never past `out`'s end, and
|
|
223
|
+
* answers where the output ends; or `DEFLATE_ERR_OUTPUT_LIMIT` when the
|
|
224
|
+
* data is longer, `GZIP_ERR_HEADER`, `GZIP_ERR_CRC`, `GZIP_ERR_SIZE`,
|
|
225
|
+
* `DEFLATE_ERR_TRUNCATED` or another `DEFLATE_ERR_*` for a malformed
|
|
226
|
+
* stream. An empty input is truncated. A window outside its array, or a
|
|
227
|
+
* negative `limit`, panics. Allocates nothing.
|
|
228
|
+
*/
|
|
229
|
+
decompress(src: u8[], off: i32, len: i32, out: u8[], at: i32, limit: i32): i32 {
|
|
230
|
+
gzipWindow("GzipDecompressor.decompress", src, off, len)
|
|
231
|
+
gzipWindow("GzipDecompressor.decompress", out, at, 0)
|
|
232
|
+
if (limit < 0) {
|
|
233
|
+
panic("GzipDecompressor.decompress: a negative limit")
|
|
234
|
+
}
|
|
235
|
+
const room: i32 = toI32(out.length) - at
|
|
236
|
+
const outEnd: i32 = at + (limit < room ? limit : room)
|
|
237
|
+
const end: i32 = off + len
|
|
238
|
+
let p: i32 = off
|
|
239
|
+
let op: i32 = at
|
|
240
|
+
let first: boolean = true
|
|
241
|
+
while (first || p < end) {
|
|
242
|
+
first = false
|
|
243
|
+
const body: i32 = gzipReadHeader(this.crc, src, p, end)
|
|
244
|
+
if (body < 0) {
|
|
245
|
+
return body
|
|
246
|
+
}
|
|
247
|
+
const inf: Inflater = this.inflater
|
|
248
|
+
inf.reset()
|
|
249
|
+
const r: i32 = inf.inflate(src, body, end, false, out, op, outEnd, op)
|
|
250
|
+
if (r === INFLATE_NEED_OUTPUT) {
|
|
251
|
+
return DEFLATE_ERR_OUTPUT_LIMIT
|
|
252
|
+
}
|
|
253
|
+
if (r !== INFLATE_DONE) {
|
|
254
|
+
return r
|
|
255
|
+
}
|
|
256
|
+
const q: i32 = inf.inPos
|
|
257
|
+
if (end - q < GZIP_TRAILER_SIZE) {
|
|
258
|
+
return DEFLATE_ERR_TRUNCATED
|
|
259
|
+
}
|
|
260
|
+
const n: i32 = inf.outPos - op
|
|
261
|
+
if (gzipGet32(src, q) !== this.crc.update(toU32(0), out, op, n)) {
|
|
262
|
+
return GZIP_ERR_CRC
|
|
263
|
+
}
|
|
264
|
+
if (gzipGet32(src, q + 4) !== toU32(n)) {
|
|
265
|
+
return GZIP_ERR_SIZE
|
|
266
|
+
}
|
|
267
|
+
op = inf.outPos
|
|
268
|
+
p = q + GZIP_TRAILER_SIZE
|
|
269
|
+
}
|
|
270
|
+
return op
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* `GzipCompressor.compress` at `level`, building the compressor for this call
|
|
276
|
+
* alone (about 330 KB, allocated and filled): keep a `GzipCompressor` for
|
|
277
|
+
* anything repeated.
|
|
278
|
+
*/
|
|
279
|
+
export const gzipCompress = (src: u8[], off: i32, len: i32, out: u8[], at: i32, level: i32): i32 =>
|
|
280
|
+
new GzipCompressor(level).compress(src, off, len, out, at)
|
|
281
|
+
|
|
282
|
+
/** `GzipDecompressor.decompress`, building the decompressor for this call alone. */
|
|
283
|
+
export const gzipDecompress = (src: u8[], off: i32, len: i32, out: u8[], at: i32, limit: i32): i32 =>
|
|
284
|
+
new GzipDecompressor().decompress(src, off, len, out, at, limit)
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* A streaming gzip compressor, for a body that does not fit in one buffer:
|
|
288
|
+
* `write` pieces, `flush` to make everything so far decodable, `finish` once.
|
|
289
|
+
* It buffers up to 64 KB of input (and 32 KB of history to match against),
|
|
290
|
+
* compressing whenever that fills, so output arrives in bursts. Every call
|
|
291
|
+
* writes into a caller window and answers where its output ends, or
|
|
292
|
+
* `DEFLATE_NO_ROOM` when the window is shorter than `bound(len)`, the most
|
|
293
|
+
* that call can produce; nothing is consumed then. About 430 KB, allocated
|
|
294
|
+
* once; `start` begins the next body.
|
|
295
|
+
*/
|
|
296
|
+
export class GzipWriter {
|
|
297
|
+
readonly deflater: Deflater
|
|
298
|
+
readonly crc: Crc32
|
|
299
|
+
/** The history and the pending input. */
|
|
300
|
+
readonly window: u8[]
|
|
301
|
+
/** Where the pending input starts in `window`, and where it ends. */
|
|
302
|
+
pending: i32
|
|
303
|
+
fill: i32
|
|
304
|
+
/** The CRC-32 and length of every byte written so far. */
|
|
305
|
+
checksum: u32
|
|
306
|
+
size: u32
|
|
307
|
+
/** Whether the header has been written, and whether `finish` has. */
|
|
308
|
+
started: boolean
|
|
309
|
+
finished: boolean
|
|
310
|
+
|
|
311
|
+
/** A writer at `level`, 0 to 9; another level panics. */
|
|
312
|
+
constructor(level: i32) {
|
|
313
|
+
this.deflater = new Deflater(level)
|
|
314
|
+
this.crc = new Crc32()
|
|
315
|
+
this.window = new Array<u8>(GZIP_WRITER_HISTORY + GZIP_WRITER_INPUT)
|
|
316
|
+
this.pending = 0
|
|
317
|
+
this.fill = 0
|
|
318
|
+
this.checksum = 0
|
|
319
|
+
this.size = 0
|
|
320
|
+
this.started = false
|
|
321
|
+
this.finished = false
|
|
322
|
+
this.start()
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/** Begins a new body: the next output starts with a header. */
|
|
326
|
+
start(): void {
|
|
327
|
+
// Push every position the tables hold a window behind the new body, so
|
|
328
|
+
// that no match reaches into the last one.
|
|
329
|
+
this.deflater.finish(this.fill)
|
|
330
|
+
this.deflater.reset()
|
|
331
|
+
this.deflater.begin(toI32(0))
|
|
332
|
+
this.pending = 0
|
|
333
|
+
this.fill = 0
|
|
334
|
+
this.checksum = 0
|
|
335
|
+
this.size = 0
|
|
336
|
+
this.started = false
|
|
337
|
+
this.finished = false
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* The most bytes a `write` of `len` bytes, or a `flush` or `finish` after
|
|
342
|
+
* it, can produce. Less than 64 KB is pending before the call, and the
|
|
343
|
+
* call compresses once per 64 KB pending: `deflateBound` of all of it,
|
|
344
|
+
* with the constant part of `deflateBound` again for each compression
|
|
345
|
+
* after the first.
|
|
346
|
+
*/
|
|
347
|
+
bound(len: i32): i32 {
|
|
348
|
+
const drains: i32 = (len >> 16) + 1
|
|
349
|
+
return deflateBound(GZIP_WRITER_INPUT + len) + drains * 18 + GZIP_HEADER_SIZE + GZIP_TRAILER_SIZE + 10
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** Writes the header into `out` at `at` if it has not been; answers the new position. */
|
|
353
|
+
header(out: u8[], at: i32): i32 {
|
|
354
|
+
if (this.started) {
|
|
355
|
+
return at
|
|
356
|
+
}
|
|
357
|
+
gzipWriteHeader(out, at, this.deflater.level)
|
|
358
|
+
this.started = true
|
|
359
|
+
return at + GZIP_HEADER_SIZE
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Compresses the pending input as non-final blocks into `out[at ..)`,
|
|
364
|
+
* then slides the window so that the last 32 KB stay as history. Answers
|
|
365
|
+
* the new position, or -1.
|
|
366
|
+
*/
|
|
367
|
+
drain(out: u8[], at: i32, final: boolean): i32 {
|
|
368
|
+
const d: Deflater = this.deflater
|
|
369
|
+
const end: i32 = d.compressRange(
|
|
370
|
+
this.window,
|
|
371
|
+
this.pending,
|
|
372
|
+
this.fill,
|
|
373
|
+
toI32(0),
|
|
374
|
+
final,
|
|
375
|
+
out,
|
|
376
|
+
at,
|
|
377
|
+
toI32(out.length)
|
|
378
|
+
)
|
|
379
|
+
if (end < 0) {
|
|
380
|
+
return -1
|
|
381
|
+
}
|
|
382
|
+
this.pending = this.fill
|
|
383
|
+
if (this.fill > GZIP_WRITER_HISTORY) {
|
|
384
|
+
const shift: i32 = this.fill - GZIP_WRITER_HISTORY
|
|
385
|
+
copyBytes(this.window, 0, this.window, shift, GZIP_WRITER_HISTORY)
|
|
386
|
+
this.fill = GZIP_WRITER_HISTORY
|
|
387
|
+
this.pending = GZIP_WRITER_HISTORY
|
|
388
|
+
d.slide(shift)
|
|
389
|
+
}
|
|
390
|
+
return end
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Takes `src[off .. off + len)` and answers where the output it produced
|
|
395
|
+
* in `out` from `at` ends (often `at`: input waits until 64 KB are
|
|
396
|
+
* pending), or `DEFLATE_NO_ROOM` when `out` has less than `bound(len)`
|
|
397
|
+
* bytes from `at`. Writing after `finish` panics, as does a window outside
|
|
398
|
+
* its array.
|
|
399
|
+
*/
|
|
400
|
+
write(src: u8[], off: i32, len: i32, out: u8[], at: i32): i32 {
|
|
401
|
+
gzipWindow("GzipWriter.write", src, off, len)
|
|
402
|
+
gzipWindow("GzipWriter.write", out, at, 0)
|
|
403
|
+
if (this.finished) {
|
|
404
|
+
panic("GzipWriter.write: the body has been finished")
|
|
405
|
+
}
|
|
406
|
+
if (toI32(out.length) - at < this.bound(len)) {
|
|
407
|
+
return DEFLATE_NO_ROOM
|
|
408
|
+
}
|
|
409
|
+
let o: i32 = this.header(out, at)
|
|
410
|
+
this.checksum = this.crc.update(this.checksum, src, off, len)
|
|
411
|
+
this.size = this.size + toU32(len)
|
|
412
|
+
let p: i32 = off
|
|
413
|
+
const end: i32 = off + len
|
|
414
|
+
// Compressed once 64 KB is pending, the first window too: `pending` is
|
|
415
|
+
// at most `GZIP_WRITER_HISTORY` here, so the window always has the room.
|
|
416
|
+
while (p < end) {
|
|
417
|
+
let take: i32 = GZIP_WRITER_INPUT - (this.fill - this.pending)
|
|
418
|
+
if (take > end - p) {
|
|
419
|
+
take = end - p
|
|
420
|
+
}
|
|
421
|
+
copyBytes(this.window, this.fill, src, p, take)
|
|
422
|
+
this.fill += take
|
|
423
|
+
p += take
|
|
424
|
+
if (this.fill - this.pending === GZIP_WRITER_INPUT) {
|
|
425
|
+
o = this.drain(out, o, false)
|
|
426
|
+
if (o < 0) {
|
|
427
|
+
panic("GzipWriter.write: a block larger than its bound")
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return o
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Compresses everything pending and ends with an empty stored block, so a
|
|
436
|
+
* reader can decode every byte written so far (zlib's `Z_SYNC_FLUSH`).
|
|
437
|
+
* Answers the new end of `out`, or `DEFLATE_NO_ROOM` below `bound(0)`.
|
|
438
|
+
*/
|
|
439
|
+
flush(out: u8[], at: i32): i32 {
|
|
440
|
+
gzipWindow("GzipWriter.flush", out, at, 0)
|
|
441
|
+
if (this.finished) {
|
|
442
|
+
panic("GzipWriter.flush: the body has been finished")
|
|
443
|
+
}
|
|
444
|
+
if (toI32(out.length) - at < this.bound(toI32(0))) {
|
|
445
|
+
return DEFLATE_NO_ROOM
|
|
446
|
+
}
|
|
447
|
+
let o: i32 = this.header(out, at)
|
|
448
|
+
if (this.fill > this.pending) {
|
|
449
|
+
o = this.drain(out, o, false)
|
|
450
|
+
if (o < 0) {
|
|
451
|
+
panic("GzipWriter.flush: a block larger than its bound")
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
o = this.deflater.writeStored(this.window, toI32(0), toI32(0), false, out, o, toI32(out.length))
|
|
455
|
+
return o
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/**
|
|
459
|
+
* Compresses everything pending as the final block and writes the
|
|
460
|
+
* trailer; answers the new end of `out`, or `DEFLATE_NO_ROOM` below
|
|
461
|
+
* `bound(0)`. `start` begins another body.
|
|
462
|
+
*/
|
|
463
|
+
finish(out: u8[], at: i32): i32 {
|
|
464
|
+
gzipWindow("GzipWriter.finish", out, at, 0)
|
|
465
|
+
if (this.finished) {
|
|
466
|
+
panic("GzipWriter.finish: the body has been finished")
|
|
467
|
+
}
|
|
468
|
+
if (toI32(out.length) - at < this.bound(toI32(0))) {
|
|
469
|
+
return DEFLATE_NO_ROOM
|
|
470
|
+
}
|
|
471
|
+
let o: i32 = this.header(out, at)
|
|
472
|
+
o = this.drain(out, o, true)
|
|
473
|
+
if (o < 0) {
|
|
474
|
+
panic("GzipWriter.finish: a block larger than its bound")
|
|
475
|
+
}
|
|
476
|
+
o = this.deflater.flushBits(out, o, toI32(out.length))
|
|
477
|
+
if (o < 0 || toI32(out.length) - o < GZIP_TRAILER_SIZE) {
|
|
478
|
+
panic("GzipWriter.finish: a block larger than its bound")
|
|
479
|
+
}
|
|
480
|
+
gzipPut32(out, o, this.checksum)
|
|
481
|
+
gzipPut32(out, o + 4, this.size)
|
|
482
|
+
this.finished = true
|
|
483
|
+
return o + GZIP_TRAILER_SIZE
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* A streaming gzip decompressor, for a body that arrives in pieces or does
|
|
489
|
+
* not fit in memory. Sans-IO in the `nish/net/postgres` shape: copy input
|
|
490
|
+
* into `inputBuffer()` at `inputEnd()` and `commit` it (or `feed` a window),
|
|
491
|
+
* then call `read` until it answers `GZIP_NEED_INPUT`; at the end of the
|
|
492
|
+
* input, `endInput()`. `read(out, at, max)` copies up to `max` decoded bytes
|
|
493
|
+
* and answers how many, `GZIP_NEED_INPUT`, `GZIP_END` once the stream and its
|
|
494
|
+
* trailer are through, or a negative error, final. A total of more than
|
|
495
|
+
* `limit` decoded bytes is `DEFLATE_ERR_OUTPUT_LIMIT`. Concatenated members
|
|
496
|
+
* are read as one body, as by `GzipDecompressor`. About 100 KB, allocated
|
|
497
|
+
* once; `start` begins the next body.
|
|
498
|
+
*/
|
|
499
|
+
export class GzipReader {
|
|
500
|
+
readonly inflater: Inflater
|
|
501
|
+
readonly crc: Crc32
|
|
502
|
+
/** Input not yet decoded. */
|
|
503
|
+
readonly input: u8[]
|
|
504
|
+
/** The decoded window: history from `windowStart`, unread output from `readPos` to `writePos`. */
|
|
505
|
+
readonly output: u8[]
|
|
506
|
+
inStart: i32
|
|
507
|
+
inEnd: i32
|
|
508
|
+
readPos: i32
|
|
509
|
+
writePos: i32
|
|
510
|
+
/** Where the current member's output began, for its distances. */
|
|
511
|
+
histStart: i32
|
|
512
|
+
/** Where the bytes not yet checksummed start. */
|
|
513
|
+
crcPos: i32
|
|
514
|
+
/** 0: a member header next; 1: inside a member's stream; 2: its trailer; 3: done; < 0: failed. */
|
|
515
|
+
phase: i32
|
|
516
|
+
/** Whether more input may come. */
|
|
517
|
+
more: boolean
|
|
518
|
+
/** The member's running CRC-32 and length, and the body's total length. */
|
|
519
|
+
checksum: u32
|
|
520
|
+
size: u32
|
|
521
|
+
total: i64
|
|
522
|
+
/** The most decoded bytes `read` hands out in all. */
|
|
523
|
+
readonly limit: i64
|
|
524
|
+
/** Whether a member has been read; another may follow. */
|
|
525
|
+
members: i32
|
|
526
|
+
|
|
527
|
+
/** A reader that refuses more than `limit` decoded bytes in all; a negative `limit` panics. */
|
|
528
|
+
constructor(limit: i64) {
|
|
529
|
+
if (limit < 0) {
|
|
530
|
+
panic("GzipReader: a negative limit")
|
|
531
|
+
}
|
|
532
|
+
this.inflater = new Inflater()
|
|
533
|
+
this.crc = new Crc32()
|
|
534
|
+
this.input = new Array<u8>(GZIP_READER_INPUT)
|
|
535
|
+
this.output = new Array<u8>(GZIP_READER_WINDOW + GZIP_READER_OUTPUT)
|
|
536
|
+
this.limit = limit
|
|
537
|
+
this.inStart = 0
|
|
538
|
+
this.inEnd = 0
|
|
539
|
+
this.readPos = 0
|
|
540
|
+
this.writePos = 0
|
|
541
|
+
this.histStart = 0
|
|
542
|
+
this.crcPos = 0
|
|
543
|
+
this.phase = 0
|
|
544
|
+
this.more = true
|
|
545
|
+
this.checksum = 0
|
|
546
|
+
this.size = 0
|
|
547
|
+
this.total = 0
|
|
548
|
+
this.members = 0
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/** Begins a new body, dropping any input and output left over. */
|
|
552
|
+
start(): void {
|
|
553
|
+
this.inStart = 0
|
|
554
|
+
this.inEnd = 0
|
|
555
|
+
this.readPos = 0
|
|
556
|
+
this.writePos = 0
|
|
557
|
+
this.histStart = 0
|
|
558
|
+
this.crcPos = 0
|
|
559
|
+
this.phase = 0
|
|
560
|
+
this.more = true
|
|
561
|
+
this.checksum = 0
|
|
562
|
+
this.size = 0
|
|
563
|
+
this.total = 0
|
|
564
|
+
this.members = 0
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/** The buffer to copy input into, from `inputEnd()` for at most `inputRoom()` bytes. */
|
|
568
|
+
inputBuffer(): u8[] {
|
|
569
|
+
this.compact()
|
|
570
|
+
return this.input
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/** Where new input goes in `inputBuffer()`. */
|
|
574
|
+
inputEnd(): i32 {
|
|
575
|
+
return this.inEnd
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/** How many bytes `inputBuffer()` has room for. */
|
|
579
|
+
inputRoom(): i32 {
|
|
580
|
+
this.compact()
|
|
581
|
+
return toI32(this.input.length) - this.inEnd
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/** Takes `n` bytes copied into `inputBuffer()` at `inputEnd()`; more than `inputRoom()` panics. */
|
|
585
|
+
commit(n: i32): void {
|
|
586
|
+
if (n < 0 || n > toI32(this.input.length) - this.inEnd) {
|
|
587
|
+
panic("GzipReader.commit: more bytes than the buffer has room for")
|
|
588
|
+
}
|
|
589
|
+
this.inEnd += n
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/** Copies as much of `src[off .. off + len)` as fits and answers how much. */
|
|
593
|
+
feed(src: u8[], off: i32, len: i32): i32 {
|
|
594
|
+
gzipWindow("GzipReader.feed", src, off, len)
|
|
595
|
+
const room: i32 = this.inputRoom()
|
|
596
|
+
const take: i32 = len < room ? len : room
|
|
597
|
+
copyBytes(this.input, this.inEnd, src, off, take)
|
|
598
|
+
this.inEnd += take
|
|
599
|
+
return take
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/** Says that no input follows what has been given. */
|
|
603
|
+
endInput(): void {
|
|
604
|
+
this.more = false
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/** Moves the undecoded input to the front of its buffer. */
|
|
608
|
+
compact(): void {
|
|
609
|
+
if (this.inStart > 0) {
|
|
610
|
+
const n: i32 = this.inEnd - this.inStart
|
|
611
|
+
copyBytes(this.input, 0, this.input, this.inStart, n)
|
|
612
|
+
this.inStart = 0
|
|
613
|
+
this.inEnd = n
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/** Adds the decoded bytes not yet checksummed to the member's CRC-32 and length. */
|
|
618
|
+
sum(): void {
|
|
619
|
+
const n: i32 = this.writePos - this.crcPos
|
|
620
|
+
if (n > 0) {
|
|
621
|
+
this.checksum = this.crc.update(this.checksum, this.output, this.crcPos, n)
|
|
622
|
+
this.size = this.size + toU32(n)
|
|
623
|
+
this.crcPos = this.writePos
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
/** Slides the decoded window down once the unread output has been read and the buffer is past half. */
|
|
628
|
+
slide(): void {
|
|
629
|
+
if (this.readPos === this.writePos && this.writePos > GZIP_READER_WINDOW) {
|
|
630
|
+
this.sum()
|
|
631
|
+
const keep: i32 = GZIP_READER_WINDOW
|
|
632
|
+
const shift: i32 = this.writePos - keep
|
|
633
|
+
copyBytes(this.output, 0, this.output, shift, keep)
|
|
634
|
+
this.readPos -= shift
|
|
635
|
+
this.writePos -= shift
|
|
636
|
+
this.crcPos -= shift
|
|
637
|
+
this.histStart = this.histStart > shift ? this.histStart - shift : 0
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/** Decodes as far as the input and the output buffer allow; answers 0 or a final code. */
|
|
642
|
+
advance(): i32 {
|
|
643
|
+
while (true) {
|
|
644
|
+
const avail: i32 = this.inEnd - this.inStart
|
|
645
|
+
if (this.phase === 0) {
|
|
646
|
+
if (avail === 0 && !this.more) {
|
|
647
|
+
if (this.members === 0) {
|
|
648
|
+
return DEFLATE_ERR_TRUNCATED
|
|
649
|
+
}
|
|
650
|
+
this.phase = 3
|
|
651
|
+
return 0
|
|
652
|
+
}
|
|
653
|
+
const body: i32 = gzipReadHeader(this.crc, this.input, this.inStart, this.inEnd)
|
|
654
|
+
if (body === DEFLATE_ERR_TRUNCATED) {
|
|
655
|
+
if (this.more) {
|
|
656
|
+
if (this.inputRoomRaw() === 0 && this.inStart === 0) {
|
|
657
|
+
return GZIP_ERR_HEADER
|
|
658
|
+
}
|
|
659
|
+
return 0
|
|
660
|
+
}
|
|
661
|
+
return DEFLATE_ERR_TRUNCATED
|
|
662
|
+
}
|
|
663
|
+
if (body < 0) {
|
|
664
|
+
return body
|
|
665
|
+
}
|
|
666
|
+
this.inStart = body
|
|
667
|
+
this.inflater.reset()
|
|
668
|
+
this.histStart = this.writePos
|
|
669
|
+
this.checksum = 0
|
|
670
|
+
this.size = 0
|
|
671
|
+
this.phase = 1
|
|
672
|
+
} else if (this.phase === 1) {
|
|
673
|
+
this.slide()
|
|
674
|
+
const outEnd: i32 = toI32(this.output.length)
|
|
675
|
+
if (this.writePos === outEnd) {
|
|
676
|
+
return 0
|
|
677
|
+
}
|
|
678
|
+
const inf: Inflater = this.inflater
|
|
679
|
+
const r: i32 = inf.inflate(
|
|
680
|
+
this.input,
|
|
681
|
+
this.inStart,
|
|
682
|
+
this.inEnd,
|
|
683
|
+
this.more,
|
|
684
|
+
this.output,
|
|
685
|
+
this.writePos,
|
|
686
|
+
outEnd,
|
|
687
|
+
this.histStart
|
|
688
|
+
)
|
|
689
|
+
this.inStart = inf.inPos
|
|
690
|
+
const made: i32 = inf.outPos - this.writePos
|
|
691
|
+
this.writePos = inf.outPos
|
|
692
|
+
this.total = this.total + toI64(made)
|
|
693
|
+
if (this.total > this.limit) {
|
|
694
|
+
return DEFLATE_ERR_OUTPUT_LIMIT
|
|
695
|
+
}
|
|
696
|
+
if (r === INFLATE_DONE) {
|
|
697
|
+
this.sum()
|
|
698
|
+
this.phase = 2
|
|
699
|
+
} else if (r === INFLATE_NEED_INPUT || r === INFLATE_NEED_OUTPUT) {
|
|
700
|
+
return 0
|
|
701
|
+
} else {
|
|
702
|
+
return r
|
|
703
|
+
}
|
|
704
|
+
} else if (this.phase === 2) {
|
|
705
|
+
if (avail < GZIP_TRAILER_SIZE) {
|
|
706
|
+
return this.more ? 0 : DEFLATE_ERR_TRUNCATED
|
|
707
|
+
}
|
|
708
|
+
if (gzipGet32(this.input, this.inStart) !== this.checksum) {
|
|
709
|
+
return GZIP_ERR_CRC
|
|
710
|
+
}
|
|
711
|
+
if (gzipGet32(this.input, this.inStart + 4) !== this.size) {
|
|
712
|
+
return GZIP_ERR_SIZE
|
|
713
|
+
}
|
|
714
|
+
this.inStart += GZIP_TRAILER_SIZE
|
|
715
|
+
this.members += 1
|
|
716
|
+
this.phase = 0
|
|
717
|
+
} else {
|
|
718
|
+
return 0
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
/** The room `input` has without compacting. */
|
|
724
|
+
inputRoomRaw(): i32 {
|
|
725
|
+
return toI32(this.input.length) - this.inEnd
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Copies up to `max` decoded bytes into `out` from `at` and answers how
|
|
730
|
+
* many (at least 1; `max` is at least 1), or `GZIP_NEED_INPUT` when it needs more input first,
|
|
731
|
+
* `GZIP_END` once the body has ended, or a negative error, after which it
|
|
732
|
+
* answers that error again until `start`. A window outside `out` panics.
|
|
733
|
+
*/
|
|
734
|
+
read(out: u8[], at: i32, max: i32): i32 {
|
|
735
|
+
gzipWindow("GzipReader.read", out, at, max)
|
|
736
|
+
if (max < 1) {
|
|
737
|
+
panic("GzipReader.read: room for no bytes")
|
|
738
|
+
}
|
|
739
|
+
if (this.phase < 0) {
|
|
740
|
+
return this.phase
|
|
741
|
+
}
|
|
742
|
+
if (this.readPos === this.writePos) {
|
|
743
|
+
const r: i32 = this.advance()
|
|
744
|
+
if (r < 0) {
|
|
745
|
+
this.phase = r
|
|
746
|
+
return r
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
const n0: i32 = this.writePos - this.readPos
|
|
750
|
+
if (n0 === 0) {
|
|
751
|
+
if (this.phase === 3) {
|
|
752
|
+
return GZIP_END
|
|
753
|
+
}
|
|
754
|
+
return GZIP_NEED_INPUT
|
|
755
|
+
}
|
|
756
|
+
const n: i32 = n0 < max ? n0 : max
|
|
757
|
+
copyBytes(out, at, this.output, this.readPos, n)
|
|
758
|
+
this.readPos += n
|
|
759
|
+
return n
|
|
760
|
+
}
|
|
761
|
+
}
|