nstdlib-nightly
Version:
Node.js standard library converted to runtime-agnostic ES modules.
826 lines (769 loc) • 27.3 kB
JavaScript
// Source: https://github.com/nodejs/node/blob/65eff1eb/lib/internal/http2/util.js
import * as binding from "nstdlib/stub/binding/http2";
import {
codes as __codes__,
getMessage,
hideStackFrames,
kIsNodeError,
} from "nstdlib/lib/internal/errors";
const {
ERR_HTTP2_HEADER_SINGLE_VALUE,
ERR_HTTP2_INVALID_CONNECTION_HEADERS,
ERR_HTTP2_INVALID_PSEUDOHEADER: {
HideStackFramesError: ERR_HTTP2_INVALID_PSEUDOHEADER,
},
ERR_HTTP2_INVALID_SETTING_VALUE,
ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS,
ERR_INVALID_ARG_TYPE,
ERR_INVALID_HTTP_TOKEN,
} = __codes__;
const kSensitiveHeaders = Symbol("sensitiveHeaders");
const kSocket = Symbol("socket");
const kProxySocket = Symbol("proxySocket");
const kRequest = Symbol("request");
const {
NGHTTP2_NV_FLAG_NONE,
NGHTTP2_NV_FLAG_NO_INDEX,
NGHTTP2_SESSION_CLIENT,
NGHTTP2_SESSION_SERVER,
HTTP2_HEADER_STATUS,
HTTP2_HEADER_METHOD,
HTTP2_HEADER_AUTHORITY,
HTTP2_HEADER_SCHEME,
HTTP2_HEADER_PATH,
HTTP2_HEADER_PROTOCOL,
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,
HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE,
HTTP2_HEADER_ACCESS_CONTROL_REQUEST_METHOD,
HTTP2_HEADER_AGE,
HTTP2_HEADER_AUTHORIZATION,
HTTP2_HEADER_CONTENT_ENCODING,
HTTP2_HEADER_CONTENT_LANGUAGE,
HTTP2_HEADER_CONTENT_LENGTH,
HTTP2_HEADER_CONTENT_LOCATION,
HTTP2_HEADER_CONTENT_MD5,
HTTP2_HEADER_CONTENT_RANGE,
HTTP2_HEADER_CONTENT_TYPE,
HTTP2_HEADER_COOKIE,
HTTP2_HEADER_DATE,
HTTP2_HEADER_DNT,
HTTP2_HEADER_ETAG,
HTTP2_HEADER_EXPIRES,
HTTP2_HEADER_FROM,
HTTP2_HEADER_HOST,
HTTP2_HEADER_IF_MATCH,
HTTP2_HEADER_IF_NONE_MATCH,
HTTP2_HEADER_IF_MODIFIED_SINCE,
HTTP2_HEADER_IF_RANGE,
HTTP2_HEADER_IF_UNMODIFIED_SINCE,
HTTP2_HEADER_LAST_MODIFIED,
HTTP2_HEADER_LOCATION,
HTTP2_HEADER_MAX_FORWARDS,
HTTP2_HEADER_PROXY_AUTHORIZATION,
HTTP2_HEADER_RANGE,
HTTP2_HEADER_REFERER,
HTTP2_HEADER_RETRY_AFTER,
HTTP2_HEADER_SET_COOKIE,
HTTP2_HEADER_TK,
HTTP2_HEADER_UPGRADE_INSECURE_REQUESTS,
HTTP2_HEADER_USER_AGENT,
HTTP2_HEADER_X_CONTENT_TYPE_OPTIONS,
HTTP2_HEADER_CONNECTION,
HTTP2_HEADER_UPGRADE,
HTTP2_HEADER_HTTP2_SETTINGS,
HTTP2_HEADER_TE,
HTTP2_HEADER_TRANSFER_ENCODING,
HTTP2_HEADER_KEEP_ALIVE,
HTTP2_HEADER_PROXY_CONNECTION,
HTTP2_METHOD_DELETE,
HTTP2_METHOD_GET,
HTTP2_METHOD_HEAD,
} = binding.constants;
// This set is defined strictly by the HTTP/2 specification. Only
// :-prefixed headers defined by that specification may be added to
// this set.
const kValidPseudoHeaders = new Set([
HTTP2_HEADER_STATUS,
HTTP2_HEADER_METHOD,
HTTP2_HEADER_AUTHORITY,
HTTP2_HEADER_SCHEME,
HTTP2_HEADER_PATH,
HTTP2_HEADER_PROTOCOL,
]);
// This set contains headers that are permitted to have only a single
// value. Multiple instances must not be specified.
const kSingleValueHeaders = new Set([
HTTP2_HEADER_STATUS,
HTTP2_HEADER_METHOD,
HTTP2_HEADER_AUTHORITY,
HTTP2_HEADER_SCHEME,
HTTP2_HEADER_PATH,
HTTP2_HEADER_PROTOCOL,
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS,
HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE,
HTTP2_HEADER_ACCESS_CONTROL_REQUEST_METHOD,
HTTP2_HEADER_AGE,
HTTP2_HEADER_AUTHORIZATION,
HTTP2_HEADER_CONTENT_ENCODING,
HTTP2_HEADER_CONTENT_LANGUAGE,
HTTP2_HEADER_CONTENT_LENGTH,
HTTP2_HEADER_CONTENT_LOCATION,
HTTP2_HEADER_CONTENT_MD5,
HTTP2_HEADER_CONTENT_RANGE,
HTTP2_HEADER_CONTENT_TYPE,
HTTP2_HEADER_DATE,
HTTP2_HEADER_DNT,
HTTP2_HEADER_ETAG,
HTTP2_HEADER_EXPIRES,
HTTP2_HEADER_FROM,
HTTP2_HEADER_HOST,
HTTP2_HEADER_IF_MATCH,
HTTP2_HEADER_IF_MODIFIED_SINCE,
HTTP2_HEADER_IF_NONE_MATCH,
HTTP2_HEADER_IF_RANGE,
HTTP2_HEADER_IF_UNMODIFIED_SINCE,
HTTP2_HEADER_LAST_MODIFIED,
HTTP2_HEADER_LOCATION,
HTTP2_HEADER_MAX_FORWARDS,
HTTP2_HEADER_PROXY_AUTHORIZATION,
HTTP2_HEADER_RANGE,
HTTP2_HEADER_REFERER,
HTTP2_HEADER_RETRY_AFTER,
HTTP2_HEADER_TK,
HTTP2_HEADER_UPGRADE_INSECURE_REQUESTS,
HTTP2_HEADER_USER_AGENT,
HTTP2_HEADER_X_CONTENT_TYPE_OPTIONS,
]);
// The HTTP methods in this set are specifically defined as assigning no
// meaning to the request payload. By default, unless the user explicitly
// overrides the endStream option on the request method, the endStream
// option will be defaulted to true when these methods are used.
const kNoPayloadMethods = new Set([
HTTP2_METHOD_DELETE,
HTTP2_METHOD_GET,
HTTP2_METHOD_HEAD,
]);
// The following ArrayBuffer instances are used to share memory more efficiently
// with the native binding side for a number of methods. These are not intended
// to be used directly by users in any way. The ArrayBuffers are created on
// the native side with values that are filled in on demand, the js code then
// reads those values out. The set of IDX constants that follow identify the
// relevant data positions within these buffers.
const { settingsBuffer, optionsBuffer } = binding;
// Note that Float64Array is used here because there is no Int64Array available
// and these deal with numbers that can be beyond the range of Uint32 and Int32.
// The values set on the native side will always be integers. This is not a
// unique example of this, this pattern can be found in use in other parts of
// Node.js core as a performance optimization.
const { sessionState, streamState } = binding;
const IDX_SETTINGS_HEADER_TABLE_SIZE = 0;
const IDX_SETTINGS_ENABLE_PUSH = 1;
const IDX_SETTINGS_INITIAL_WINDOW_SIZE = 2;
const IDX_SETTINGS_MAX_FRAME_SIZE = 3;
const IDX_SETTINGS_MAX_CONCURRENT_STREAMS = 4;
const IDX_SETTINGS_MAX_HEADER_LIST_SIZE = 5;
const IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL = 6;
const IDX_SETTINGS_FLAGS = 7;
// Maximum number of allowed additional settings
const MAX_ADDITIONAL_SETTINGS = 10;
const IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE = 0;
const IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH = 1;
const IDX_SESSION_STATE_NEXT_STREAM_ID = 2;
const IDX_SESSION_STATE_LOCAL_WINDOW_SIZE = 3;
const IDX_SESSION_STATE_LAST_PROC_STREAM_ID = 4;
const IDX_SESSION_STATE_REMOTE_WINDOW_SIZE = 5;
const IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE = 6;
const IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE = 7;
const IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE = 8;
const IDX_STREAM_STATE = 0;
const IDX_STREAM_STATE_WEIGHT = 1;
const IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT = 2;
const IDX_STREAM_STATE_LOCAL_CLOSE = 3;
const IDX_STREAM_STATE_REMOTE_CLOSE = 4;
const IDX_STREAM_STATE_LOCAL_WINDOW_SIZE = 5;
const IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE = 0;
const IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS = 1;
const IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH = 2;
const IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS = 3;
const IDX_OPTIONS_PADDING_STRATEGY = 4;
const IDX_OPTIONS_MAX_HEADER_LIST_PAIRS = 5;
const IDX_OPTIONS_MAX_OUTSTANDING_PINGS = 6;
const IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS = 7;
const IDX_OPTIONS_MAX_SESSION_MEMORY = 8;
const IDX_OPTIONS_MAX_SETTINGS = 9;
const IDX_OPTIONS_FLAGS = 10;
function updateOptionsBuffer(options) {
let flags = 0;
if (typeof options.maxDeflateDynamicTableSize === "number") {
flags |= 1 << IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE;
optionsBuffer[IDX_OPTIONS_MAX_DEFLATE_DYNAMIC_TABLE_SIZE] =
options.maxDeflateDynamicTableSize;
}
if (typeof options.maxReservedRemoteStreams === "number") {
flags |= 1 << IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS;
optionsBuffer[IDX_OPTIONS_MAX_RESERVED_REMOTE_STREAMS] =
options.maxReservedRemoteStreams;
}
if (typeof options.maxSendHeaderBlockLength === "number") {
flags |= 1 << IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH;
optionsBuffer[IDX_OPTIONS_MAX_SEND_HEADER_BLOCK_LENGTH] =
options.maxSendHeaderBlockLength;
}
if (typeof options.peerMaxConcurrentStreams === "number") {
flags |= 1 << IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS;
optionsBuffer[IDX_OPTIONS_PEER_MAX_CONCURRENT_STREAMS] =
options.peerMaxConcurrentStreams;
}
if (typeof options.paddingStrategy === "number") {
flags |= 1 << IDX_OPTIONS_PADDING_STRATEGY;
optionsBuffer[IDX_OPTIONS_PADDING_STRATEGY] = options.paddingStrategy;
}
if (typeof options.maxHeaderListPairs === "number") {
flags |= 1 << IDX_OPTIONS_MAX_HEADER_LIST_PAIRS;
optionsBuffer[IDX_OPTIONS_MAX_HEADER_LIST_PAIRS] =
options.maxHeaderListPairs;
}
if (typeof options.maxOutstandingPings === "number") {
flags |= 1 << IDX_OPTIONS_MAX_OUTSTANDING_PINGS;
optionsBuffer[IDX_OPTIONS_MAX_OUTSTANDING_PINGS] =
options.maxOutstandingPings;
}
if (typeof options.maxOutstandingSettings === "number") {
flags |= 1 << IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS;
optionsBuffer[IDX_OPTIONS_MAX_OUTSTANDING_SETTINGS] = Math.max(
1,
options.maxOutstandingSettings,
);
}
if (typeof options.maxSessionMemory === "number") {
flags |= 1 << IDX_OPTIONS_MAX_SESSION_MEMORY;
optionsBuffer[IDX_OPTIONS_MAX_SESSION_MEMORY] = Math.max(
1,
options.maxSessionMemory,
);
}
if (typeof options.maxSettings === "number") {
flags |= 1 << IDX_OPTIONS_MAX_SETTINGS;
optionsBuffer[IDX_OPTIONS_MAX_SETTINGS] = Math.max(1, options.maxSettings);
}
optionsBuffer[IDX_OPTIONS_FLAGS] = flags;
}
function addCustomSettingsToObj() {
const toRet = {};
const num = settingsBuffer[IDX_SETTINGS_FLAGS + 1];
for (let i = 0; i < num; i++) {
toRet[settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * i + 1].toString()] =
Number(settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * i + 2]);
}
return toRet;
}
function getDefaultSettings() {
settingsBuffer[IDX_SETTINGS_FLAGS] = 0;
settingsBuffer[IDX_SETTINGS_FLAGS + 1] = 0; // Length of custom settings
binding.refreshDefaultSettings();
const holder = { __proto__: null };
const flags = settingsBuffer[IDX_SETTINGS_FLAGS];
if (
(flags & (1 << IDX_SETTINGS_HEADER_TABLE_SIZE)) ===
1 << IDX_SETTINGS_HEADER_TABLE_SIZE
) {
holder.headerTableSize = settingsBuffer[IDX_SETTINGS_HEADER_TABLE_SIZE];
}
if (
(flags & (1 << IDX_SETTINGS_ENABLE_PUSH)) ===
1 << IDX_SETTINGS_ENABLE_PUSH
) {
holder.enablePush = settingsBuffer[IDX_SETTINGS_ENABLE_PUSH] === 1;
}
if (
(flags & (1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE)) ===
1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE
) {
holder.initialWindowSize = settingsBuffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE];
}
if (
(flags & (1 << IDX_SETTINGS_MAX_FRAME_SIZE)) ===
1 << IDX_SETTINGS_MAX_FRAME_SIZE
) {
holder.maxFrameSize = settingsBuffer[IDX_SETTINGS_MAX_FRAME_SIZE];
}
if (
(flags & (1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS)) ===
1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS
) {
holder.maxConcurrentStreams =
settingsBuffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS];
}
if (
(flags & (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE)) ===
1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE
) {
holder.maxHeaderListSize = holder.maxHeaderSize =
settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE];
}
if (
(flags & (1 << IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL)) ===
1 << IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL
) {
holder.enableConnectProtocol =
settingsBuffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL] === 1;
}
if (settingsBuffer[IDX_SETTINGS_FLAGS + 1])
holder.customSettings = addCustomSettingsToObj();
return holder;
}
// Remote is a boolean. true to fetch remote settings, false to fetch local.
// this is only called internally
function getSettings(session, remote) {
if (remote) session.remoteSettings();
else session.localSettings();
const toRet = {
headerTableSize: settingsBuffer[IDX_SETTINGS_HEADER_TABLE_SIZE],
enablePush: !!settingsBuffer[IDX_SETTINGS_ENABLE_PUSH],
initialWindowSize: settingsBuffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE],
maxFrameSize: settingsBuffer[IDX_SETTINGS_MAX_FRAME_SIZE],
maxConcurrentStreams: settingsBuffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS],
maxHeaderListSize: settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE],
maxHeaderSize: settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE],
enableConnectProtocol:
!!settingsBuffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL],
};
if (settingsBuffer[IDX_SETTINGS_FLAGS + 1])
toRet.customSettings = addCustomSettingsToObj();
return toRet;
}
function updateSettingsBuffer(settings) {
let flags = 0;
let numCustomSettings = 0;
if (typeof settings.customSettings === "object") {
const customSettings = settings.customSettings;
for (const setting in customSettings) {
const val = customSettings[setting];
if (typeof val === "number") {
let set = false;
const nsetting = Number(setting);
if (
Number.isNaN(nsetting) ||
typeof nsetting !== "number" ||
0 >= nsetting ||
nsetting > 0xffff
)
throw new ERR_HTTP2_INVALID_SETTING_VALUE.RangeError(
"Range Error",
nsetting,
0,
0xffff,
);
if (
Number.isNaN(val) ||
typeof val !== "number" ||
0 >= val ||
val > 0xffffffff
)
throw new ERR_HTTP2_INVALID_SETTING_VALUE.RangeError(
"Range Error",
val,
0,
0xffffffff,
);
if (nsetting < IDX_SETTINGS_FLAGS) {
set = true;
switch (nsetting) {
case IDX_SETTINGS_HEADER_TABLE_SIZE:
flags |= 1 << IDX_SETTINGS_HEADER_TABLE_SIZE;
settingsBuffer[IDX_SETTINGS_HEADER_TABLE_SIZE] = val;
break;
case IDX_SETTINGS_ENABLE_PUSH:
flags |= 1 << IDX_SETTINGS_ENABLE_PUSH;
settingsBuffer[IDX_SETTINGS_ENABLE_PUSH] = val;
break;
case IDX_SETTINGS_INITIAL_WINDOW_SIZE:
flags |= 1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE;
settingsBuffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] = val;
break;
case IDX_SETTINGS_MAX_FRAME_SIZE:
flags |= 1 << IDX_SETTINGS_MAX_FRAME_SIZE;
settingsBuffer[IDX_SETTINGS_MAX_FRAME_SIZE] = val;
break;
case IDX_SETTINGS_MAX_CONCURRENT_STREAMS:
flags |= 1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS;
settingsBuffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS] = val;
break;
case IDX_SETTINGS_MAX_HEADER_LIST_SIZE:
flags |= 1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE;
settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE] = val;
break;
case IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL:
flags |= 1 << IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL;
settingsBuffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL] = val;
break;
default:
set = false;
break;
}
}
if (!set) {
// not supported
let i = 0;
while (i < numCustomSettings) {
if (
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * i + 1] === nsetting
) {
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * i + 2] = val;
break;
}
i++;
}
if (i === numCustomSettings) {
if (numCustomSettings === MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 1] =
nsetting;
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 2] =
val;
numCustomSettings++;
}
}
}
}
}
settingsBuffer[IDX_SETTINGS_FLAGS + 1] = numCustomSettings;
if (typeof settings.headerTableSize === "number") {
flags |= 1 << IDX_SETTINGS_HEADER_TABLE_SIZE;
settingsBuffer[IDX_SETTINGS_HEADER_TABLE_SIZE] = settings.headerTableSize;
}
if (typeof settings.maxConcurrentStreams === "number") {
flags |= 1 << IDX_SETTINGS_MAX_CONCURRENT_STREAMS;
settingsBuffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS] =
settings.maxConcurrentStreams;
}
if (typeof settings.initialWindowSize === "number") {
flags |= 1 << IDX_SETTINGS_INITIAL_WINDOW_SIZE;
settingsBuffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE] =
settings.initialWindowSize;
}
if (typeof settings.maxFrameSize === "number") {
flags |= 1 << IDX_SETTINGS_MAX_FRAME_SIZE;
settingsBuffer[IDX_SETTINGS_MAX_FRAME_SIZE] = settings.maxFrameSize;
}
if (
typeof settings.maxHeaderListSize === "number" ||
typeof settings.maxHeaderSize === "number"
) {
flags |= 1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE;
if (
settings.maxHeaderSize !== undefined &&
settings.maxHeaderSize !== settings.maxHeaderListSize
) {
process.emitWarning(
"settings.maxHeaderSize overwrite settings.maxHeaderListSize",
);
settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE] =
settings.maxHeaderSize;
} else {
settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE] =
settings.maxHeaderListSize;
}
}
if (typeof settings.enablePush === "boolean") {
flags |= 1 << IDX_SETTINGS_ENABLE_PUSH;
settingsBuffer[IDX_SETTINGS_ENABLE_PUSH] = Number(settings.enablePush);
}
if (typeof settings.enableConnectProtocol === "boolean") {
flags |= 1 << IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL;
settingsBuffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL] = Number(
settings.enableConnectProtocol,
);
}
settingsBuffer[IDX_SETTINGS_FLAGS] = flags;
}
function remoteCustomSettingsToBuffer(remoteCustomSettings) {
if (remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS)
throw new ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS();
let numCustomSettings = 0;
for (let i = 0; i < remoteCustomSettings.length; i++) {
const nsetting = remoteCustomSettings[i];
if (typeof nsetting === "number" && nsetting <= 0xffff && nsetting >= 0) {
settingsBuffer[IDX_SETTINGS_FLAGS + 1 + 2 * numCustomSettings + 1] =
nsetting;
numCustomSettings++;
} else
throw new ERR_HTTP2_INVALID_SETTING_VALUE.RangeError(
"Range Error",
nsetting,
0,
0xffff,
);
}
settingsBuffer[IDX_SETTINGS_FLAGS + 1] = numCustomSettings;
}
function getSessionState(session) {
session.refreshState();
return {
effectiveLocalWindowSize:
sessionState[IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE],
effectiveRecvDataLength:
sessionState[IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH],
nextStreamID: sessionState[IDX_SESSION_STATE_NEXT_STREAM_ID],
localWindowSize: sessionState[IDX_SESSION_STATE_LOCAL_WINDOW_SIZE],
lastProcStreamID: sessionState[IDX_SESSION_STATE_LAST_PROC_STREAM_ID],
remoteWindowSize: sessionState[IDX_SESSION_STATE_REMOTE_WINDOW_SIZE],
outboundQueueSize: sessionState[IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE],
deflateDynamicTableSize:
sessionState[IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE],
inflateDynamicTableSize:
sessionState[IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE],
};
}
function getStreamState(stream) {
stream.refreshState();
return {
state: streamState[IDX_STREAM_STATE],
weight: streamState[IDX_STREAM_STATE_WEIGHT],
sumDependencyWeight: streamState[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT],
localClose: streamState[IDX_STREAM_STATE_LOCAL_CLOSE],
remoteClose: streamState[IDX_STREAM_STATE_REMOTE_CLOSE],
localWindowSize: streamState[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE],
};
}
function isIllegalConnectionSpecificHeader(name, value) {
switch (name) {
case HTTP2_HEADER_CONNECTION:
case HTTP2_HEADER_UPGRADE:
case HTTP2_HEADER_HTTP2_SETTINGS:
case HTTP2_HEADER_KEEP_ALIVE:
case HTTP2_HEADER_PROXY_CONNECTION:
case HTTP2_HEADER_TRANSFER_ENCODING:
return true;
case HTTP2_HEADER_TE:
return value !== "trailers";
default:
return false;
}
}
const assertValidPseudoHeader = hideStackFrames((key) => {
if (!kValidPseudoHeaders.has(key)) {
throw new ERR_HTTP2_INVALID_PSEUDOHEADER(key);
}
});
const assertValidPseudoHeaderResponse = hideStackFrames((key) => {
if (key !== ":status") {
throw new ERR_HTTP2_INVALID_PSEUDOHEADER(key);
}
});
const assertValidPseudoHeaderTrailer = hideStackFrames((key) => {
throw new ERR_HTTP2_INVALID_PSEUDOHEADER(key);
});
const emptyArray = [];
const kNeverIndexFlag = String.fromCharCode(NGHTTP2_NV_FLAG_NO_INDEX);
const kNoHeaderFlags = String.fromCharCode(NGHTTP2_NV_FLAG_NONE);
function mapToHeaders(map, assertValuePseudoHeader = assertValidPseudoHeader) {
let headers = "";
let pseudoHeaders = "";
let count = 0;
const keys = Object.keys(map);
const singles = new Set();
let i, j;
let isArray;
let key;
let value;
let isSingleValueHeader;
let err;
const neverIndex = (map[kSensitiveHeaders] || emptyArray).map((v) =>
v.toLowerCase(),
);
for (i = 0; i < keys.length; ++i) {
key = keys[i];
value = map[key];
if (value === undefined || key === "") continue;
key = key.toLowerCase();
isSingleValueHeader = kSingleValueHeaders.has(key);
isArray = Array.isArray(value);
if (isArray) {
switch (value.length) {
case 0:
continue;
case 1:
value = String(value[0]);
isArray = false;
break;
default:
if (isSingleValueHeader) throw new ERR_HTTP2_HEADER_SINGLE_VALUE(key);
}
} else {
value = String(value);
}
if (isSingleValueHeader) {
if (singles.has(key)) throw new ERR_HTTP2_HEADER_SINGLE_VALUE(key);
singles.add(key);
}
const flags = neverIndex.includes(key) ? kNeverIndexFlag : kNoHeaderFlags;
if (key[0] === ":") {
err = assertValuePseudoHeader(key);
if (err !== undefined) throw err;
pseudoHeaders += `${key}\0${value}\0${flags}`;
count++;
continue;
}
if (key.includes(" ")) {
throw new ERR_INVALID_HTTP_TOKEN("Header name", key);
}
if (isIllegalConnectionSpecificHeader(key, value)) {
throw new ERR_HTTP2_INVALID_CONNECTION_HEADERS(key);
}
if (isArray) {
for (j = 0; j < value.length; ++j) {
const val = String(value[j]);
headers += `${key}\0${val}\0${flags}`;
}
count += value.length;
continue;
}
headers += `${key}\0${value}\0${flags}`;
count++;
}
return [pseudoHeaders + headers, count];
}
class NghttpError extends Error {
constructor(integerCode, customErrorCode) {
super(
customErrorCode
? getMessage(customErrorCode, [], null)
: binding.nghttp2ErrorString(integerCode),
);
this.code = customErrorCode || "ERR_HTTP2_ERROR";
this.errno = integerCode;
}
get [kIsNodeError]() {
return true;
}
toString() {
return `${this.name} [${this.code}]: ${this.message}`;
}
}
const assertIsObject = hideStackFrames((value, name, types) => {
if (
value !== undefined &&
(value === null || typeof value !== "object" || Array.isArray(value))
) {
throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(
name,
types || "Object",
value,
);
}
});
const assertIsArray = hideStackFrames((value, name, types) => {
if (value !== undefined && (value === null || !Array.isArray(value))) {
throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(
name,
types || "Array",
value,
);
}
});
const assertWithinRange = hideStackFrames(
(name, value, min = 0, max = Infinity) => {
if (
value !== undefined &&
(typeof value !== "number" || value < min || value > max)
) {
throw new ERR_HTTP2_INVALID_SETTING_VALUE.RangeError.HideStackFramesError(
name,
value,
min,
max,
);
}
},
);
function toHeaderObject(headers, sensitiveHeaders) {
const obj = { __proto__: null };
for (let n = 0; n < headers.length; n += 2) {
const name = headers[n];
let value = headers[n + 1];
if (name === HTTP2_HEADER_STATUS) value |= 0;
const existing = obj[name];
if (existing === undefined) {
obj[name] = name === HTTP2_HEADER_SET_COOKIE ? [value] : value;
} else if (!kSingleValueHeaders.has(name)) {
switch (name) {
case HTTP2_HEADER_COOKIE:
// https://tools.ietf.org/html/rfc7540#section-8.1.2.5
// "...If there are multiple Cookie header fields after decompression,
// these MUST be concatenated into a single octet string using the
// two-octet delimiter of 0x3B, 0x20 (the ASCII string "; ") before
// being passed into a non-HTTP/2 context."
obj[name] = `${existing}; ${value}`;
break;
case HTTP2_HEADER_SET_COOKIE:
// https://tools.ietf.org/html/rfc7230#section-3.2.2
// "Note: In practice, the "Set-Cookie" header field ([RFC6265]) often
// appears multiple times in a response message and does not use the
// list syntax, violating the above requirements on multiple header
// fields with the same name. Since it cannot be combined into a
// single field-value, recipients ought to handle "Set-Cookie" as a
// special case while processing header fields."
existing.push(value);
break;
default:
// https://tools.ietf.org/html/rfc7230#section-3.2.2
// "A recipient MAY combine multiple header fields with the same field
// name into one "field-name: field-value" pair, without changing the
// semantics of the message, by appending each subsequent field value
// to the combined field value in order, separated by a comma."
obj[name] = `${existing}, ${value}`;
break;
}
}
}
obj[kSensitiveHeaders] = sensitiveHeaders;
return obj;
}
function isPayloadMeaningless(method) {
return kNoPayloadMethods.has(method);
}
function sessionName(type) {
switch (type) {
case NGHTTP2_SESSION_CLIENT:
return "client";
case NGHTTP2_SESSION_SERVER:
return "server";
default:
return "<invalid>";
}
}
function getAuthority(headers) {
// For non-CONNECT requests, HTTP/2 allows either :authority
// or Host to be used equivalently. The first is preferred
// when making HTTP/2 requests, and the latter is preferred
// when converting from an HTTP/1 message.
if (headers[HTTP2_HEADER_AUTHORITY] !== undefined)
return headers[HTTP2_HEADER_AUTHORITY];
if (headers[HTTP2_HEADER_HOST] !== undefined)
return headers[HTTP2_HEADER_HOST];
}
export { assertIsObject };
export { assertIsArray };
export { assertValidPseudoHeader };
export { assertValidPseudoHeaderResponse };
export { assertValidPseudoHeaderTrailer };
export { assertWithinRange };
export { getAuthority };
export { getDefaultSettings };
export { getSessionState };
export { getSettings };
export { getStreamState };
export { isPayloadMeaningless };
export { kSensitiveHeaders };
export { kSocket };
export { kProxySocket };
export { kRequest };
export { mapToHeaders };
export { MAX_ADDITIONAL_SETTINGS };
export { NghttpError };
export { remoteCustomSettingsToBuffer };
export { sessionName };
export { toHeaderObject };
export { updateOptionsBuffer };
export { updateSettingsBuffer };