quico 0.3.0 → 0.7.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/README.md +2489 -147
- package/index.js +56 -34
- package/package.json +3 -3
- package/src/agent.js +364 -36
- package/src/client.js +474 -118
- package/src/client_profile.js +266 -0
- package/src/connect_udp.js +578 -0
- package/src/connect_udp_client.js +770 -0
- package/src/crypto.js +257 -140
- package/src/h1.js +953 -0
- package/src/h1_client.js +731 -0
- package/src/h1_server.js +624 -0
- package/src/h2.js +1325 -0
- package/src/h2_client.js +675 -0
- package/src/h2_fingerprint.js +403 -0
- package/src/h2_frames.js +582 -0
- package/src/h2_server.js +862 -0
- package/src/h3.js +1966 -469
- package/src/h3_client.js +476 -48
- package/src/h3_server.js +1349 -94
- package/src/hpack.js +455 -0
- package/src/hpack_primitives.js +520 -0
- package/src/http_common.js +901 -0
- package/src/ja4h.js +256 -0
- package/src/qpack.js +555 -0
- package/src/quic_connection.js +6666 -632
- package/src/quic_server.js +822 -69
- package/src/quic_socket.js +183 -7
- package/src/quic_version.js +143 -0
- package/src/server.js +843 -115
- package/src/server_stats.js +93 -0
- package/src/streams.js +376 -36
- package/src/tcp_listener.js +557 -0
- package/src/tls_bridge.js +516 -75
- package/src/tls_options.js +108 -0
- package/src/transport.js +541 -39
- package/src/utils.js +94 -12
- package/src/webtransport.js +433 -436
- package/src/ws.js +624 -0
- package/src/ws_client.js +641 -0
- package/src/ws_server.js +833 -0
- package/src/wt_stream.js +618 -0
package/src/hpack.js
ADDED
|
@@ -0,0 +1,455 @@
|
|
|
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
|
+
import {
|
|
16
|
+
HPACK_STATIC,
|
|
17
|
+
buildStaticLookup,
|
|
18
|
+
readPrefixedInt,
|
|
19
|
+
encodePrefixedInt,
|
|
20
|
+
decodeStringLiteral,
|
|
21
|
+
encodeStringLiteral,
|
|
22
|
+
bytesToLatin1,
|
|
23
|
+
entrySize,
|
|
24
|
+
ENTRY_OVERHEAD
|
|
25
|
+
} from './hpack_primitives.js';
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
// ============================================================
|
|
29
|
+
// HPACK — header compression for HTTP/2 (RFC 7541).
|
|
30
|
+
//
|
|
31
|
+
// The wire primitives (Huffman, prefixed integers, string literals) are
|
|
32
|
+
// shared with QPACK and live in hpack_primitives.js. What is HPACK's own, and
|
|
33
|
+
// what lives here, is the dynamic table: how entries are indexed, when they
|
|
34
|
+
// are inserted, and how they are evicted.
|
|
35
|
+
//
|
|
36
|
+
// The difference from QPACK is worth stating, because the two look alike and
|
|
37
|
+
// are not. HPACK assumes ordered delivery — TCP — so a header block may
|
|
38
|
+
// modify the dynamic table INLINE, and the decoder is guaranteed to have
|
|
39
|
+
// applied every earlier block before this one. QPACK cannot assume that, so
|
|
40
|
+
// it moves table updates onto a separate stream and carries an insert count
|
|
41
|
+
// in every block. That is why h3.js does not simply use this file.
|
|
42
|
+
//
|
|
43
|
+
// Two endpoints, two tables, four in total: each endpoint's ENCODER table
|
|
44
|
+
// mirrors the peer's DECODER table. They stay in step only because every
|
|
45
|
+
// block is applied exactly once and in order; a single desync corrupts every
|
|
46
|
+
// header that follows, on every stream, which is why the decoder below treats
|
|
47
|
+
// any inconsistency as fatal rather than skipping a field.
|
|
48
|
+
// ============================================================
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
// RFC 7541 §4.1. The 32 is an allowance for the entry's own bookkeeping, fixed
|
|
52
|
+
// by the spec so that both peers compute the same size for the same entry.
|
|
53
|
+
// From hpack_primitives — shared with QPACK, which defines it identically.
|
|
54
|
+
|
|
55
|
+
// SETTINGS_HEADER_TABLE_SIZE default (RFC 7540 §6.5.2).
|
|
56
|
+
var DEFAULT_TABLE_SIZE = 4096;
|
|
57
|
+
|
|
58
|
+
// A decoded header list can be far larger than the block that encoded it: an
|
|
59
|
+
// indexed field is one byte and may expand to kilobytes, and a block may
|
|
60
|
+
// repeat it thousands of times. Without a ceiling a few hundred bytes on the
|
|
61
|
+
// wire become gigabytes in memory — the "HPACK bomb". RFC 7541 §7.3 names the
|
|
62
|
+
// hazard; the limit is ours to choose and is announced to the peer as
|
|
63
|
+
// SETTINGS_MAX_HEADER_LIST_SIZE.
|
|
64
|
+
var DEFAULT_MAX_HEADER_LIST_SIZE = 128 * 1024;
|
|
65
|
+
|
|
66
|
+
var _staticLookup = buildStaticLookup(HPACK_STATIC, 1); // HPACK indexes from 1
|
|
67
|
+
var STATIC_FULL = _staticLookup.full;
|
|
68
|
+
var STATIC_NAME = _staticLookup.name;
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
function hpackError(message) {
|
|
72
|
+
var err = new Error(message);
|
|
73
|
+
err.code = 'HPACK_COMPRESSION_ERROR';
|
|
74
|
+
// RFC 7540 §4.3: a header-block failure is a CONNECTION error of type
|
|
75
|
+
// COMPRESSION_ERROR, never a stream error. The tables are connection-wide,
|
|
76
|
+
// so once they diverge nothing on any stream can be trusted.
|
|
77
|
+
err.h2ErrorCode = 0x9; // COMPRESSION_ERROR
|
|
78
|
+
return err;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
// ============================================================
|
|
84
|
+
// Dynamic table
|
|
85
|
+
//
|
|
86
|
+
// Index 1..61 is the static table; the dynamic table continues from 62 with
|
|
87
|
+
// the NEWEST entry first, so every insertion renumbers every existing entry.
|
|
88
|
+
// Storing newest-first in a plain array makes that renumbering free.
|
|
89
|
+
// ============================================================
|
|
90
|
+
|
|
91
|
+
function DynamicTable(maxSize) {
|
|
92
|
+
var entries = []; // [ [name, value, size], ... ] newest first
|
|
93
|
+
var size = 0;
|
|
94
|
+
var limit = maxSize === undefined ? DEFAULT_TABLE_SIZE : maxSize;
|
|
95
|
+
|
|
96
|
+
// The largest size the peer has allowed us via SETTINGS_HEADER_TABLE_SIZE.
|
|
97
|
+
// `limit` may be reduced below this by a dynamic table size update, but
|
|
98
|
+
// never raised above it.
|
|
99
|
+
var settingsLimit = limit;
|
|
100
|
+
|
|
101
|
+
function evict() {
|
|
102
|
+
while (size > limit && entries.length > 0) {
|
|
103
|
+
size -= entries.pop()[2];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
get size() { return size; },
|
|
109
|
+
get limit() { return limit; },
|
|
110
|
+
get length() { return entries.length; },
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Insert at the front (RFC 7541 §4.4).
|
|
114
|
+
*
|
|
115
|
+
* An entry larger than the whole table is not an error: the table is
|
|
116
|
+
* simply emptied and the entry is not stored. Treating it as an error
|
|
117
|
+
* would break a legal encoder.
|
|
118
|
+
*/
|
|
119
|
+
add: function (name, value) {
|
|
120
|
+
var s = entrySize(name, value);
|
|
121
|
+
if (s > limit) { entries = []; size = 0; return; }
|
|
122
|
+
entries.unshift([name, value, s]);
|
|
123
|
+
size += s;
|
|
124
|
+
evict();
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
/** 1-based index across static then dynamic, or null. */
|
|
128
|
+
get: function (index) {
|
|
129
|
+
if (index >= 1 && index <= HPACK_STATIC.length) {
|
|
130
|
+
return HPACK_STATIC[index - 1];
|
|
131
|
+
}
|
|
132
|
+
var di = index - HPACK_STATIC.length - 1;
|
|
133
|
+
if (di < 0 || di >= entries.length) return null;
|
|
134
|
+
return entries[di];
|
|
135
|
+
},
|
|
136
|
+
|
|
137
|
+
/** Search for an exact or name-only match. Returns { index, full }. */
|
|
138
|
+
find: function (name, value) {
|
|
139
|
+
var full = STATIC_FULL[name + '\u0000' + value];
|
|
140
|
+
if (full !== undefined) return { index: full, full: true };
|
|
141
|
+
|
|
142
|
+
var nameIdx = STATIC_NAME[name];
|
|
143
|
+
for (var i = 0; i < entries.length; i++) {
|
|
144
|
+
if (entries[i][0] === name) {
|
|
145
|
+
var idx = HPACK_STATIC.length + 1 + i;
|
|
146
|
+
if (entries[i][1] === value) return { index: idx, full: true };
|
|
147
|
+
if (nameIdx === undefined) nameIdx = idx;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (nameIdx !== undefined) return { index: nameIdx, full: false };
|
|
151
|
+
return null;
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
/** A dynamic table size update from the peer, or a local decision. */
|
|
155
|
+
setLimit: function (newLimit) {
|
|
156
|
+
if (newLimit > settingsLimit) {
|
|
157
|
+
// §6.3: an update above what SETTINGS permitted is a decoder error.
|
|
158
|
+
// Allowing it would let a peer force unbounded memory use with a
|
|
159
|
+
// two-byte instruction.
|
|
160
|
+
throw hpackError('dynamic table size update ' + newLimit +
|
|
161
|
+
' exceeds the agreed maximum ' + settingsLimit);
|
|
162
|
+
}
|
|
163
|
+
limit = newLimit;
|
|
164
|
+
evict();
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
/** SETTINGS_HEADER_TABLE_SIZE changed. */
|
|
168
|
+
setSettingsLimit: function (newLimit) {
|
|
169
|
+
settingsLimit = newLimit;
|
|
170
|
+
if (limit > settingsLimit) { limit = settingsLimit; evict(); }
|
|
171
|
+
},
|
|
172
|
+
|
|
173
|
+
_entries: function () { return entries; }
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
// ============================================================
|
|
179
|
+
// Decoder
|
|
180
|
+
// ============================================================
|
|
181
|
+
|
|
182
|
+
function HpackDecoder(options) {
|
|
183
|
+
if (!(this instanceof HpackDecoder)) return new HpackDecoder(options);
|
|
184
|
+
options = options || {};
|
|
185
|
+
|
|
186
|
+
var table = new DynamicTable(options.maxTableSize);
|
|
187
|
+
var maxHeaderListSize = options.maxHeaderListSize || DEFAULT_MAX_HEADER_LIST_SIZE;
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Decode one header block into [ [name, value], ... ].
|
|
191
|
+
*
|
|
192
|
+
* Order is preserved: HTTP/2 pseudo-headers must come first and repeated
|
|
193
|
+
* fields keep their order, so an object would be the wrong shape here.
|
|
194
|
+
* Throws on any inconsistency — see hpackError.
|
|
195
|
+
*/
|
|
196
|
+
this.decode = function (block) {
|
|
197
|
+
if (!Buffer.isBuffer(block)) block = Buffer.from(block);
|
|
198
|
+
|
|
199
|
+
var out = [];
|
|
200
|
+
var listSize = 0;
|
|
201
|
+
var pos = 0;
|
|
202
|
+
|
|
203
|
+
// §4.2: dynamic table size updates may only appear at the very start of a
|
|
204
|
+
// block. A peer that slips one in later is trying to resize the table
|
|
205
|
+
// between two fields, which the encoder's own view cannot reflect.
|
|
206
|
+
var allowSizeUpdate = true;
|
|
207
|
+
|
|
208
|
+
while (pos < block.length) {
|
|
209
|
+
var byte = block[pos];
|
|
210
|
+
|
|
211
|
+
if ((byte & 0x80) === 0x80) {
|
|
212
|
+
// ---- Indexed Header Field (§6.1) ----
|
|
213
|
+
var idx = readPrefixedInt(block, 7, pos);
|
|
214
|
+
if (!idx) throw hpackError('truncated indexed field');
|
|
215
|
+
pos = idx.next;
|
|
216
|
+
if (idx.value === 0) {
|
|
217
|
+
// Index 0 is not a valid entry — the encoding exists but the RFC
|
|
218
|
+
// reserves it precisely so that a zero cannot be mistaken for one.
|
|
219
|
+
throw hpackError('indexed field with index 0');
|
|
220
|
+
}
|
|
221
|
+
var entry = table.get(idx.value);
|
|
222
|
+
if (!entry) throw hpackError('index ' + idx.value + ' is not in the table');
|
|
223
|
+
out.push([entry[0], entry[1]]);
|
|
224
|
+
listSize += entrySize(entry[0], entry[1]);
|
|
225
|
+
allowSizeUpdate = false;
|
|
226
|
+
|
|
227
|
+
} else if ((byte & 0xc0) === 0x40) {
|
|
228
|
+
// ---- Literal with Incremental Indexing (§6.2.1) ----
|
|
229
|
+
var r = readLiteral(block, pos, 6);
|
|
230
|
+
pos = r.next;
|
|
231
|
+
table.add(r.name, r.value);
|
|
232
|
+
out.push([r.name, r.value]);
|
|
233
|
+
listSize += entrySize(r.name, r.value);
|
|
234
|
+
allowSizeUpdate = false;
|
|
235
|
+
|
|
236
|
+
} else if ((byte & 0xe0) === 0x20) {
|
|
237
|
+
// ---- Dynamic Table Size Update (§6.3) ----
|
|
238
|
+
if (!allowSizeUpdate) {
|
|
239
|
+
throw hpackError('dynamic table size update must precede the fields in a block');
|
|
240
|
+
}
|
|
241
|
+
var sz = readPrefixedInt(block, 5, pos);
|
|
242
|
+
if (!sz) throw hpackError('truncated table size update');
|
|
243
|
+
pos = sz.next;
|
|
244
|
+
table.setLimit(sz.value);
|
|
245
|
+
|
|
246
|
+
} else if ((byte & 0xf0) === 0x10) {
|
|
247
|
+
// ---- Literal Never Indexed (§6.2.3) ----
|
|
248
|
+
var n = readLiteral(block, pos, 4);
|
|
249
|
+
pos = n.next;
|
|
250
|
+
out.push([n.name, n.value, true]); // third slot: never-index flag
|
|
251
|
+
listSize += entrySize(n.name, n.value);
|
|
252
|
+
allowSizeUpdate = false;
|
|
253
|
+
|
|
254
|
+
} else {
|
|
255
|
+
// ---- Literal without Indexing (§6.2.2) ----
|
|
256
|
+
var w = readLiteral(block, pos, 4);
|
|
257
|
+
pos = w.next;
|
|
258
|
+
out.push([w.name, w.value]);
|
|
259
|
+
listSize += entrySize(w.name, w.value);
|
|
260
|
+
allowSizeUpdate = false;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (listSize > maxHeaderListSize) {
|
|
264
|
+
throw hpackError('decoded header list exceeds ' + maxHeaderListSize + ' bytes');
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return out;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
function readLiteral(block, pos, prefixBits) {
|
|
272
|
+
var nameIdx = readPrefixedInt(block, prefixBits, pos);
|
|
273
|
+
if (!nameIdx) throw hpackError('truncated literal field');
|
|
274
|
+
pos = nameIdx.next;
|
|
275
|
+
|
|
276
|
+
var name;
|
|
277
|
+
if (nameIdx.value === 0) {
|
|
278
|
+
var nameLit = decodeStringLiteral(block, 7, pos, true);
|
|
279
|
+
if (!nameLit) throw hpackError('truncated literal name');
|
|
280
|
+
pos = nameLit.next;
|
|
281
|
+
name = bytesToLatin1(nameLit.bytes);
|
|
282
|
+
if (name.length === 0) throw hpackError('empty header name');
|
|
283
|
+
// §8.1.2: field names in HTTP/2 are lowercase, and a mismatch must be
|
|
284
|
+
// treated as malformed. Checking it here catches it before the value
|
|
285
|
+
// reaches an application that compares names case-sensitively.
|
|
286
|
+
if (/[A-Z]/.test(name)) throw hpackError('header name is not lowercase: ' + name);
|
|
287
|
+
} else {
|
|
288
|
+
var entry = table.get(nameIdx.value);
|
|
289
|
+
if (!entry) throw hpackError('name index ' + nameIdx.value + ' is not in the table');
|
|
290
|
+
name = entry[0];
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
var valLit = decodeStringLiteral(block, 7, pos, true);
|
|
294
|
+
if (!valLit) throw hpackError('truncated literal value');
|
|
295
|
+
return { name: name, value: bytesToLatin1(valLit.bytes), next: valLit.next };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/** The peer changed SETTINGS_HEADER_TABLE_SIZE. */
|
|
299
|
+
this.setMaxTableSize = function (size) { table.setSettingsLimit(size); };
|
|
300
|
+
this.setMaxHeaderListSize = function (size) { maxHeaderListSize = size; };
|
|
301
|
+
|
|
302
|
+
Object.defineProperty(this, 'tableSize', { get: function () { return table.size; } });
|
|
303
|
+
Object.defineProperty(this, 'table', { get: function () { return table; } });
|
|
304
|
+
|
|
305
|
+
return this;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
// ============================================================
|
|
310
|
+
// Encoder
|
|
311
|
+
// ============================================================
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Header names whose values must never enter the dynamic table.
|
|
315
|
+
*
|
|
316
|
+
* CRIME-style attacks recover a secret by observing how the compressed size
|
|
317
|
+
* changes when an attacker-controlled header sits next to it: a guess that
|
|
318
|
+
* matches compresses better. Marking these never-indexed (§7.1.3) removes the
|
|
319
|
+
* signal. The cost is a few bytes per request on exactly the headers where
|
|
320
|
+
* that is the right trade.
|
|
321
|
+
*/
|
|
322
|
+
var NEVER_INDEX = {
|
|
323
|
+
'authorization': true,
|
|
324
|
+
'proxy-authorization': true,
|
|
325
|
+
'cookie': true,
|
|
326
|
+
'set-cookie': true
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
function HpackEncoder(options) {
|
|
330
|
+
if (!(this instanceof HpackEncoder)) return new HpackEncoder(options);
|
|
331
|
+
options = options || {};
|
|
332
|
+
|
|
333
|
+
var table = new DynamicTable(options.maxTableSize);
|
|
334
|
+
var pendingSizeUpdate = null;
|
|
335
|
+
var useHuffman = options.huffman !== false;
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Encode a header list.
|
|
339
|
+
*
|
|
340
|
+
* `headers` may be [ [name, value], ... ] — order preserved, repeats kept —
|
|
341
|
+
* or a plain object, which is convenient but cannot express either.
|
|
342
|
+
*/
|
|
343
|
+
this.encode = function (headers) {
|
|
344
|
+
var list = normalize(headers);
|
|
345
|
+
var out = [];
|
|
346
|
+
|
|
347
|
+
// A pending size update goes first, because §4.2 requires it to precede
|
|
348
|
+
// the fields.
|
|
349
|
+
if (pendingSizeUpdate !== null) {
|
|
350
|
+
var upd = encodePrefixedInt(pendingSizeUpdate, 5);
|
|
351
|
+
upd[0] |= 0x20;
|
|
352
|
+
push(out, upd);
|
|
353
|
+
pendingSizeUpdate = null;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
for (var i = 0; i < list.length; i++) {
|
|
357
|
+
var name = list[i][0];
|
|
358
|
+
var value = list[i][1];
|
|
359
|
+
var neverIndex = list[i][2] === true || NEVER_INDEX[name] === true;
|
|
360
|
+
|
|
361
|
+
var hit = table.find(name, value);
|
|
362
|
+
|
|
363
|
+
if (hit && hit.full && !neverIndex) {
|
|
364
|
+
// Indexed Header Field: 1xxxxxxx
|
|
365
|
+
var idx = encodePrefixedInt(hit.index, 7);
|
|
366
|
+
idx[0] |= 0x80;
|
|
367
|
+
push(out, idx);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
var nameIndex = hit ? hit.index : 0;
|
|
372
|
+
|
|
373
|
+
if (neverIndex) {
|
|
374
|
+
// Literal Never Indexed: 0001xxxx. Deliberately NOT added to the
|
|
375
|
+
// table — that is the entire point.
|
|
376
|
+
var nx = encodePrefixedInt(nameIndex, 4);
|
|
377
|
+
nx[0] |= 0x10;
|
|
378
|
+
push(out, nx);
|
|
379
|
+
if (nameIndex === 0) push(out, encodeStringLiteral(name, 7, huff()));
|
|
380
|
+
push(out, encodeStringLiteral(value, 7, huff()));
|
|
381
|
+
continue;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Literal with Incremental Indexing: 01xxxxxx
|
|
385
|
+
var li = encodePrefixedInt(nameIndex, 6);
|
|
386
|
+
li[0] |= 0x40;
|
|
387
|
+
push(out, li);
|
|
388
|
+
if (nameIndex === 0) push(out, encodeStringLiteral(name, 7, huff()));
|
|
389
|
+
push(out, encodeStringLiteral(value, 7, huff()));
|
|
390
|
+
table.add(name, value);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
return Buffer.from(out);
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
function huff() { return useHuffman ? undefined : false; }
|
|
397
|
+
|
|
398
|
+
function push(out, bytes) {
|
|
399
|
+
for (var i = 0; i < bytes.length; i++) out.push(bytes[i]);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function normalize(headers) {
|
|
403
|
+
if (Array.isArray(headers)) {
|
|
404
|
+
var list = [];
|
|
405
|
+
for (var i = 0; i < headers.length; i++) {
|
|
406
|
+
var h = headers[i];
|
|
407
|
+
list.push([String(h[0]).toLowerCase(), String(h[1]), h[2]]);
|
|
408
|
+
}
|
|
409
|
+
return list;
|
|
410
|
+
}
|
|
411
|
+
var out = [];
|
|
412
|
+
for (var name in headers) {
|
|
413
|
+
if (!Object.prototype.hasOwnProperty.call(headers, name)) continue;
|
|
414
|
+
var v = headers[name];
|
|
415
|
+
if (v === undefined || v === null) continue;
|
|
416
|
+
var lower = String(name).toLowerCase();
|
|
417
|
+
if (Array.isArray(v)) {
|
|
418
|
+
for (var j = 0; j < v.length; j++) out.push([lower, String(v[j])]);
|
|
419
|
+
} else {
|
|
420
|
+
out.push([lower, String(v)]);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return out;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Change our table size and tell the peer in the next block.
|
|
428
|
+
*
|
|
429
|
+
* The update must be ANNOUNCED before it takes effect on the wire, so the
|
|
430
|
+
* table is not resized here — the instruction is queued and applied when the
|
|
431
|
+
* next block is built, keeping our table and the peer's decoder in step.
|
|
432
|
+
*/
|
|
433
|
+
this.setMaxTableSize = function (size) {
|
|
434
|
+
table.setSettingsLimit(size);
|
|
435
|
+
pendingSizeUpdate = Math.min(size, table.limit);
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
Object.defineProperty(this, 'tableSize', { get: function () { return table.size; } });
|
|
439
|
+
Object.defineProperty(this, 'table', { get: function () { return table; } });
|
|
440
|
+
|
|
441
|
+
return this;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
export {
|
|
446
|
+
HpackEncoder,
|
|
447
|
+
HpackDecoder,
|
|
448
|
+
DynamicTable,
|
|
449
|
+
HPACK_STATIC,
|
|
450
|
+
NEVER_INDEX,
|
|
451
|
+
entrySize,
|
|
452
|
+
hpackError,
|
|
453
|
+
DEFAULT_TABLE_SIZE,
|
|
454
|
+
DEFAULT_MAX_HEADER_LIST_SIZE
|
|
455
|
+
};
|