et green = ''; let red = ''; let white = ''; const kReadableOperator = { deepStrictEqual: 'Expected values to be strictly deep-equal:', strictEqual: 'Expected values to be strictly equal:', strictEqualObject: 'Expected "actual" to be reference-equal to "expected":', deepEqual: 'Expected values to be loosely deep-equal:', notDeepStrictEqual: 'Expected "actual" not to be strictly deep-equal to:', notStrictEqual: 'Expected "actual" to be strictly unequal to:', notStrictEqualObject: 'Expected "actual" not to be reference-equal to "expected":', notDeepEqual: 'Expected "actual" not to be loosely deep-equal to:', notIdentical: 'Values have same structure but are not reference-equal:', notDeepEqualUnequal: 'Expected values not to be loosely deep-equal:' }; // Comparing short primitives should just show === / !== instead of using the // diff. const kMaxShortLength = 12; function copyError(source) { const keys = ObjectKeys(source); const target = ObjectCreate(ObjectGetPrototypeOf(source)); for (const key of keys) { target[key] = source[key]; } ObjectDefineProperty(target, 'message', { value: source.message }); return target; } function inspectValue(val) { // The util.inspect default values could be changed. This makes sure the // error messages contain the necessary information nevertheless. return inspect( val, { compact: false, customInspect: false, depth: 1000, maxArrayLength: Infinity, // Assert compares only enumerable properties (with a few exceptions). showHidden: false, // Assert does not detect proxies currently. showProxy: false, sorted: true, // Inspect getters as we also check them when comparing entries. getters: true, } ); } function createErrDiff(actual, expected, operator) { let other = ''; let res = ''; let end = ''; let skipped = false; const actualInspected = inspectValue(actual); const actualLines = StringPrototypeSplit(actualInspected, '\n'); const expectedLines = StringPrototypeSplit(inspectValue(expected), '\n'); let i = 0; let indicator = ''; // In case both values are objects or functions explicitly mark them as not // reference equal for the `strictEqual` operator. if (operator === 'strictEqual' && ((typeof actual === 'object' && actual !== null && typeof expected === 'object' && expected !== null) || (typeof actual === 'function' && typeof expected === 'function'))) { operator = 'strictEqualObject'; } // If "actual" and "expected" fit on a single line and they are not strictly // equal, check further special handling. if (actualLines.length === 1 && expectedLines.length === 1 && actualLines[0] !== expectedLines[0]) { // Check for the visible length using the `removeColors()` function, if // appropriate. const c = inspect.defaultOptions.colors; const actualRaw = c ? removeColors(actualLines[0]) : actualLines[0]; const expectedRaw = c ? removeColors(expectedLines[0]) : expectedLines[0]; const inputLength = actualRaw.length + expectedRaw.length; // If the character length of "actual" and "expected" together is less than // kMaxShortLength and if neither is an object and at least one of them is // not `zero`, use the strict equal comparison to visualize the output. if (inputLength <= kMaxShortLength) { if ((typeof actual !== 'object' || actual === null) && (typeof expected !== 'object' || expected === null) && (actual !== 0 || expected !== 0)) { // -0 === +0 return `${kReadableOperator[operator]}\n\n` + `${actualLines[0]} !== ${expectedLines[0]}\n`; } } else if (operator !== 'strictEqualObject') { // If the stderr is a tty and the input length is lower than the current // columns per line, add a mismatch indicator below the output. If it is // not a tty, use a default value of 80 characters. const maxLength = process.stderr.isTTY ? process.stderr.columns : 80; if (inputLength < maxLength) { while (actualRaw[i] === expectedRaw[i]) { i++; } // Ignore the first characters. if (i > 2) { // Add position indicator for the first mismatch in case it is a // single line and the input length is less than the column length. indicator = `\n ${StringPrototypeRepeat(' ', i)}^`; i = 0; } } } } // Remove all ending lines that match (this optimizes the output for // readability by reducing the number of total changed lines). let a = actualLines[actualLines.length - 1]; let b = expectedLines[expectedLines.length - 1]; while (a === b) { if (i++ < 3) { end = `\n ${a}${end}`; } else { other = a; } ArrayPrototypePop(actualLines); ArrayPrototypePop(expectedLines); if (actualLines.length === 0 || expectedLines.length === 0) break; a = actualLines[actualLines.length - 1]; b = expectedLines[expectedLines.length - 1]; } const maxLines = MathMax(actualLines.length, expectedLines.length); // Strict equal with identical objects that are not identical by reference. // E.g., assert.deepStrictEqual({ a: Symbol() }, { a: Symbol() }) if (maxLines === 0) { // We have to get the result again. The lines were all removed before. const actualLines = StringPrototypeSplit(actualInspected, '\n'); // Only remove lines in case it makes sense to collapse those. // TODO: Accept env to always show the full error. if (actualLines.length > 50) { actualLines[46] = `${blue}...${white}`; while (actualLines.length > 47) { ArrayPrototypePop(actualLines); } } return `${kReadableOperator.notIdentical}\n\n` + `${ArrayPrototypeJoin(actualLines, '\n')}\n`; } // There were at least five identical lines at the end. Mark a couple of // skipped. if (i >= 5) { end = `\n${blue}...${white}${end}`; skipped = true; } if (other !== '') { end = `\n ${other}${end}`; other = ''; } let printedLines = 0; let identical = 0; const msg = kReadableOperator[operator] + `\n${green}+ actual${white} ${red}- expected${white}`; const skippedMsg = ` ${blue}...${white} Lines skipped`; let lines = actualLines; let plusMinus = `${green}+${white}`; let maxLength = expectedLines.length; if (actualLines.length < maxLines) { lines = expectedLines; plusMinus = `${red}-${white}`; maxLength = actualLines.length; } for (i = 0; i < maxLines; i++) { if (maxLength < i + 1) { // If more than two former lines are identical, print them. Collapse them // in case more than five lines were identical. if (identical > 2) { if (identical > 3) { if (identical > 4) { if (identical === 5) { res += `\n ${lines[i - 3]}`; printedLines++; } else { res += `\n${blue}...${white}`; skipped = true; } } res += `\n ${lines[i - 2]}`; printedLines++; } res += `\n ${lines[i - 1]}`; printedLines++; } // No identical lines before. identical = 0; // Add the expected line to the cache. if (lines === actualLines) { res += `\n${plusMinus} ${lines[i]}`; } else { other += `\n${plusMinus} ${lines[i]}`; } printedLines++; // Only extra actual lines exist // Lines diverge } else { const expectedLine = expectedLines[i]; let actualLine = actualLines[i]; // If the lines diverge, specifically check for lines that only diverge by // a trailing comma. In that case it is actually identical and we should // mark it as such. let divergingLines = actualLine !== expectedLine && (!StringPrototypeEndsWith(actualLine, ',') || StringPrototypeSlice(actualLine, 0, -1) !== expectedLine); // If the expected line has a trailing comma but is otherwise identical, // add a comma at the end of the actual line. Otherwise the output could // look weird as in: // // [ // 1 // No comma at the end! // + 2 // ] // if (divergingLines && StringPrototypeEndsWith(expectedLine, ',') && StringPrototypeSlice(expectedLine, 0, -1) === actualLine) { divergingLines = false; actualLine += ','; } if (divergingLines) { // If more than two former lines are identical, print them. Collapse // them in case more than five lines were identical. if (identical > 2) { if (identical > 3) { if (identical > 4) { if (identical === 5) { res += `\n ${actualLines[i - 3]}`; printedLines++; } else { res += `\n${blue}...${white}`; skipped = true; } } res += `\n ${actualLines[i - 2]}`; printedLines++; } res += `\n ${actualLines[i - 1]}`; printedLines++; } // No identical lines before. identical = 0; // Add the actual line to the result and cache the expected diverging // line so consecutive diverging lines show up as +++--- and not +-+-+-. res += `\n${green}+${white} ${actualLine}`; other += `\n${red}-${white} ${expectedLine}`; printedLines += 2; // Lines are identical } else { // Add all cached information to the result before adding other things // and reset the cache. res += other; other = ''; identical++; // The very first identical line since the last diverging line is be // added to the result. if (identical <= 2) { res += `\n ${actualLine}`; printedLines++; } } } // Inspected object to big (Show ~50 rows max) if (printedLines > 50 && i < maxLines - 2) { return `${msg}${skippedMsg}\n${res}\n${blue}...${white}${other}\n` + `${blue}...${white}`; } } return `${msg}${skipped ? skippedMsg : ''}\n${res}${other}${end}${indicator}`; } function addEllipsis(string) { const lines = StringPrototypeSplit(string, '\n', 11); if (lines.length > 10) { lines.length = 10; return `${ArrayPrototypeJoin(lines, '\n')}\n...`; } else if (string.length > 512) { return `${StringPrototypeSlice(string, 512)}...`; } return string; } class AssertionError extends Error { constructor(options) { validateObject(options, 'options'); const { message, operator, stackStartFn, details, // Compatibility with older versions. stackStartFunction } = options; let { actual, expected } = options; const limit = Error.stackTraceLimit; if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = 0; if (message != null) { super(String(message)); } else { if (process.stderr.isTTY) { // Reset on each call to make sure we handle dynamically set environment // variables correct. if (process.stderr.hasColors()) { blue = '\u001b[34m'; green = '\u001b[32m'; white = '\u001b[39m'; red = '\u001b[31m'; } else { blue = ''; green = ''; white = ''; red = ''; } } // Prevent the error stack from being visible by duplicating the error // in a very close way to the original in case both sides are actually // instances of Error. if (typeof actual === 'object' && actual !== null && typeof expected === 'object' && expected !== null && 'stack' in actual && actual instanceof Error && 'stack' in expected && expected instanceof Error) { actual = copyError(actual); expected = copyError(expected); } if (operator === 'deepStrictEqual' || operator === 'strictEqual') { super(createErrDiff(actual, expected, operator)); } else if (operator === 'notDeepStrictEqual' || operator === 'notStrictEqual') { // In case the objects are equal but the operator requires unequal, show // the first object and say A equals B let base = kReadableOperator[operator]; const res = StringPrototypeSplit(inspectValue(actual), '\n'); // In case "actual" is an object or a function, it should not be // reference equal. if (operator === 'notStrictEqual' && ((typeof actual === 'object' && actual !== null) || typeof actual === 'function')) { base = kReadableOperator.notStrictEqualObject; } // Only remove lines in case it makes sense to collapse those. // TODO: Accept env to always show the full error. if (res.length > 50) { res[46] = `${blue}...${white}`; while (res.length > 47) { ArrayPrototypePop(res); } } // Only print a single input. if (res.length === 1) { super(`${base}${res[0].length > 5 ? '\n\n' : ' '}${res[0]}`); } else { super(`${base}\n\n${ArrayPrototypeJoin(res, '\n')}\n`); } } else { let res = inspectValue(actual); let other = inspectValue(expected); const knownOperator = kReadableOperator[operator]; if (operator === 'notDeepEqual' && res === other) { res = `${knownOperator}\n\n${res}`; if (res.length > 1024) { res = `${StringPrototypeSlice(res, 0, 1021)}...`; } super(res); } else { if (res.length > 512) { res = `${StringPrototypeSlice(res, 0, 509)}...`; } if (other.length > 512) { other = `${StringPrototypeSlice(other, 0, 509)}...`; } if (operator === 'deepEqual') { res = `${knownOperator}\n\n${res}\n\nshould loosely deep-equal\n\n`; } else { const newOp = kReadableOperator[`${operator}Unequal`]; if (newOp) { res = `${newOp}\n\n${res}\n\nshould not loosely deep-equal\n\n`; } else { other = ` ${operator} ${other}`; } } super(`${res}${other}`); } } } if (isErrorStackTraceLimitWritable()) Error.stackTraceLimit = limit; this.generatedMessage = !message; ObjectDefineProperty(this, 'name', { value: 'AssertionError [ERR_ASSERTION]', enumerable: false, writable: true, configurable: true }); this.code = 'ERR_ASSERTION'; if (details) { this.actual = undefined; this.expected = undefined; this.operator = undefined; for (let i = 0; i < details.length; i++) { this['message ' + i] = details[i].message; this['actual ' + i] = details[i].actual; this['expected ' + i] = details[i].expected; this['operator ' + i] = details[i].operator; this['stack trace ' + i] = details[i].stack; } } else { this.actual = actual; this.expected = expected; this.operator = operator; } ErrorCaptureStackTrace(this, stackStartFn || stackStartFunction); // Create error message including the error code in the name. this.stack; // eslint-disable-line no-unused-expressions // Reset the name. this.name = 'AssertionError'; } toString() { return `${this.name} [${this.code}]: ${this.message}`; } [inspect.custom](recurseTimes, ctx) { // Long strings should not be fully inspected. const tmpActual = this.actual; const tmpExpected = this.expected; if (typeof this.actual === 'string') { this.actual = addEllipsis(this.actual); } if (typeof this.expected === 'string') { this.expected = addEllipsis(this.expected); } // This limits the `actual` and `expected` property default inspection to // the minimum depth. Otherwise those values would be too verbose compared // to the actual error message which contains a combined view of these two // input values. const result = inspect(this, { ...ctx, customInspect: false, depth: 0 }); // Reset the properties after inspection. this.actual = tmpActual; this.expected = tmpExpected; return result; } } module.exports = AssertionError; 'use strict'; const { ArrayIsArray, ArrayPrototypeIncludes, ArrayPrototypeMap, ArrayPrototypePush, Error, MathMax, Number, ObjectCreate, ObjectDefineProperty, ObjectKeys, SafeSet, String, StringFromCharCode, StringPrototypeIncludes, StringPrototypeToLowerCase, Symbol, } = primordials; const binding = internalBinding('http2'); const { codes: { ERR_HTTP2_HEADER_SINGLE_VALUE, ERR_HTTP2_INVALID_CONNECTION_HEADERS, ERR_HTTP2_INVALID_PSEUDOHEADER, ERR_HTTP2_INVALID_SETTING_VALUE, ERR_INVALID_ARG_TYPE, ERR_INVALID_HTTP_TOKEN }, captureLargerStackTrace, getMessage, hideStackFrames, kIsNodeError, } = require('internal/errors'); const kSensitiveHeaders = Symbol('nodejs.http2.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 SafeSet([ 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 SafeSet([ 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 SafeSet([ 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; 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] = MathMax(1, options.maxOutstandingSettings); } if (typeof options.maxSessionMemory === 'number') { flags |= (1 << IDX_OPTIONS_MAX_SESSION_MEMORY); optionsBuffer[IDX_OPTIONS_MAX_SESSION_MEMORY] = MathMax(1, options.maxSessionMemory); } if (typeof options.maxSettings === 'number') { flags |= (1 << IDX_OPTIONS_MAX_SETTINGS); optionsBuffer[IDX_OPTIONS_MAX_SETTINGS] = MathMax(1, options.maxSettings); } optionsBuffer[IDX_OPTIONS_FLAGS] = flags; } function getDefaultSettings() { settingsBuffer[IDX_SETTINGS_FLAGS] = 0; binding.refreshDefaultSettings(); const holder = ObjectCreate(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; } 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(); return { 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] }; } function updateSettingsBuffer(settings) { let flags = 0; 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 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 = StringFromCharCode(NGHTTP2_NV_FLAG_NO_INDEX); const kNoHeaderFlags = StringFromCharCode(NGHTTP2_NV_FLAG_NONE); function mapToHeaders(map, assertValuePseudoHeader = assertValidPseudoHeader) { let headers = ''; let pseudoHeaders = ''; let count = 0; const keys = ObjectKeys(map); const singles = new SafeSet(); let i, j; let isArray; let key; let value; let isSingleValueHeader; let err; const neverIndex = ArrayPrototypeMap(map[kSensitiveHeaders] || emptyArray, StringPrototypeToLowerCase); for (i = 0; i < keys.length; ++i) { key = keys[i]; value = map[key]; if (value === undefined || key === '') continue; key = StringPrototypeToLowerCase(key); isSingleValueHeader = kSingleValueHeaders.has(key); isArray = ArrayIsArray(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 = ArrayPrototypeIncludes(neverIndex, key) ? kNeverIndexFlag : kNoHeaderFlags; if (key[0] === ':') { err = assertValuePseudoHeader(key); if (err !== undefined) throw err; pseudoHeaders += `${key}\0${value}\0${flags}`; count++; continue; } if (StringPrototypeIncludes(key, ' ')) { 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; captureLargerStackTrace(this); ObjectDefineProperty(this, kIsNodeError, { value: true, enumerable: false, writable: false, configurable: true, }); } toString() { return `${this.name} [${this.code}]: ${this.message}`; } } const assertIsObject = hideStackFrames((value, name, types) => { if (value !== undefined && (value === null || typeof value !== 'object' || ArrayIsArray(value))) { throw new ERR_INVALID_ARG_TYPE(name, types || 'Object', 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( name, value, min, max); } } ); function toHeaderObject(headers, sensitiveHeaders) { const obj = ObjectCreate(null); for (var 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." ArrayPrototypePush(existing, 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 ''; } } 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]; } module.exports = { assertIsObject, assertValidPseudoHeader, assertValidPseudoHeaderResponse, assertValidPseudoHeaderTrailer, assertWithinRange, getAuthority, getDefaultSettings, getSessionState, getSettings, getStreamState, isPayloadMeaningless, kSensitiveHeaders, kSocket, kProxySocket, kRequest, mapToHeaders, NghttpError, sessionName, toHeaderObject, updateOptionsBuffer, updateSettingsBuffer };