Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 41 additions & 16 deletions src/js/node/http2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,29 @@ const StringPrototypeToLowerCase = String.prototype.toLowerCase;
const StringPrototypeIncludes = String.prototype.includes;
const StringPrototypeStartsWith = String.prototype.startsWith;
const ObjectPrototypeHasOwnProperty = Object.prototype.hasOwnProperty;
const ObjectCreate = Object.create;
const ObjectGetPrototypeOf = Object.getPrototypeOf;

// The copy of an outbound header block: https://github.com/nodejs/node/blob/v26.3.0/lib/internal/http2/util.js#L712
function copyHeaders(headers?: HeadersObject | null): HeadersObject {
if (headers == undefined) return ObjectCreate(null);
// A spread does not clone a null-prototype source. Object.assign onto Object.create(null) does.
if (!$isProxyObject(headers) && ObjectGetPrototypeOf(headers) === null) {
return ObjectAssign(ObjectCreate(null), headers);
}
// Spread first: JSC clones the object in one call, then changes the prototype.
return { ...headers, __proto__: null };
}

// A response reads its never-index list through the caller's object: https://github.com/nodejs/node/blob/v26.3.0/lib/internal/http2/core.js#L2622
function copyResponseHeaders(headers?: HeadersObject | null): HeadersObject {
const copy = copyHeaders(headers);
if (headers != undefined && copy[sensitiveHeaders] === undefined) {
const inherited = headers[sensitiveHeaders];
if (inherited !== undefined) copy[sensitiveHeaders] = inherited;
}
return copy;
}

const H2FrameParser = $rust("h2_frame_parser.rs", "H2FrameParserConstructor");
const { upgradeRawSocketToH2 } = require("node:_http2_upgrade");
Expand Down Expand Up @@ -2430,11 +2453,11 @@ class Http2Stream extends (Duplex as Http2StreamBase) {
}

if (headers == undefined) {
headers = {};
headers = copyHeaders();
} else if (!$isObject(headers) || $isArray(headers)) {
throw $ERR_INVALID_ARG_TYPE("headers", "object", headers);
} else {
headers = { ...headers };
headers = copyHeaders(headers);
}
const sensitives = headers[sensitiveHeaders];
if (sensitives !== undefined && !$isArray(sensitives)) {
Expand Down Expand Up @@ -2766,7 +2789,7 @@ class Http2Stream extends (Duplex as Http2StreamBase) {
// already-half-closed stream. The emit("wantTrailers") path
// below reaches the same result via sendTrailers({}) which
// assigns #sentTrailers itself.
this.#sentTrailers = {};
this.#sentTrailers = copyHeaders();
} else {
this.emit("wantTrailers");
}
Expand Down Expand Up @@ -3297,7 +3320,7 @@ class ServerHttp2Stream extends Http2Stream {
if (!parser) {
throw $ERR_HTTP2_INVALID_STREAM();
}
headers = { ...headers };
headers = copyHeaders(headers);
Comment thread
robobun marked this conversation as resolved.
Resolved
assertNoConnectionHeaders(headers);
// Thrown synchronously like node, before a promised stream id is reserved.
if (session[kStrictSingleValueFields] !== false) assertSingleValueHeaders(headers);
Expand Down Expand Up @@ -3394,11 +3417,11 @@ class ServerHttp2Stream extends Http2Stream {
throw $ERR_INVALID_ARG_TYPE("headers", ["Array", "Object"], headers);
}
if (headers == undefined) {
headers = {};
headers = copyHeaders();
} else if (!$isObject(headers)) {
throw $ERR_INVALID_ARG_TYPE("headers", "object", headers);
} else {
headers = { ...headers };
headers = copyResponseHeaders(headers);
}

if (headers[HTTP2_HEADER_STATUS] === undefined) {
Expand Down Expand Up @@ -3454,11 +3477,11 @@ class ServerHttp2Stream extends Http2Stream {
throw $ERR_INVALID_ARG_TYPE("headers", ["Array", "Object"], headers);
}
if (headers == undefined) {
headers = {};
headers = copyHeaders();
} else if (!$isObject(headers)) {
throw $ERR_INVALID_ARG_TYPE("headers", "object", headers);
} else {
headers = { ...headers };
headers = copyResponseHeaders(headers);
}

if (headers[HTTP2_HEADER_STATUS] === undefined) {
Expand Down Expand Up @@ -3513,11 +3536,11 @@ class ServerHttp2Stream extends Http2Stream {
}

if (headers == undefined) {
headers = {};
headers = copyHeaders();
} else if (!$isObject(headers) || $isArray(headers)) {
throw $ERR_INVALID_ARG_TYPE("headers", "object", headers);
} else {
headers = { ...headers };
headers = copyHeaders(headers);
}

for (const name in headers) {
Expand Down Expand Up @@ -3588,7 +3611,7 @@ class ServerHttp2Stream extends Http2Stream {
let rawHeadersList: any[] | null = null;
let statusCode;
if (headers == undefined) {
headers = {};
headers = copyHeaders();
} else if ($isArray(headers)) {
statusCode = 0;
let statusFound = false;
Expand Down Expand Up @@ -3634,7 +3657,7 @@ class ServerHttp2Stream extends Http2Stream {
} else if (!$isObject(headers)) {
throw $ERR_INVALID_ARG_TYPE("headers", "object", headers);
} else {
headers = { ...headers };
headers = copyResponseHeaders(headers);
}

const sensitives = headers[sensitiveHeaders];
Expand Down Expand Up @@ -3919,7 +3942,7 @@ function receivedValueLabel(value) {
}

function buildSensitiveNames(headers, sensitives): Record<string, boolean> {
const map: Record<string, boolean> = {};
const map: Record<string, boolean> = ObjectCreate(null);
if (sensitives) {
for (let i = 0; i < sensitives.length; i++) {
map[String(sensitives[i]).toLowerCase()] = true;
Expand Down Expand Up @@ -5991,7 +6014,9 @@ class ClientHttp2Session extends Http2Session {
if (options.silent !== undefined) validateBoolean(options.silent, "options.silent");
}
if (this.destroyed) {
const req = new ClientHttp2Stream(undefined, this, headers);
// The object form only: sentHeaders is a copy, as for a live session.
const sent = $isObject(headers) && !$isArray(headers) ? copyHeaders(headers) : headers;
const req = new ClientHttp2Stream(undefined, this, sent);
process.nextTick(destroyWithInvalidSessionNT, req);
return req;
}
Expand All @@ -6015,7 +6040,7 @@ class ClientHttp2Session extends Http2Session {
// for duplicates) backs sentHeaders.
let rawHeadersList: any[] | null = null;
if (headers == undefined) {
headers = {};
headers = copyHeaders();
} else if ($isArray(headers)) {
const raw = headers;
let method, scheme, authority, path, protocol;
Expand Down Expand Up @@ -6079,7 +6104,7 @@ class ClientHttp2Session extends Http2Session {
} else if (!$isObject(headers)) {
throw $ERR_INVALID_ARG_TYPE("headers", "object", headers);
} else {
headers = { ...headers };
headers = copyHeaders(headers);
}

// Copy options so user-supplied getters run now, before the header block
Expand Down
Loading
Loading