UNPKG

nstdlib-nightly

Version:

Node.js standard library converted to runtime-agnostic ES modules.

826 lines (769 loc) 27.3 kB
// 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 };