quico 0.4.0 → 0.8.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,559 @@
1
+ /*
2
+ * quico: HTTP/3 and QUIC implementation for Node.js
3
+ * Copyright 2025 colocohen
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at:
8
+ *
9
+ * https://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * This file is part of the open-source project hosted at:
12
+ * https://github.com/colocohen/quico
13
+ */
14
+
15
+
16
+ // ============================================================
17
+ // Field-compression primitives shared by HPACK and QPACK.
18
+ //
19
+ // QPACK (RFC 9204, HTTP/3) is a reworking of HPACK (RFC 7541, HTTP/2) for
20
+ // out-of-order delivery. The two differ in how the dynamic table is indexed
21
+ // and updated — but the *wire primitives* underneath are the same document:
22
+ //
23
+ // - Huffman code RFC 7541 Appendix B (QPACK cites it verbatim)
24
+ // - Prefixed integers RFC 7541 §5.1 (QPACK §4.1.1: identical)
25
+ // - String literals RFC 7541 §5.2 (QPACK §4.1.2: identical)
26
+ //
27
+ // Only the static tables differ (RFC 7541 Appendix A vs RFC 9204 Appendix A),
28
+ // and even those are built and searched the same way — hence the shared
29
+ // buildStaticLookup() below.
30
+ //
31
+ // The file is named for HPACK because HPACK is where these primitives are
32
+ // *defined*; QPACK imports them by reference. Nothing here is HTTP/2-specific.
33
+ // ============================================================
34
+
35
+
36
+ // ============================================================
37
+ // Huffman (RFC 7541 Appendix B)
38
+ //
39
+ // 257 entries: symbols 0..255 plus EOS at 256. EOS never appears in a
40
+ // well-formed encoding — it exists only as the source of the padding bit
41
+ // pattern (its code is all-ones), so decoding it is a protocol error.
42
+ // ============================================================
43
+
44
+ var HUFF_CODES = new Uint32Array([
45
+ 0x1ff8,0x7fffd8,0xfffffe2,0xfffffe3,0xfffffe4,0xfffffe5,0xfffffe6,0xfffffe7,
46
+ 0xfffffe8,0xffffea,0x3ffffffc,0xfffffe9,0xfffffea,0x3ffffffd,0xfffffeb,0xfffffec,
47
+ 0xfffffed,0xfffffee,0xfffffef,0xffffff0,0xffffff1,0xffffff2,0x3ffffffe,0xffffff3,
48
+ 0xffffff4,0xffffff5,0xffffff6,0xffffff7,0xffffff8,0xffffff9,0xffffffa,0xffffffb,
49
+ 0x14,0x3f8,0x3f9,0xffa,0x1ff9,0x15,0xf8,0x7fa,0x3fa,0x3fb,0xf9,0x7fb,0xfa,
50
+ 0x16,0x17,0x18,0x0,0x1,0x2,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x5c,0xfb,
51
+ 0x7ffc,0x20,0xffb,0x3fc,0x1ffa,0x21,0x5d,0x5e,0x5f,0x60,0x61,0x62,0x63,0x64,
52
+ 0x65,0x66,0x67,0x68,0x69,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,0x70,0x71,0x72,0xfc,
53
+ 0x73,0xfd,0x1ffb,0x7fff0,0x1ffc,0x3ffc,0x22,0x7ffd,0x3,0x23,0x4,0x24,0x5,0x25,
54
+ 0x26,0x27,0x6,0x74,0x75,0x28,0x29,0x2a,0x7,0x2b,0x76,0x2c,0x8,0x9,0x2d,0x77,
55
+ 0x78,0x79,0x7a,0x7b,0x7ffe,0x7fc,0x3ffd,0x1ffd,0xffffffc,0xfffe6,0x3fffd2,
56
+ 0xfffe7,0xfffe8,0x3fffd3,0x3fffd4,0x3fffd5,0x7fffd9,0x3fffd6,0x7fffda,0x7fffdb,
57
+ 0x7fffdc,0x7fffdd,0x7fffde,0xffffeb,0x7fffdf,0xffffec,0xffffed,0x3fffd7,0x7fffe0,
58
+ 0xffffee,0x7fffe1,0x7fffe2,0x7fffe3,0x7fffe4,0x1fffdc,0x3fffd8,0x7fffe5,0x3fffd9,
59
+ 0x7fffe6,0x7fffe7,0xffffef,0x3fffda,0x1fffdd,0xfffe9,0x3fffdb,0x3fffdc,0x7fffe8,
60
+ 0x7fffe9,0x1fffde,0x7fffea,0x3fffdd,0x3fffde,0xfffff0,0x1fffdf,0x3fffdf,0x7fffeb,
61
+ 0x7fffec,0x1fffe0,0x1fffe1,0x3fffe0,0x1fffe2,0x7fffed,0x3fffe1,0x7fffee,0x7fffef,
62
+ 0xfffea,0x3fffe2,0x3fffe3,0x3fffe4,0x7ffff0,0x3fffe5,0x3fffe6,0x7ffff1,0x3ffffe0,
63
+ 0x3ffffe1,0xfffeb,0x7fff1,0x3fffe7,0x7ffff2,0x3fffe8,0x1ffffec,0x3ffffe2,0x3ffffe3,
64
+ 0x3ffffe4,0x7ffffde,0x7ffffdf,0x3ffffe5,0xfffff1,0x1ffffed,0x7fff2,0x1fffe3,
65
+ 0x3ffffe6,0x7ffffe0,0x7ffffe1,0x3ffffe7,0x7ffffe2,0xfffff2,0x1fffe4,0x1fffe5,
66
+ 0x3ffffe8,0x3ffffe9,0xffffffd,0x7ffffe3,0x7ffffe4,0x7ffffe5,0xfffec,0xfffff3,
67
+ 0xfffed,0x1fffe6,0x3fffe9,0x1fffe7,0x1fffe8,0x7ffff3,0x3fffea,0x3fffeb,0x1ffffee,
68
+ 0x1ffffef,0xfffff4,0xfffff5,0x3ffffea,0x7ffff4,0x3ffffeb,0x7ffffe6,0x3ffffec,
69
+ 0x3ffffed,0x7ffffe7,0x7ffffe8,0x7ffffe9,0x7ffffea,0x7ffffeb,0xffffffe,0x7ffffec,
70
+ 0x7ffffed,0x7ffffee,0x7ffffef,0x7fffff0,0x3ffffee,0x3fffffff
71
+ ]);
72
+
73
+ var HUFF_BITS = new Uint8Array([
74
+ 13,23,28,28,28,28,28,28,28,24,30,28,28,30,28,28,28,28,28,28,28,28,30,28,28,
75
+ 28,28,28,28,28,28,28,6,10,10,12,13,6,8,11,10,10,8,11,8,6,6,6,5,5,5,6,6,6,6,
76
+ 6,6,6,7,8,15,6,12,10,13,6,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,8,7,
77
+ 8,13,19,13,14,6,15,5,6,5,6,5,6,6,6,5,7,7,6,6,6,5,6,7,6,5,5,6,7,7,7,7,7,15,
78
+ 11,14,13,28,20,22,20,20,22,22,22,23,22,23,23,23,23,23,24,23,24,24,22,23,24,
79
+ 23,23,23,23,21,22,23,22,23,23,24,22,21,20,22,22,23,23,21,23,22,22,24,21,22,
80
+ 23,23,21,21,22,21,23,22,23,23,20,22,22,22,23,22,22,23,26,26,20,19,22,23,22,
81
+ 25,26,26,26,27,27,26,24,25,19,21,26,27,27,26,27,24,21,21,26,26,28,27,27,27,
82
+ 20,24,20,21,22,21,21,23,22,22,25,25,24,24,26,23,26,27,26,26,27,27,27,27,27,
83
+ 28,27,27,27,27,27,26,30
84
+ ]);
85
+
86
+ var HUFF_EOS = 256; // index of the EOS symbol; decoding it is an error
87
+ var _HD_HAS_SYM = 1 << 17, _HD_INVALID = 1 << 18; // huffDecode table flags
88
+
89
+ var _huffRoot = (function () {
90
+ var root = {};
91
+ for (var i = 0; i < HUFF_CODES.length; i++) {
92
+ var code = HUFF_CODES[i], bits = HUFF_BITS[i], node = root;
93
+ for (var j = bits - 1; j >= 0; j--) {
94
+ var bit = (code >> j) & 1;
95
+ if (!node[bit]) node[bit] = {};
96
+ node = node[bit];
97
+ }
98
+ node.sym = i;
99
+ }
100
+ return root;
101
+ })();
102
+
103
+
104
+ // Table-driven decoding, four bits at a time.
105
+ //
106
+ // The decoder used to walk the code tree one bit per step: eight object
107
+ // lookups per input byte. Here every internal node of that same tree is a
108
+ // state, and for each state and each 4-bit nibble a table holds what four
109
+ // steps of the walk would have done — the node it ends on, the symbol it
110
+ // completed (at most one: the shortest code is 5 bits), and whether it fell
111
+ // off the tree. Two lookups per byte, same walk, same results.
112
+ //
113
+ // The padding checks at the end need only the state the input stopped in:
114
+ // the bits since the last complete symbol are that node's depth, and they
115
+ // are all ones exactly when its path from the root is.
116
+ var _huffDec = (function () {
117
+ var nodes = [_huffRoot], depth = [0], ones = [1];
118
+ var idOf = new Map([[_huffRoot, 0]]);
119
+ for (var q = 0; q < nodes.length; q++) {
120
+ for (var b = 0; b <= 1; b++) {
121
+ var child = nodes[q][b];
122
+ if (child && child.sym === undefined && !idOf.has(child)) {
123
+ idOf.set(child, nodes.length);
124
+ nodes.push(child);
125
+ depth.push(depth[q] + 1);
126
+ ones.push(ones[q] && b === 1 ? 1 : 0);
127
+ }
128
+ }
129
+ }
130
+ // entry = next | sym << 8 | HAS_SYM | INVALID ; sym is 0..256
131
+ var table = new Int32Array(nodes.length * 16);
132
+ for (var st = 0; st < nodes.length; st++) {
133
+ for (var nib = 0; nib < 16; nib++) {
134
+ var node = nodes[st], entry = 0;
135
+ for (var k = 3; k >= 0; k--) {
136
+ node = node[(nib >> k) & 1];
137
+ if (!node) { entry |= _HD_INVALID; node = _huffRoot; break; }
138
+ if (node.sym !== undefined) {
139
+ entry |= _HD_HAS_SYM | (node.sym << 8);
140
+ node = _huffRoot;
141
+ }
142
+ }
143
+ table[st * 16 + nib] = entry | idOf.get(node);
144
+ }
145
+ }
146
+ return { table: table, depth: Uint8Array.from(depth), ones: Uint8Array.from(ones) };
147
+ })();
148
+
149
+ /**
150
+ * Decode a Huffman-coded byte string (RFC 7541 §5.2). Returns the raw
151
+ * Uint8Array, NOT a string.
152
+ *
153
+ * Field names and values are opaque octets on the wire, and the two callers
154
+ * disagree about how to turn them into text — so that decision stays with
155
+ * them. (QPACK in this codebase has always used TextDecoder/UTF-8; a stricter
156
+ * HPACK may prefer a lossless latin-1 mapping. Baking either one in here would
157
+ * silently change the other.)
158
+ *
159
+ * `strict` turns on the three padding checks the RFC mandates (§5.2): padding
160
+ * longer than 7 bits, padding that is not the EOS prefix (all ones), and an
161
+ * actual EOS symbol in the stream must all be treated as errors. They are off
162
+ * by default so that existing callers keep their current lenient behaviour;
163
+ * decoders that must answer a malformed peer with COMPRESSION_ERROR pass true.
164
+ */
165
+ function huffDecode(buf, strict) {
166
+ var table = _huffDec.table;
167
+ var out = new Uint8Array(((buf.length * 8) / 5) | 0);
168
+ var n = 0, state = 0;
169
+
170
+ for (var i = 0; i < buf.length; i++) {
171
+ var byte = buf[i];
172
+ for (var half = 0; half < 2; half++) {
173
+ var e = table[(state << 4) | (half === 0 ? byte >> 4 : byte & 15)];
174
+ if (e & _HD_INVALID) throw new Error('Invalid Huffman code');
175
+ if (e & _HD_HAS_SYM) {
176
+ var sym = (e >> 8) & 0x1FF;
177
+ if (strict && sym === HUFF_EOS) {
178
+ throw new Error('Huffman: EOS symbol in input');
179
+ }
180
+ out[n++] = sym; // EOS (256) stores as 0, as Uint8Array.from did
181
+ }
182
+ state = e & 0xFF;
183
+ }
184
+ }
185
+
186
+ if (strict && state !== 0) {
187
+ // Trailing bits that did not complete a symbol are padding. They must be
188
+ // at most 7 bits and must be the most-significant bits of the EOS code.
189
+ if (_huffDec.depth[state] > 7) throw new Error('Huffman: padding longer than 7 bits');
190
+ if (!_huffDec.ones[state]) throw new Error('Huffman: padding is not the EOS prefix');
191
+ }
192
+
193
+ // A view, not a copy: every caller turns the bytes into a string at once.
194
+ return out.subarray(0, n);
195
+ }
196
+
197
+
198
+ /** Byte length huffEncode() would produce, without producing it. */
199
+ function huffEncodedLength(bytes) {
200
+ var bits = 0;
201
+ for (var i = 0; i < bytes.length; i++) bits += HUFF_BITS[bytes[i]];
202
+ return (bits + 7) >> 3;
203
+ }
204
+
205
+
206
+ // Powers of two as floats, used wherever a shift would exceed 32 bits.
207
+ // huffEncode accumulates up to 7 leftover bits plus one 30-bit code = 37 bits;
208
+ // readPrefixedInt walks a continuation chain whose shift reaches 7 bits per
209
+ // byte, so the table has to cover the full continuation limit below (an
210
+ // undersized table silently yields undefined → NaN, which readPrefixedInt then
211
+ // reports as "too large").
212
+ var _POW2 = (function () {
213
+ var p = new Float64Array(80);
214
+ for (var i = 0; i < 80; i++) p[i] = Math.pow(2, i);
215
+ return p;
216
+ })();
217
+
218
+
219
+ /**
220
+ * Huffman-encode a byte array (RFC 7541 §5.2). Pads the final byte with the
221
+ * most-significant bits of the EOS code, i.e. ones.
222
+ */
223
+ function huffEncode(bytes) {
224
+ var out = new Uint8Array(huffEncodedLength(bytes));
225
+ var pos = 0;
226
+ var cur = 0, nbits = 0;
227
+
228
+ for (var i = 0; i < bytes.length; i++) {
229
+ var sym = bytes[i];
230
+ cur = cur * _POW2[HUFF_BITS[sym]] + HUFF_CODES[sym];
231
+ nbits += HUFF_BITS[sym];
232
+ while (nbits >= 8) {
233
+ var shift = _POW2[nbits - 8];
234
+ out[pos++] = Math.floor(cur / shift) & 0xff;
235
+ cur = cur % shift;
236
+ nbits -= 8;
237
+ }
238
+ }
239
+
240
+ if (nbits > 0) {
241
+ var pad = 8 - nbits;
242
+ out[pos++] = ((cur * _POW2[pad]) + (_POW2[pad] - 1)) & 0xff;
243
+ }
244
+
245
+ return out;
246
+ }
247
+
248
+
249
+ // ============================================================
250
+ // Prefixed integers (RFC 7541 §5.1, QPACK §4.1.1)
251
+ // ============================================================
252
+
253
+ // A malicious peer can send an unbounded run of continuation bytes to make the
254
+ // decoder spin. RFC 7541 §5.1 notes that implementations must limit the value
255
+ // they will decode; ten continuation bytes already exceed 2^53, so anything
256
+ // longer is either an attack or a value we could not represent anyway.
257
+ var _MAX_CONTINUATION_BYTES = 10;
258
+
259
+ /**
260
+ * Read a prefixed integer at buf[pos].
261
+ * Returns { value, next } or null if the buffer ends mid-integer (truncated).
262
+ *
263
+ * Used by the QPACK header-block parser, the QPACK encoder-instruction parser,
264
+ * and the HPACK header-block parser — the encoding is byte-identical in all
265
+ * three.
266
+ */
267
+ function readPrefixedInt(buf, prefixBits, pos) {
268
+ if (pos >= buf.length) return null;
269
+ var max = (1 << prefixBits) - 1;
270
+ var value = buf[pos] & max;
271
+ pos++;
272
+ if (value < max) return { value: value, next: pos };
273
+
274
+ var m = 0;
275
+ var seen = 0;
276
+ while (pos < buf.length) {
277
+ var byte = buf[pos++];
278
+ // Multiplication rather than `<< m`: m reaches 28+ on large values, where
279
+ // the 32-bit shift wraps and silently produces a wrong (often negative)
280
+ // integer.
281
+ value += (byte & 0x7f) * _POW2[m];
282
+ if ((byte & 0x80) === 0) {
283
+ if (!isFinite(value) || value > Number.MAX_SAFE_INTEGER) {
284
+ throw new Error('Prefixed integer too large');
285
+ }
286
+ return { value: value, next: pos };
287
+ }
288
+ m += 7;
289
+ if (++seen > _MAX_CONTINUATION_BYTES) {
290
+ throw new Error('Prefixed integer too long');
291
+ }
292
+ }
293
+ return null; // truncated mid-integer
294
+ }
295
+
296
+
297
+ /**
298
+ * Encode a prefixed integer (RFC 7541 §5.1). Returns a plain Array of bytes;
299
+ * the caller ORs its opcode bits into byte 0, which is why the high bits above
300
+ * the prefix are left clear here.
301
+ */
302
+ function encodePrefixedInt(value, prefixBits) {
303
+ var max = (1 << prefixBits) - 1;
304
+ if (value < max) return [value];
305
+ var bytes = [max];
306
+ value -= max;
307
+ while (value >= 128) {
308
+ bytes.push((value % 128) | 0x80);
309
+ value = Math.floor(value / 128);
310
+ }
311
+ bytes.push(value);
312
+ return bytes;
313
+ }
314
+
315
+
316
+ // ============================================================
317
+ // String literals (RFC 7541 §5.2, QPACK §4.1.2)
318
+ //
319
+ // Layout: one bit H (Huffman?), then a (prefixBits)-wide prefixed integer
320
+ // holding the byte length, then that many bytes. H sits immediately above the
321
+ // length prefix, so its mask is 1 << prefixBits — 0x80 for the usual 7-bit
322
+ // prefix, 0x08 for the 3-bit prefix QPACK uses for literal names.
323
+ // ============================================================
324
+
325
+ /**
326
+ * Decode a string literal at buf[pos].
327
+ * Returns { bytes, huffman, next }, or null if the literal is truncated.
328
+ *
329
+ * `bytes` is a Uint8Array for the same reason huffDecode returns one: the text
330
+ * codec is the caller's decision.
331
+ */
332
+ function decodeStringLiteral(buf, prefixBits, pos, strict) {
333
+ if (pos >= buf.length) return null;
334
+ var huffman = (buf[pos] & (1 << prefixBits)) !== 0;
335
+ var len = readPrefixedInt(buf, prefixBits, pos);
336
+ if (!len) return null;
337
+ pos = len.next;
338
+ if (pos + len.value > buf.length) return null; // truncated payload
339
+ var raw = buf.subarray(pos, pos + len.value);
340
+ pos += len.value;
341
+ return {
342
+ bytes: huffman ? huffDecode(raw, strict) : raw,
343
+ huffman: huffman,
344
+ next: pos
345
+ };
346
+ }
347
+
348
+
349
+ /**
350
+ * Encode a string literal. Returns a plain Array of bytes whose byte 0 already
351
+ * carries the H bit; the caller ORs in the opcode bits above it.
352
+ *
353
+ * `input` may be a Uint8Array (used as-is) or a string (UTF-8 encoded).
354
+ *
355
+ * useHuffman:
356
+ * undefined — Huffman only when it actually shrinks the string. This is what
357
+ * browsers and nghttp2 do: Huffman can *grow* a run of unusual
358
+ * bytes, and an encoder is free to send either form.
359
+ * true/false — force. `false` is what QPACK here has always done; leaving it
360
+ * that way keeps the bytes on the wire unchanged.
361
+ */
362
+ function encodeStringLiteral(input, prefixBits, useHuffman) {
363
+ var raw = (typeof input === 'string') ? _utf8(input) : input;
364
+ var body = raw;
365
+ var huffman = false;
366
+
367
+ if (useHuffman !== false) {
368
+ var encoded = huffEncode(raw);
369
+ if (useHuffman === true || encoded.length < raw.length) {
370
+ body = encoded;
371
+ huffman = true;
372
+ }
373
+ }
374
+
375
+ var out = encodePrefixedInt(body.length, prefixBits);
376
+ if (huffman) out[0] |= (1 << prefixBits);
377
+ for (var i = 0; i < body.length; i++) out.push(body[i]);
378
+ return out;
379
+ }
380
+
381
+
382
+ // ============================================================
383
+ // Static tables
384
+ // ============================================================
385
+
386
+ /**
387
+ * HPACK static table — RFC 7541 Appendix A. 61 entries, indexed from 1.
388
+ * Stored 0-based here; buildStaticLookup(HPACK_STATIC, 1) yields wire indices.
389
+ */
390
+ var HPACK_STATIC = [
391
+ [':authority',''], [':method','GET'], [':method','POST'], [':path','/'],
392
+ [':path','/index.html'], [':scheme','http'], [':scheme','https'],
393
+ [':status','200'], [':status','204'], [':status','206'], [':status','304'],
394
+ [':status','400'], [':status','404'], [':status','500'],
395
+ ['accept-charset',''], ['accept-encoding','gzip, deflate'],
396
+ ['accept-language',''], ['accept-ranges',''], ['accept',''],
397
+ ['access-control-allow-origin',''], ['age',''], ['allow',''],
398
+ ['authorization',''], ['cache-control',''], ['content-disposition',''],
399
+ ['content-encoding',''], ['content-language',''], ['content-length',''],
400
+ ['content-location',''], ['content-range',''], ['content-type',''],
401
+ ['cookie',''], ['date',''], ['etag',''], ['expect',''], ['expires',''],
402
+ ['from',''], ['host',''], ['if-match',''], ['if-modified-since',''],
403
+ ['if-none-match',''], ['if-range',''], ['if-unmodified-since',''],
404
+ ['last-modified',''], ['link',''], ['location',''], ['max-forwards',''],
405
+ ['proxy-authenticate',''], ['proxy-authorization',''], ['range',''],
406
+ ['referer',''], ['refresh',''], ['retry-after',''], ['server',''],
407
+ ['set-cookie',''], ['strict-transport-security',''],
408
+ ['transfer-encoding',''], ['user-agent',''], ['vary',''], ['via',''],
409
+ ['www-authenticate','']
410
+ ];
411
+
412
+ /**
413
+ * QPACK static table — RFC 9204 Appendix A. 99 entries, indexed from 0.
414
+ * Unrelated to the HPACK table above: different membership, different order.
415
+ */
416
+ var QPACK_STATIC = [
417
+ [':authority',''], [':path','/'], ['age','0'], ['content-disposition',''],
418
+ ['content-length','0'], ['cookie',''], ['date',''], ['etag',''],
419
+ ['if-modified-since',''], ['if-none-match',''], ['last-modified',''],
420
+ ['link',''], ['location',''], ['referer',''], ['set-cookie',''],
421
+ [':method','CONNECT'], [':method','DELETE'], [':method','GET'],
422
+ [':method','HEAD'], [':method','OPTIONS'], [':method','POST'],
423
+ [':method','PUT'], [':scheme','http'], [':scheme','https'],
424
+ [':status','103'], [':status','200'], [':status','304'],
425
+ [':status','404'], [':status','503'], ['accept','*/*'],
426
+ ['accept','application/dns-message'], ['accept-encoding','gzip, deflate, br'],
427
+ ['accept-ranges','bytes'], ['access-control-allow-headers','cache-control'],
428
+ ['access-control-allow-headers','content-type'],
429
+ ['access-control-allow-origin','*'], ['cache-control','max-age=0'],
430
+ ['cache-control','max-age=2592000'], ['cache-control','max-age=604800'],
431
+ ['cache-control','no-cache'], ['cache-control','no-store'],
432
+ ['cache-control','public, max-age=31536000'], ['content-encoding','br'],
433
+ ['content-encoding','gzip'], ['content-type','application/dns-message'],
434
+ ['content-type','application/javascript'], ['content-type','application/json'],
435
+ ['content-type','application/x-www-form-urlencoded'],
436
+ ['content-type','image/gif'], ['content-type','image/jpeg'],
437
+ ['content-type','image/png'], ['content-type','text/css'],
438
+ ['content-type','text/html; charset=utf-8'], ['content-type','text/plain'],
439
+ ['content-type','text/plain;charset=utf-8'], ['range','bytes=0-'],
440
+ ['strict-transport-security','max-age=31536000'],
441
+ ['strict-transport-security','max-age=31536000; includesubdomains'],
442
+ ['strict-transport-security','max-age=31536000; includesubdomains; preload'],
443
+ ['vary','accept-encoding'], ['vary','origin'],
444
+ ['x-content-type-options','nosniff'], ['x-xss-protection','1; mode=block'],
445
+ [':status','100'], [':status','204'], [':status','206'], [':status','302'],
446
+ [':status','400'], [':status','403'], [':status','421'], [':status','425'],
447
+ [':status','500'], ['accept-language',''],
448
+ ['access-control-allow-credentials','FALSE'],
449
+ ['access-control-allow-credentials','TRUE'],
450
+ ['access-control-allow-headers','*'],
451
+ ['access-control-allow-methods','get'],
452
+ ['access-control-allow-methods','get, post, options'],
453
+ ['access-control-allow-methods','options'],
454
+ ['access-control-expose-headers','content-length'],
455
+ ['access-control-request-headers','content-type'],
456
+ ['access-control-request-method','get'],
457
+ ['access-control-request-method','post'], ['alt-svc','clear'],
458
+ ['authorization',''],
459
+ ['content-security-policy',"script-src 'none'; object-src 'none'; base-uri 'none'"],
460
+ ['early-data','1'], ['expect-ct',''], ['forwarded',''], ['if-range',''],
461
+ ['origin',''], ['purpose','prefetch'], ['server',''],
462
+ ['timing-allow-origin','*'], ['upgrade-insecure-requests','1'],
463
+ ['user-agent',''], ['x-forwarded-for',''],
464
+ ['x-frame-options','deny'], ['x-frame-options','sameorigin']
465
+ ];
466
+
467
+
468
+ /**
469
+ * Build the two lookup maps an encoder needs for a static table:
470
+ * full: "name\0value" → index (exact entry → single indexed field)
471
+ * name: "name" → index (name only → literal with name reference)
472
+ *
473
+ * `offset` is added to every index: 0 for QPACK (indexed from 0), 1 for HPACK
474
+ * (indexed from 1). Earlier entries win, so the lowest index is kept for a
475
+ * duplicated name — smaller on the wire, and it matches what other encoders do.
476
+ */
477
+ function buildStaticLookup(table, offset) {
478
+ offset = offset || 0;
479
+ var full = {}, name = {};
480
+ for (var i = 0; i < table.length; i++) {
481
+ var key = table[i][0] + '\0' + table[i][1];
482
+ if (!(key in full)) full[key] = i + offset;
483
+ if (!(table[i][0] in name)) name[table[i][0]] = i + offset;
484
+ }
485
+ return { full: full, name: name };
486
+ }
487
+
488
+
489
+ // ============================================================
490
+ // Text helpers
491
+ //
492
+ // Offered, not imposed — see the note on huffDecode. TextDecoder('utf-8')
493
+ // replaces any non-UTF-8 byte with U+FFFD and loses it; latin-1 is a lossless
494
+ // byte↔char mapping, which is also how node:http surfaces header values. A new
495
+ // decoder should prefer bytesToLatin1; QPACK below stays on UTF-8 because that
496
+ // is what it has always done and this refactor is not the place to change it.
497
+ // ============================================================
498
+
499
+ function bytesToLatin1(bytes) {
500
+ var s = '';
501
+ // Chunked to stay clear of the argument-count limit on very long values.
502
+ for (var i = 0; i < bytes.length; i += 4096) {
503
+ s += String.fromCharCode.apply(null, Array.prototype.slice.call(bytes, i, i + 4096));
504
+ }
505
+ return s;
506
+ }
507
+
508
+ function _utf8(str) {
509
+ return new TextEncoder().encode(str);
510
+ }
511
+
512
+
513
+ // ============================================================
514
+ // Dynamic table entry size
515
+ //
516
+ // RFC 7541 §4.1 and RFC 9204 §3.2.1 define it identically: the name, the
517
+ // value, and 32 bytes of assumed per-entry overhead. The constant stands in
518
+ // for the bookkeeping a real implementation carries, and BOTH PEERS MUST
519
+ // AGREE ON IT — a table sized any other way drifts apart from the peer's and
520
+ // every index after the first eviction resolves to the wrong field.
521
+ //
522
+ // In bytes, not characters. Sizing by string length overflows the table on
523
+ // any non-ASCII header value, and does so silently.
524
+ // ============================================================
525
+
526
+ var ENTRY_OVERHEAD = 32;
527
+
528
+ function entrySize(name, value) {
529
+ return Buffer.byteLength(name, 'utf8') + Buffer.byteLength(value, 'utf8') + ENTRY_OVERHEAD;
530
+ }
531
+
532
+
533
+ export {
534
+ ENTRY_OVERHEAD,
535
+ entrySize,
536
+ // Huffman
537
+ HUFF_CODES,
538
+ HUFF_BITS,
539
+ HUFF_EOS,
540
+ huffDecode,
541
+ huffEncode,
542
+ huffEncodedLength,
543
+
544
+ // Prefixed integers
545
+ readPrefixedInt,
546
+ encodePrefixedInt,
547
+
548
+ // String literals
549
+ decodeStringLiteral,
550
+ encodeStringLiteral,
551
+
552
+ // Static tables
553
+ HPACK_STATIC,
554
+ QPACK_STATIC,
555
+ buildStaticLookup,
556
+
557
+ // Text
558
+ bytesToLatin1
559
+ };