@amritk/nish-x86_64-linux 0.23.0 → 0.24.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.
@@ -0,0 +1,168 @@
1
+ /**
2
+ * `nish/compress/zlib` — the zlib format (RFC 1950) around
3
+ * `nish/compress/deflate`: a two-byte header, the DEFLATE stream and the
4
+ * Adler-32 of the data, big-endian. What HTTP's `Content-Encoding: deflate`
5
+ * means (RFC 9110 §8.4.1.2), and what PNG and many protocols carry.
6
+ *
7
+ * import { DEFLATE_DEFAULT } from "nish/compress/deflate";
8
+ * import { ZlibCompressor, ZlibDecompressor } from "nish/compress/zlib";
9
+ *
10
+ * const z = new ZlibCompressor(DEFLATE_DEFAULT);
11
+ * const end: i32 = z.compress(src, 0, n, out, 0); // -1: no room
12
+ * const got: i32 = new ZlibDecompressor().decompress(out, 0, end, back, 0, n); // < 0: an error
13
+ *
14
+ * The objects allocate their tables once and nothing per call; the free
15
+ * functions build one for a single call. The header written is `0x78` and
16
+ * the `FLEVEL` of the level, with no preset dictionary. Reading refuses a
17
+ * method other than 8, a window over 32 KB, a header whose check fails
18
+ * (`ZLIB_ERR_HEADER`) and a preset dictionary (`ZLIB_ERR_DICTIONARY`, since
19
+ * none can be supplied); a wrong Adler-32 is `ZLIB_ERR_ADLER`, and bytes
20
+ * after the trailer are `DEFLATE_ERR_TRAILING`.
21
+ *
22
+ * Written from RFC 1950.
23
+ */
24
+ import { adler32 } from "nish/compress/checksum"
25
+ import {
26
+ DEFLATE_ERR_OUTPUT_LIMIT,
27
+ DEFLATE_ERR_TRAILING,
28
+ DEFLATE_ERR_TRUNCATED,
29
+ DEFLATE_NO_ROOM,
30
+ Deflater,
31
+ INFLATE_DONE,
32
+ INFLATE_NEED_OUTPUT,
33
+ Inflater,
34
+ } from "nish/compress/deflate"
35
+
36
+ /** A zlib header that is not one: the wrong method, a window over 32 KB, or a failed check. */
37
+ export const ZLIB_ERR_HEADER: i32 = -12
38
+
39
+ /** The trailer's Adler-32 is not the Adler-32 of the data. */
40
+ export const ZLIB_ERR_ADLER: i32 = -13
41
+
42
+ /** The stream needs a preset dictionary (`FDICT`), which this reader has no way to be given. */
43
+ export const ZLIB_ERR_DICTIONARY: i32 = -14
44
+
45
+ /** Panics unless `buf[off .. off + len)` lies inside `buf`. */
46
+ const zlibWindow = (what: string, buf: u8[], off: i32, len: i32): void => {
47
+ if (off < 0 || len < 0 || off > toI32(buf.length) - len) {
48
+ panic(`${what}: the window [${off}, ${off} + ${len}) is outside a buffer of ${toI32(buf.length)} bytes`)
49
+ }
50
+ }
51
+
52
+ /** A zlib compressor at one level, allocated once. Not shared between threads. */
53
+ export class ZlibCompressor {
54
+ readonly deflater: Deflater
55
+
56
+ /** A compressor at `level`, 0 to 9; another level panics. */
57
+ constructor(level: i32) {
58
+ this.deflater = new Deflater(level)
59
+ }
60
+
61
+ /**
62
+ * Compresses `src[off .. off + len)` into `out` from `at` as one zlib
63
+ * stream and answers where it ends, or `DEFLATE_NO_ROOM` (-1) when `out`
64
+ * cannot hold it; `compressBound(len)` bytes always can. A window outside
65
+ * its array panics. Allocates nothing.
66
+ */
67
+ compress(src: u8[], off: i32, len: i32, out: u8[], at: i32): i32 {
68
+ zlibWindow("ZlibCompressor.compress", src, off, len)
69
+ zlibWindow("ZlibCompressor.compress", out, at, 0)
70
+ if (toI32(out.length) - at < 6) {
71
+ return DEFLATE_NO_ROOM
72
+ }
73
+ const level: i32 = this.deflater.level
74
+ // FLEVEL (§2.2): fastest, fast, default, slowest.
75
+ let flevel: i32 = 3
76
+ if (level < 2) {
77
+ flevel = 0
78
+ } else if (level < 6) {
79
+ flevel = 1
80
+ } else if (level === 6) {
81
+ flevel = 2
82
+ }
83
+ const cmf: i32 = 0x78
84
+ let flg: i32 = flevel << 6
85
+ flg += (toI32(31) - ((cmf * 256 + flg) % 31)) % 31
86
+ out[at] = toU8(cmf)
87
+ out[at + 1] = toU8(flg)
88
+ const end: i32 = this.deflater.compress(src, off, len, out, at + 2)
89
+ if (end < 0 || toI32(out.length) - end < 4) {
90
+ return DEFLATE_NO_ROOM
91
+ }
92
+ const a: u32 = adler32(src, off, len)
93
+ out[end] = toU8(a >>> 24)
94
+ out[end + 1] = toU8((a >>> 16) & 255)
95
+ out[end + 2] = toU8((a >>> 8) & 255)
96
+ out[end + 3] = toU8(a & 255)
97
+ return end + 4
98
+ }
99
+ }
100
+
101
+ /** A zlib decompressor, allocated once. Not shared between threads. */
102
+ export class ZlibDecompressor {
103
+ readonly inflater: Inflater
104
+
105
+ /** Builds the tables. */
106
+ constructor() {
107
+ this.inflater = new Inflater()
108
+ }
109
+
110
+ /**
111
+ * Decompresses the zlib stream `src[off .. off + len)` into `out` from
112
+ * `at`, writing at most `limit` bytes and never past `out`'s end, and
113
+ * answers where the output ends, or a negative `ZLIB_ERR_*` or
114
+ * `DEFLATE_ERR_*`. A window outside its array, or a negative `limit`,
115
+ * panics. Allocates nothing.
116
+ */
117
+ decompress(src: u8[], off: i32, len: i32, out: u8[], at: i32, limit: i32): i32 {
118
+ zlibWindow("ZlibDecompressor.decompress", src, off, len)
119
+ zlibWindow("ZlibDecompressor.decompress", out, at, 0)
120
+ if (limit < 0) {
121
+ panic("ZlibDecompressor.decompress: a negative limit")
122
+ }
123
+ if (len < 2) {
124
+ return DEFLATE_ERR_TRUNCATED
125
+ }
126
+ const cmf: i32 = toI32(src[off])
127
+ const flg: i32 = toI32(src[off + 1])
128
+ if ((cmf & 15) !== 8 || cmf >> 4 > 7 || (cmf * 256 + flg) % 31 !== 0) {
129
+ return ZLIB_ERR_HEADER
130
+ }
131
+ if ((flg & 0x20) !== 0) {
132
+ return ZLIB_ERR_DICTIONARY
133
+ }
134
+ const room: i32 = toI32(out.length) - at
135
+ const outEnd: i32 = at + (limit < room ? limit : room)
136
+ const inf: Inflater = this.inflater
137
+ inf.reset()
138
+ const end: i32 = off + len
139
+ const r: i32 = inf.inflate(src, off + 2, end, false, out, at, outEnd, at)
140
+ if (r === INFLATE_NEED_OUTPUT) {
141
+ return DEFLATE_ERR_OUTPUT_LIMIT
142
+ }
143
+ if (r !== INFLATE_DONE) {
144
+ return r
145
+ }
146
+ const q: i32 = inf.inPos
147
+ if (end - q < 4) {
148
+ return DEFLATE_ERR_TRUNCATED
149
+ }
150
+ const want: u32 =
151
+ (toU32(src[q]) << 24) | (toU32(src[q + 1]) << 16) | (toU32(src[q + 2]) << 8) | toU32(src[q + 3])
152
+ if (adler32(out, at, inf.outPos - at) !== want) {
153
+ return ZLIB_ERR_ADLER
154
+ }
155
+ if (q + 4 !== end) {
156
+ return DEFLATE_ERR_TRAILING
157
+ }
158
+ return inf.outPos
159
+ }
160
+ }
161
+
162
+ /** `ZlibCompressor.compress` at `level`, building the compressor for this call alone. */
163
+ export const zlibCompress = (src: u8[], off: i32, len: i32, out: u8[], at: i32, level: i32): i32 =>
164
+ new ZlibCompressor(level).compress(src, off, len, out, at)
165
+
166
+ /** `ZlibDecompressor.decompress`, building the decompressor for this call alone. */
167
+ export const zlibDecompress = (src: u8[], off: i32, len: i32, out: u8[], at: i32, limit: i32): i32 =>
168
+ new ZlibDecompressor().decompress(src, off, len, out, at, limit)
@@ -215,9 +215,8 @@ const sha256Compress = (h: u32[], w: u32[], src: u8[], at: i32): void => {
215
215
  // early exit, because that is the shape that proves the plain `w[t]` and
216
216
  // `h[i]` indices in range and drops their bounds checks. The computed ones —
217
217
  // the byte reads in `sha256LoadWord` and `w[t - 2]` and its kin — keep a
218
- // check each; measured, removing every check (with `--unchecked-indexing`,
219
- // which no longer reaches a `nish/` module at all) moved the 1 MiB time by
220
- // less than the run-to-run noise.
218
+ // check each; measured, removing every check moved the 1 MiB time by less
219
+ // than the run-to-run noise.
221
220
  if (toI32(h.length) >= 8 && toI32(w.length) >= 64 && at >= 0 && at <= toI32(src.length) - SHA256_BLOCK) {
222
221
  // Step 1: the message schedule.
223
222
  for (let t: i32 = 0; t < 16; t += 1) {
@@ -21,10 +21,9 @@
21
21
  * subtraction needs no bias and a carry is a floor division that works the
22
22
  * same on a negative limb.
23
23
  *
24
- * Signed `i64` overflow is undefined behaviour in this language, and
25
- * `--wrapping` does not reach a `nish/` module (only `wrappingAdd`,
26
- * `wrappingSub` and `wrappingMul` from `nish:unsafe` define a wrap, at the
27
- * site that calls one), so the limb bounds below are not a nicety: each function states what it accepts and what it answers, and
24
+ * Signed `i64` overflow is undefined behaviour in this language (only
25
+ * `wrappingAdd`, `wrappingSub` and `wrappingMul` from `nish:unsafe` define a
26
+ * wrap, at the site that calls one), so the limb bounds below are not a nicety: each function states what it accepts and what it answers, and
28
27
  * `f25519Mul` says why its sums fit.
29
28
  *
30
29
  * **Constant time, by construction, and in part by disassembly.** Nothing
@@ -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)