tunnelfetch 1.6.4 → 1.6.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tunnelfetch",
3
- "version": "1.6.4",
3
+ "version": "1.6.5",
4
4
  "description": "A fetch-shaped HTTP client that can route through HTTP CONNECT / HTTPS / SOCKS5 proxies on runtimes with only raw TCP, such as Cloudflare Workers. Implements TLS in userland because the runtime cannot verify a tunnelled peer.",
5
5
  "keywords": [
6
6
  "fetch",
package/src/client.js CHANGED
@@ -516,6 +516,9 @@ function registerHttp2(client, key, conn) {
516
516
  ...(client.options.http2HpackIndexing
517
517
  ? { hpackIndexing: client.options.http2HpackIndexing }
518
518
  : {}),
519
+ ...(client.options.http2HeadersPriority
520
+ ? { headersPriority: client.options.http2HeadersPriority }
521
+ : {}),
519
522
  onClose: () => {
520
523
  client._h2conns.delete(h2);
521
524
  // Only drop the keyed entry if it is still this connection; a newer one may have replaced it.
@@ -266,6 +266,9 @@ export class Http2Connection {
266
266
  // fields go into the HPACK dynamic table. Both default to curl's, both captured off the wire.
267
267
  this._pseudoHeaderOrder = opts.pseudoHeaderOrder ?? null;
268
268
  this._hpackIndexing = opts.hpackIndexing ?? null;
269
+ // Whether to set PRIORITY on the request HEADERS, and with what. null means do not set it,
270
+ // which is curl's behaviour and stays the default.
271
+ this._headersPriority = opts.headersPriority ?? null;
269
272
  this._expectFirstSettings = true;
270
273
 
271
274
  this._fatal = null; // set once; rejects every stream and every future request
@@ -449,14 +452,18 @@ export class Http2Connection {
449
452
  * run is one write, so no other frame can interleave it (RFC 9113 s6.10). */
450
453
  _sendHeaderBlock(streamId, block, endStream) {
451
454
  const max = this._peerMaxFrameSize;
455
+ // Priority rides the opening HEADERS and nothing else: a CONTINUATION has no flags but
456
+ // END_HEADERS (RFC 9113 s6.10), so putting it anywhere else would be malformed rather than
457
+ // merely wrong-looking.
458
+ const priority = this._headersPriority;
452
459
  if (block.length <= max) {
453
- this._write(headersFrame(streamId, block, { endStream, endHeaders: true }));
460
+ this._write(headersFrame(streamId, block, { endStream, endHeaders: true, priority }));
454
461
  return;
455
462
  }
456
463
  const frames = [];
457
464
  let o = 0;
458
465
  const first = block.subarray(0, max);
459
- frames.push(headersFrame(streamId, first, { endStream, endHeaders: false }));
466
+ frames.push(headersFrame(streamId, first, { endStream, endHeaders: false, priority }));
460
467
  o = max;
461
468
  while (o < block.length) {
462
469
  const chunk = block.subarray(o, Math.min(o + max, block.length));
@@ -152,13 +152,43 @@ export function goawayFrame(lastStreamId, errorCode, debug = EMPTY) {
152
152
  return serializeFrame(FRAME.GOAWAY, 0, 0, payload);
153
153
  }
154
154
 
155
- /** A HEADERS frame carrying a full (already-fragmented-if-needed) block. No PADDED, no PRIORITY —
156
- * a client that emits neither is exactly what curl does. */
157
- export function headersFrame(streamId, block, { endStream = false, endHeaders = true } = {}) {
155
+ /**
156
+ * A HEADERS frame carrying a full (already-fragmented-if-needed) block. Never PADDED.
157
+ *
158
+ * PRIORITY is emitted only when `priority` is supplied, because whether a client sends it is part
159
+ * of its identity: curl does not, Chromium does — flags 0x25 with `80 00 00 00 ff`, captured in
160
+ * test/tls/_captured-h2.js. RFC 9113 s5.3.2 deprecates the mechanism and this package ignores
161
+ * every PRIORITY frame it receives, but a frame-level fingerprinter reads whether the flag is set,
162
+ * so refusing to emit it would make the Chromium identity wrong in a way nothing else could fix.
163
+ *
164
+ * The five bytes are exclusive (1 bit) + stream dependency (31) + weight (8). The weight is sent
165
+ * as-is: RFC 7540 s6.3 defines the wire byte as weight-minus-one, so Chromium's 255 is a weight of
166
+ * 256, and passing the byte through keeps this function free of an off-by-one nobody would see.
167
+ *
168
+ * @param {{ exclusive?: boolean, streamDependency?: number, weight: number } | null} [opts.priority]
169
+ */
170
+ export function headersFrame(
171
+ streamId,
172
+ block,
173
+ { endStream = false, endHeaders = true, priority = null } = {},
174
+ ) {
158
175
  let flags = 0;
159
176
  if (endStream) flags |= FLAG.END_STREAM;
160
177
  if (endHeaders) flags |= FLAG.END_HEADERS;
161
- return serializeFrame(FRAME.HEADERS, flags, streamId, block);
178
+ if (!priority) return serializeFrame(FRAME.HEADERS, flags, streamId, block);
179
+
180
+ flags |= FLAG.PRIORITY;
181
+ const dep = priority.streamDependency ?? 0;
182
+ const head = new Uint8Array(5);
183
+ head[0] = ((dep >>> 24) & 0x7f) | (priority.exclusive ? 0x80 : 0);
184
+ head[1] = (dep >>> 16) & 0xff;
185
+ head[2] = (dep >>> 8) & 0xff;
186
+ head[3] = dep & 0xff;
187
+ head[4] = priority.weight & 0xff;
188
+ const payload = new Uint8Array(5 + block.length);
189
+ payload.set(head, 0);
190
+ payload.set(block, 5);
191
+ return serializeFrame(FRAME.HEADERS, flags, streamId, payload);
162
192
  }
163
193
 
164
194
  /** A CONTINUATION frame (RFC 9113 s6.10), for a header block that overflows one frame. */
package/src/profiles.js CHANGED
@@ -117,12 +117,15 @@ export const chrome = Object.freeze({
117
117
  // have been right, which is not a reason to have guessed. It is declared explicitly now so the
118
118
  // identity states its own value instead of inheriting one by accident.
119
119
  http2HpackIndexing: Object.freeze({ ':path': 'without' }),
120
- // STILL a real difference, and newly found by the same capture: Chromium sets the PRIORITY flag
121
- // on its request HEADERS frame (flags 0x25) and carries the five deprecated priority bytes.
122
- // This package never sets it (see frames.js headersFrame), so the frame layout differs from
123
- // Chromium's even where every value above it matches. Not in `requires` because it is a framing
124
- // detail this package deliberately does not emit — RFC 9113 s5.3.2 deprecates it but it is a
125
- // signal a frame-level fingerprinter reads, and it is written down here rather than discovered.
120
+ // Chromium sets PRIORITY on its request HEADERS (flags 0x25) and carries the five deprecated
121
+ // priority bytes: `80 00 00 00 ff`, i.e. exclusive, dependency 0, weight byte 255. Captured, in
122
+ // test/tls/_captured-h2.js. 1.6.2 found the difference and documented it; 1.6.5 emits it.
123
+ //
124
+ // RFC 9113 s5.3.2 deprecates the mechanism and this package still IGNORES every PRIORITY frame it
125
+ // receives — sending it is a statement about identity, not a request to be prioritised, and those
126
+ // are different things. curl sets no priority, so `profiles.curl` leaves this unset and the
127
+ // default stays "do not send".
128
+ http2HeadersPriority: Object.freeze({ exclusive: true, streamDependency: 0, weight: 255 }),
126
129
  //
127
130
  // The connection window immediately above was DEAD until 1.6.1 — declared here, copied by
128
131
  // nothing, passed by nothing, and read under a different name — so every connection using this
@@ -211,7 +214,7 @@ export function applyProfile(options) {
211
214
  // was missing, which made it dead config: the chrome profile declared Chromium's ~15 MiB window
212
215
  // and every chrome connection sent curl's 1000 MiB one.
213
216
  for (const key of ['headerOrder', 'http2Settings', 'http2ConnectionWindow',
214
- 'http2PseudoHeaderOrder', 'http2HpackIndexing']) {
217
+ 'http2PseudoHeaderOrder', 'http2HpackIndexing', 'http2HeadersPriority']) {
215
218
  if (options[key] === undefined && p[key] != null) out[key] = p[key];
216
219
  }
217
220
  // Profile headers are DEFAULTS: a request that sets its own User-Agent keeps it. They are folded
@@ -79,6 +79,7 @@ export class Http2Connection {
79
79
  _settingsFlight: Array<[number, number]> | null;
80
80
  _pseudoHeaderOrder: string[] | null;
81
81
  _hpackIndexing: Record<string, "without" | "incremental" | "never"> | null;
82
+ _headersPriority: any;
82
83
  _expectFirstSettings: boolean;
83
84
  _fatal: any;
84
85
  _goaway: {
@@ -42,11 +42,25 @@ export function rstStreamFrame(streamId: any, errorCode: any): Uint8Array<ArrayB
42
42
  export function pingFrame(opaque: any, ack?: boolean): Uint8Array<ArrayBufferLike>;
43
43
  /** A GOAWAY frame (RFC 9113 s6.8). */
44
44
  export function goawayFrame(lastStreamId: any, errorCode: any, debug?: Uint8Array<ArrayBuffer>): Uint8Array<ArrayBufferLike>;
45
- /** A HEADERS frame carrying a full (already-fragmented-if-needed) block. No PADDED, no PRIORITY —
46
- * a client that emits neither is exactly what curl does. */
47
- export function headersFrame(streamId: any, block: any, { endStream, endHeaders }?: {
45
+ /**
46
+ * A HEADERS frame carrying a full (already-fragmented-if-needed) block. Never PADDED.
47
+ *
48
+ * PRIORITY is emitted only when `priority` is supplied, because whether a client sends it is part
49
+ * of its identity: curl does not, Chromium does — flags 0x25 with `80 00 00 00 ff`, captured in
50
+ * test/tls/_captured-h2.js. RFC 9113 s5.3.2 deprecates the mechanism and this package ignores
51
+ * every PRIORITY frame it receives, but a frame-level fingerprinter reads whether the flag is set,
52
+ * so refusing to emit it would make the Chromium identity wrong in a way nothing else could fix.
53
+ *
54
+ * The five bytes are exclusive (1 bit) + stream dependency (31) + weight (8). The weight is sent
55
+ * as-is: RFC 7540 s6.3 defines the wire byte as weight-minus-one, so Chromium's 255 is a weight of
56
+ * 256, and passing the byte through keeps this function free of an off-by-one nobody would see.
57
+ *
58
+ * @param {{ exclusive?: boolean, streamDependency?: number, weight: number } | null} [opts.priority]
59
+ */
60
+ export function headersFrame(streamId: any, block: any, { endStream, endHeaders, priority }?: {
48
61
  endStream?: boolean | undefined;
49
62
  endHeaders?: boolean | undefined;
63
+ priority?: null | undefined;
50
64
  }): Uint8Array<ArrayBufferLike>;
51
65
  /** A CONTINUATION frame (RFC 9113 s6.10), for a header block that overflows one frame. */
52
66
  export function continuationFrame(streamId: any, block: any, endHeaders: any): Uint8Array<ArrayBufferLike>;
@@ -74,6 +74,11 @@ export const chrome: Readonly<{
74
74
  http2HpackIndexing: Readonly<{
75
75
  ':path': "without";
76
76
  }>;
77
+ http2HeadersPriority: Readonly<{
78
+ exclusive: true;
79
+ streamDependency: 0;
80
+ weight: 255;
81
+ }>;
77
82
  http2PseudoHeaderOrder: readonly string[];
78
83
  headerOrder: readonly string[];
79
84
  headers: readonly string[][];