mirror of https://github.com/lukechilds/node.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
512 lines
16 KiB
512 lines
16 KiB
'use strict';
|
|
|
|
const binding = process.binding('http2');
|
|
const errors = require('internal/errors');
|
|
|
|
const {
|
|
HTTP2_HEADER_STATUS,
|
|
HTTP2_HEADER_METHOD,
|
|
HTTP2_HEADER_AUTHORITY,
|
|
HTTP2_HEADER_SCHEME,
|
|
HTTP2_HEADER_PATH,
|
|
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_ETAG,
|
|
HTTP2_HEADER_EXPIRES,
|
|
HTTP2_HEADER_FROM,
|
|
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_USER_AGENT,
|
|
|
|
HTTP2_HEADER_CONNECTION,
|
|
HTTP2_HEADER_UPGRADE,
|
|
HTTP2_HEADER_HTTP2_SETTINGS,
|
|
HTTP2_HEADER_TE,
|
|
HTTP2_HEADER_TRANSFER_ENCODING,
|
|
HTTP2_HEADER_HOST,
|
|
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
|
|
]);
|
|
|
|
// 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_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_ETAG,
|
|
HTTP2_HEADER_EXPIRES,
|
|
HTTP2_HEADER_FROM,
|
|
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_USER_AGENT
|
|
]);
|
|
|
|
// 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 = new Uint32Array(binding.settingsArrayBuffer);
|
|
const optionsBuffer = new Uint32Array(binding.optionsArrayBuffer);
|
|
|
|
// 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 = new Float64Array(binding.sessionStateArrayBuffer);
|
|
const streamState = new Float64Array(binding.streamStateArrayBuffer);
|
|
|
|
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_FLAGS = 6;
|
|
|
|
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_FLAGS = 5;
|
|
|
|
function updateOptionsBuffer(options) {
|
|
var 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;
|
|
}
|
|
optionsBuffer[IDX_OPTIONS_FLAGS] = flags;
|
|
}
|
|
|
|
function getDefaultSettings() {
|
|
settingsBuffer[IDX_SETTINGS_FLAGS] = 0;
|
|
binding.refreshDefaultSettings();
|
|
const holder = Object.create(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 =
|
|
settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE];
|
|
}
|
|
|
|
return holder;
|
|
}
|
|
|
|
// remote is a boolean. true to fetch remote settings, false to fetch local.
|
|
// this is only called internally
|
|
function getSettings(session, remote) {
|
|
const holder = Object.create(null);
|
|
if (remote)
|
|
binding.refreshRemoteSettings(session);
|
|
else
|
|
binding.refreshLocalSettings(session);
|
|
|
|
holder.headerTableSize =
|
|
settingsBuffer[IDX_SETTINGS_HEADER_TABLE_SIZE];
|
|
holder.enablePush =
|
|
!!settingsBuffer[IDX_SETTINGS_ENABLE_PUSH];
|
|
holder.initialWindowSize =
|
|
settingsBuffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE];
|
|
holder.maxFrameSize =
|
|
settingsBuffer[IDX_SETTINGS_MAX_FRAME_SIZE];
|
|
holder.maxConcurrentStreams =
|
|
settingsBuffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS];
|
|
holder.maxHeaderListSize =
|
|
settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE];
|
|
return holder;
|
|
}
|
|
|
|
function updateSettingsBuffer(settings) {
|
|
var 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') {
|
|
flags |= (1 << IDX_SETTINGS_MAX_HEADER_LIST_SIZE);
|
|
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);
|
|
}
|
|
|
|
settingsBuffer[IDX_SETTINGS_FLAGS] = flags;
|
|
}
|
|
|
|
function getSessionState(session) {
|
|
const holder = Object.create(null);
|
|
binding.refreshSessionState(session);
|
|
holder.effectiveLocalWindowSize =
|
|
sessionState[IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE];
|
|
holder.effectiveRecvDataLength =
|
|
sessionState[IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH];
|
|
holder.nextStreamID =
|
|
sessionState[IDX_SESSION_STATE_NEXT_STREAM_ID];
|
|
holder.localWindowSize =
|
|
sessionState[IDX_SESSION_STATE_LOCAL_WINDOW_SIZE];
|
|
holder.lastProcStreamID =
|
|
sessionState[IDX_SESSION_STATE_LAST_PROC_STREAM_ID];
|
|
holder.remoteWindowSize =
|
|
sessionState[IDX_SESSION_STATE_REMOTE_WINDOW_SIZE];
|
|
holder.outboundQueueSize =
|
|
sessionState[IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE];
|
|
holder.deflateDynamicTableSize =
|
|
sessionState[IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE];
|
|
holder.inflateDynamicTableSize =
|
|
sessionState[IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE];
|
|
return holder;
|
|
}
|
|
|
|
function getStreamState(session, stream) {
|
|
const holder = Object.create(null);
|
|
binding.refreshStreamState(session, stream);
|
|
holder.state =
|
|
streamState[IDX_STREAM_STATE];
|
|
holder.weight =
|
|
streamState[IDX_STREAM_STATE_WEIGHT];
|
|
holder.sumDependencyWeight =
|
|
streamState[IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT];
|
|
holder.localClose =
|
|
streamState[IDX_STREAM_STATE_LOCAL_CLOSE];
|
|
holder.remoteClose =
|
|
streamState[IDX_STREAM_STATE_REMOTE_CLOSE];
|
|
holder.localWindowSize =
|
|
streamState[IDX_STREAM_STATE_LOCAL_WINDOW_SIZE];
|
|
return holder;
|
|
}
|
|
|
|
function isIllegalConnectionSpecificHeader(name, value) {
|
|
switch (name) {
|
|
case HTTP2_HEADER_CONNECTION:
|
|
case HTTP2_HEADER_UPGRADE:
|
|
case HTTP2_HEADER_HOST:
|
|
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:
|
|
const val = Array.isArray(value) ? value.join(', ') : value;
|
|
return val !== 'trailers';
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function assertValidPseudoHeader(key) {
|
|
if (!kValidPseudoHeaders.has(key)) {
|
|
const err = new errors.Error('ERR_HTTP2_INVALID_PSEUDOHEADER', key);
|
|
Error.captureStackTrace(err, assertValidPseudoHeader);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
function assertValidPseudoHeaderResponse(key) {
|
|
if (key !== ':status') {
|
|
const err = new errors.Error('ERR_HTTP2_INVALID_PSEUDOHEADER', key);
|
|
Error.captureStackTrace(err, assertValidPseudoHeaderResponse);
|
|
return err;
|
|
}
|
|
}
|
|
|
|
function assertValidPseudoHeaderTrailer(key) {
|
|
const err = new errors.Error('ERR_HTTP2_INVALID_PSEUDOHEADER', key);
|
|
Error.captureStackTrace(err, assertValidPseudoHeaderTrailer);
|
|
return err;
|
|
}
|
|
|
|
function mapToHeaders(map,
|
|
assertValuePseudoHeader = assertValidPseudoHeader) {
|
|
const ret = [];
|
|
const keys = Object.keys(map);
|
|
const singles = new Set();
|
|
for (var i = 0; i < keys.length; i++) {
|
|
let key = keys[i];
|
|
let value = map[key];
|
|
let val;
|
|
if (typeof key === 'symbol' || value === undefined || !key)
|
|
continue;
|
|
key = String(key).toLowerCase();
|
|
const isArray = Array.isArray(value);
|
|
if (isArray) {
|
|
switch (value.length) {
|
|
case 0:
|
|
continue;
|
|
case 1:
|
|
value = String(value[0]);
|
|
break;
|
|
default:
|
|
if (kSingleValueHeaders.has(key))
|
|
return new errors.Error('ERR_HTTP2_HEADER_SINGLE_VALUE', key);
|
|
}
|
|
}
|
|
if (key[0] === ':') {
|
|
const err = assertValuePseudoHeader(key);
|
|
if (err !== undefined)
|
|
return err;
|
|
ret.unshift([key, String(value)]);
|
|
} else {
|
|
if (kSingleValueHeaders.has(key)) {
|
|
if (singles.has(key))
|
|
return new errors.Error('ERR_HTTP2_HEADER_SINGLE_VALUE', key);
|
|
singles.add(key);
|
|
}
|
|
if (isIllegalConnectionSpecificHeader(key, value)) {
|
|
return new errors.Error('ERR_HTTP2_INVALID_CONNECTION_HEADERS');
|
|
}
|
|
if (isArray) {
|
|
for (var k = 0; k < value.length; k++) {
|
|
val = String(value[k]);
|
|
ret.push([key, val]);
|
|
}
|
|
} else {
|
|
val = String(value);
|
|
ret.push([key, val]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
class NghttpError extends Error {
|
|
constructor(ret) {
|
|
super(binding.nghttp2ErrorString(ret));
|
|
this.code = 'ERR_HTTP2_ERROR';
|
|
this.name = 'Error [ERR_HTTP2_ERROR]';
|
|
this.errno = ret;
|
|
}
|
|
}
|
|
|
|
function assertIsObject(value, name, types) {
|
|
if (value !== undefined &&
|
|
(value === null ||
|
|
typeof value !== 'object' ||
|
|
Array.isArray(value))) {
|
|
const err = new errors.TypeError('ERR_INVALID_ARG_TYPE',
|
|
name, types || 'object');
|
|
Error.captureStackTrace(err, assertIsObject);
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
function assertWithinRange(name, value, min = 0, max = Infinity) {
|
|
if (value !== undefined &&
|
|
(typeof value !== 'number' || value < min || value > max)) {
|
|
const err = new errors.RangeError('ERR_HTTP2_INVALID_SETTING_VALUE',
|
|
name, value);
|
|
err.min = min;
|
|
err.max = max;
|
|
err.actual = value;
|
|
Error.captureStackTrace(err, assertWithinRange);
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
function toHeaderObject(headers) {
|
|
const obj = Object.create(null);
|
|
for (var n = 0; n < headers.length; n = n + 2) {
|
|
var name = headers[n];
|
|
var value = headers[n + 1];
|
|
if (name === HTTP2_HEADER_STATUS)
|
|
value |= 0;
|
|
var existing = obj[name];
|
|
if (existing === undefined) {
|
|
obj[name] = value;
|
|
} else if (!kSingleValueHeaders.has(name)) {
|
|
if (name === 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}`;
|
|
} else {
|
|
if (Array.isArray(existing))
|
|
existing.push(value);
|
|
else
|
|
obj[name] = [existing, value];
|
|
}
|
|
}
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
function isPayloadMeaningless(method) {
|
|
return kNoPayloadMethods.has(method);
|
|
}
|
|
|
|
module.exports = {
|
|
assertIsObject,
|
|
assertValidPseudoHeaderResponse,
|
|
assertValidPseudoHeaderTrailer,
|
|
assertWithinRange,
|
|
getDefaultSettings,
|
|
getSessionState,
|
|
getSettings,
|
|
getStreamState,
|
|
isPayloadMeaningless,
|
|
mapToHeaders,
|
|
NghttpError,
|
|
toHeaderObject,
|
|
updateOptionsBuffer,
|
|
updateSettingsBuffer
|
|
};
|
|
|