http2: add raw header array support to h2Session.request()
This also notably changes error handling for request(). Previously some invalid header values (but not all) would cause the session to be unnecessarily destroyed automatically, e.g. passing an unparseable header name to request(). This is no longer the case: header validation failures will throw an error, but will not destroy the session or emit 'error' events. PR-URL: https://github.com/nodejs/node/pull/57917 Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
9bcef6821c
commit
43bf1f619a
@ -1078,7 +1078,7 @@ changes:
|
|||||||
`weight` option is deprecated.
|
`weight` option is deprecated.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
* `headers` {HTTP/2 Headers Object}
|
* `headers` {HTTP/2 Headers Object} | {Array}
|
||||||
|
|
||||||
* `options` {Object}
|
* `options` {Object}
|
||||||
* `endStream` {boolean} `true` if the `Http2Stream` _writable_ side should
|
* `endStream` {boolean} `true` if the `Http2Stream` _writable_ side should
|
||||||
|
@ -9,7 +9,6 @@ const {
|
|||||||
ObjectDefineProperty,
|
ObjectDefineProperty,
|
||||||
ObjectEntries,
|
ObjectEntries,
|
||||||
ObjectHasOwn,
|
ObjectHasOwn,
|
||||||
ObjectKeys,
|
|
||||||
Promise,
|
Promise,
|
||||||
Proxy,
|
Proxy,
|
||||||
ReflectApply,
|
ReflectApply,
|
||||||
@ -47,10 +46,7 @@ const { Duplex } = require('stream');
|
|||||||
const tls = require('tls');
|
const tls = require('tls');
|
||||||
const { setImmediate, setTimeout, clearTimeout } = require('timers');
|
const { setImmediate, setTimeout, clearTimeout } = require('timers');
|
||||||
|
|
||||||
const {
|
const { kIncomingMessage } = require('_http_common');
|
||||||
kIncomingMessage,
|
|
||||||
_checkIsHttpToken: checkIsHttpToken,
|
|
||||||
} = require('_http_common');
|
|
||||||
const { kServerResponse, Server: HttpServer, httpServerPreClose, setupConnectionsTracking } = require('_http_server');
|
const { kServerResponse, Server: HttpServer, httpServerPreClose, setupConnectionsTracking } = require('_http_server');
|
||||||
const JSStreamSocket = require('internal/js_stream_socket');
|
const JSStreamSocket = require('internal/js_stream_socket');
|
||||||
|
|
||||||
@ -69,9 +65,6 @@ const {
|
|||||||
codes: {
|
codes: {
|
||||||
ERR_HTTP2_ALTSVC_INVALID_ORIGIN,
|
ERR_HTTP2_ALTSVC_INVALID_ORIGIN,
|
||||||
ERR_HTTP2_ALTSVC_LENGTH,
|
ERR_HTTP2_ALTSVC_LENGTH,
|
||||||
ERR_HTTP2_CONNECT_AUTHORITY,
|
|
||||||
ERR_HTTP2_CONNECT_PATH,
|
|
||||||
ERR_HTTP2_CONNECT_SCHEME,
|
|
||||||
ERR_HTTP2_GOAWAY_SESSION,
|
ERR_HTTP2_GOAWAY_SESSION,
|
||||||
ERR_HTTP2_HEADERS_AFTER_RESPOND,
|
ERR_HTTP2_HEADERS_AFTER_RESPOND,
|
||||||
ERR_HTTP2_HEADERS_SENT,
|
ERR_HTTP2_HEADERS_SENT,
|
||||||
@ -109,7 +102,6 @@ const {
|
|||||||
ERR_INVALID_ARG_TYPE,
|
ERR_INVALID_ARG_TYPE,
|
||||||
ERR_INVALID_ARG_VALUE,
|
ERR_INVALID_ARG_VALUE,
|
||||||
ERR_INVALID_CHAR,
|
ERR_INVALID_CHAR,
|
||||||
ERR_INVALID_HTTP_TOKEN,
|
|
||||||
ERR_OUT_OF_RANGE,
|
ERR_OUT_OF_RANGE,
|
||||||
ERR_SOCKET_CLOSED,
|
ERR_SOCKET_CLOSED,
|
||||||
},
|
},
|
||||||
@ -138,23 +130,26 @@ const {
|
|||||||
const {
|
const {
|
||||||
assertIsObject,
|
assertIsObject,
|
||||||
assertIsArray,
|
assertIsArray,
|
||||||
assertValidPseudoHeader,
|
|
||||||
assertValidPseudoHeaderResponse,
|
assertValidPseudoHeaderResponse,
|
||||||
assertValidPseudoHeaderTrailer,
|
assertValidPseudoHeaderTrailer,
|
||||||
assertWithinRange,
|
assertWithinRange,
|
||||||
|
buildNgHeaderString,
|
||||||
getAuthority,
|
getAuthority,
|
||||||
getDefaultSettings,
|
getDefaultSettings,
|
||||||
getSessionState,
|
getSessionState,
|
||||||
getSettings,
|
getSettings,
|
||||||
getStreamState,
|
getStreamState,
|
||||||
isPayloadMeaningless,
|
isPayloadMeaningless,
|
||||||
|
kAuthority,
|
||||||
kSensitiveHeaders,
|
kSensitiveHeaders,
|
||||||
kSocket,
|
kSocket,
|
||||||
kRequest,
|
kRequest,
|
||||||
|
kProtocol,
|
||||||
kProxySocket,
|
kProxySocket,
|
||||||
mapToHeaders,
|
|
||||||
MAX_ADDITIONAL_SETTINGS,
|
MAX_ADDITIONAL_SETTINGS,
|
||||||
NghttpError,
|
NghttpError,
|
||||||
|
prepareRequestHeadersArray,
|
||||||
|
prepareRequestHeadersObject,
|
||||||
remoteCustomSettingsToBuffer,
|
remoteCustomSettingsToBuffer,
|
||||||
sessionName,
|
sessionName,
|
||||||
toHeaderObject,
|
toHeaderObject,
|
||||||
@ -242,7 +237,6 @@ const NETServer = net.Server;
|
|||||||
const TLSServer = tls.Server;
|
const TLSServer = tls.Server;
|
||||||
|
|
||||||
const kAlpnProtocol = Symbol('alpnProtocol');
|
const kAlpnProtocol = Symbol('alpnProtocol');
|
||||||
const kAuthority = Symbol('authority');
|
|
||||||
const kEncrypted = Symbol('encrypted');
|
const kEncrypted = Symbol('encrypted');
|
||||||
const kID = Symbol('id');
|
const kID = Symbol('id');
|
||||||
const kInit = Symbol('init');
|
const kInit = Symbol('init');
|
||||||
@ -254,7 +248,6 @@ const kOwner = owner_symbol;
|
|||||||
const kOrigin = Symbol('origin');
|
const kOrigin = Symbol('origin');
|
||||||
const kPendingRequestCalls = Symbol('kPendingRequestCalls');
|
const kPendingRequestCalls = Symbol('kPendingRequestCalls');
|
||||||
const kProceed = Symbol('proceed');
|
const kProceed = Symbol('proceed');
|
||||||
const kProtocol = Symbol('protocol');
|
|
||||||
const kRemoteSettings = Symbol('remote-settings');
|
const kRemoteSettings = Symbol('remote-settings');
|
||||||
const kRequestAsyncResource = Symbol('requestAsyncResource');
|
const kRequestAsyncResource = Symbol('requestAsyncResource');
|
||||||
const kSentHeaders = Symbol('sent-headers');
|
const kSentHeaders = Symbol('sent-headers');
|
||||||
@ -297,7 +290,6 @@ const {
|
|||||||
HTTP2_HEADER_DATE,
|
HTTP2_HEADER_DATE,
|
||||||
HTTP2_HEADER_METHOD,
|
HTTP2_HEADER_METHOD,
|
||||||
HTTP2_HEADER_PATH,
|
HTTP2_HEADER_PATH,
|
||||||
HTTP2_HEADER_PROTOCOL,
|
|
||||||
HTTP2_HEADER_SCHEME,
|
HTTP2_HEADER_SCHEME,
|
||||||
HTTP2_HEADER_STATUS,
|
HTTP2_HEADER_STATUS,
|
||||||
HTTP2_HEADER_CONTENT_LENGTH,
|
HTTP2_HEADER_CONTENT_LENGTH,
|
||||||
@ -312,7 +304,6 @@ const {
|
|||||||
|
|
||||||
HTTP2_METHOD_GET,
|
HTTP2_METHOD_GET,
|
||||||
HTTP2_METHOD_HEAD,
|
HTTP2_METHOD_HEAD,
|
||||||
HTTP2_METHOD_CONNECT,
|
|
||||||
|
|
||||||
HTTP_STATUS_CONTINUE,
|
HTTP_STATUS_CONTINUE,
|
||||||
HTTP_STATUS_RESET_CONTENT,
|
HTTP_STATUS_RESET_CONTENT,
|
||||||
@ -1808,7 +1799,7 @@ class ClientHttp2Session extends Http2Session {
|
|||||||
|
|
||||||
// Submits a new HTTP2 request to the connected peer. Returns the
|
// Submits a new HTTP2 request to the connected peer. Returns the
|
||||||
// associated Http2Stream instance.
|
// associated Http2Stream instance.
|
||||||
request(headers, options) {
|
request(headersParam, options) {
|
||||||
debugSessionObj(this, 'initiating request');
|
debugSessionObj(this, 'initiating request');
|
||||||
|
|
||||||
if (this.destroyed)
|
if (this.destroyed)
|
||||||
@ -1819,62 +1810,61 @@ class ClientHttp2Session extends Http2Session {
|
|||||||
|
|
||||||
this[kUpdateTimer]();
|
this[kUpdateTimer]();
|
||||||
|
|
||||||
if (headers !== null && headers !== undefined) {
|
let headersList;
|
||||||
const keys = ObjectKeys(headers);
|
let headersObject;
|
||||||
for (let i = 0; i < keys.length; i++) {
|
let scheme;
|
||||||
const header = keys[i];
|
let authority;
|
||||||
if (header[0] === ':') {
|
let method;
|
||||||
assertValidPseudoHeader(header);
|
|
||||||
} else if (header && !checkIsHttpToken(header))
|
|
||||||
this.destroy(new ERR_INVALID_HTTP_TOKEN('Header name', header));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assertIsObject(headers, 'headers');
|
if (ArrayIsArray(headersParam)) {
|
||||||
assertIsObject(options, 'options');
|
({
|
||||||
|
headersList,
|
||||||
headers = ObjectAssign({ __proto__: null }, headers);
|
scheme,
|
||||||
options = { ...options };
|
authority,
|
||||||
|
method,
|
||||||
if (headers[HTTP2_HEADER_METHOD] === undefined)
|
} = prepareRequestHeadersArray(headersParam, this));
|
||||||
headers[HTTP2_HEADER_METHOD] = HTTP2_METHOD_GET;
|
} else if (!!headersParam && typeof headersParam === 'object') {
|
||||||
|
({
|
||||||
const connect = headers[HTTP2_HEADER_METHOD] === HTTP2_METHOD_CONNECT;
|
headersObject,
|
||||||
|
headersList,
|
||||||
if (!connect || headers[HTTP2_HEADER_PROTOCOL] !== undefined) {
|
scheme,
|
||||||
if (getAuthority(headers) === undefined)
|
authority,
|
||||||
headers[HTTP2_HEADER_AUTHORITY] = this[kAuthority];
|
method,
|
||||||
if (headers[HTTP2_HEADER_SCHEME] === undefined)
|
} = prepareRequestHeadersObject(headersParam, this));
|
||||||
headers[HTTP2_HEADER_SCHEME] = this[kProtocol].slice(0, -1);
|
} else if (headersParam === undefined) {
|
||||||
if (headers[HTTP2_HEADER_PATH] === undefined)
|
({
|
||||||
headers[HTTP2_HEADER_PATH] = '/';
|
headersObject,
|
||||||
|
headersList,
|
||||||
|
scheme,
|
||||||
|
authority,
|
||||||
|
method,
|
||||||
|
} = prepareRequestHeadersObject({}, this));
|
||||||
} else {
|
} else {
|
||||||
if (headers[HTTP2_HEADER_AUTHORITY] === undefined)
|
throw new ERR_INVALID_ARG_TYPE.HideStackFramesError(
|
||||||
throw new ERR_HTTP2_CONNECT_AUTHORITY();
|
'headers',
|
||||||
if (headers[HTTP2_HEADER_SCHEME] !== undefined)
|
['Object', 'Array'],
|
||||||
throw new ERR_HTTP2_CONNECT_SCHEME();
|
headersParam,
|
||||||
if (headers[HTTP2_HEADER_PATH] !== undefined)
|
);
|
||||||
throw new ERR_HTTP2_CONNECT_PATH();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assertIsObject(options, 'options');
|
||||||
|
options = { ...options };
|
||||||
|
|
||||||
setAndValidatePriorityOptions(options);
|
setAndValidatePriorityOptions(options);
|
||||||
|
|
||||||
if (options.endStream === undefined) {
|
if (options.endStream === undefined) {
|
||||||
// For some methods, we know that a payload is meaningless, so end the
|
// For some methods, we know that a payload is meaningless, so end the
|
||||||
// stream by default if the user has not specifically indicated a
|
// stream by default if the user has not specifically indicated a
|
||||||
// preference.
|
// preference.
|
||||||
options.endStream = isPayloadMeaningless(headers[HTTP2_HEADER_METHOD]);
|
options.endStream = isPayloadMeaningless(method);
|
||||||
} else {
|
} else {
|
||||||
validateBoolean(options.endStream, 'options.endStream');
|
validateBoolean(options.endStream, 'options.endStream');
|
||||||
}
|
}
|
||||||
|
|
||||||
const headersList = mapToHeaders(headers);
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-use-before-define
|
// eslint-disable-next-line no-use-before-define
|
||||||
const stream = new ClientHttp2Stream(this, undefined, undefined, {});
|
const stream = new ClientHttp2Stream(this, undefined, undefined, {});
|
||||||
stream[kSentHeaders] = headers;
|
stream[kSentHeaders] = headersObject; // N.b. Only set for object headers, not raw headers
|
||||||
stream[kOrigin] = `${headers[HTTP2_HEADER_SCHEME]}://` +
|
stream[kOrigin] = `${scheme}://${authority}`;
|
||||||
`${getAuthority(headers)}`;
|
|
||||||
const reqAsync = new AsyncResource('PendingRequest');
|
const reqAsync = new AsyncResource('PendingRequest');
|
||||||
stream[kRequestAsyncResource] = reqAsync;
|
stream[kRequestAsyncResource] = reqAsync;
|
||||||
|
|
||||||
@ -2350,7 +2340,7 @@ class Http2Stream extends Duplex {
|
|||||||
|
|
||||||
this[kUpdateTimer]();
|
this[kUpdateTimer]();
|
||||||
|
|
||||||
const headersList = mapToHeaders(headers, assertValidPseudoHeaderTrailer);
|
const headersList = buildNgHeaderString(headers, assertValidPseudoHeaderTrailer);
|
||||||
this[kSentTrailers] = headers;
|
this[kSentTrailers] = headers;
|
||||||
|
|
||||||
// Send the trailers in setImmediate so we don't do it on nghttp2 stack.
|
// Send the trailers in setImmediate so we don't do it on nghttp2 stack.
|
||||||
@ -2598,7 +2588,7 @@ function processRespondWithFD(self, fd, headers, offset = 0, length = -1,
|
|||||||
|
|
||||||
let headersList;
|
let headersList;
|
||||||
try {
|
try {
|
||||||
headersList = mapToHeaders(headers, assertValidPseudoHeaderResponse);
|
headersList = buildNgHeaderString(headers, assertValidPseudoHeaderResponse);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
self.destroy(err);
|
self.destroy(err);
|
||||||
return;
|
return;
|
||||||
@ -2822,7 +2812,7 @@ class ServerHttp2Stream extends Http2Stream {
|
|||||||
if (headers[HTTP2_HEADER_METHOD] === HTTP2_METHOD_HEAD)
|
if (headers[HTTP2_HEADER_METHOD] === HTTP2_METHOD_HEAD)
|
||||||
headRequest = options.endStream = true;
|
headRequest = options.endStream = true;
|
||||||
|
|
||||||
const headersList = mapToHeaders(headers);
|
const headersList = buildNgHeaderString(headers);
|
||||||
|
|
||||||
const streamOptions = options.endStream ? STREAM_OPTION_EMPTY_PAYLOAD : 0;
|
const streamOptions = options.endStream ? STREAM_OPTION_EMPTY_PAYLOAD : 0;
|
||||||
|
|
||||||
@ -2902,7 +2892,7 @@ class ServerHttp2Stream extends Http2Stream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
headers = processHeaders(headers, options);
|
headers = processHeaders(headers, options);
|
||||||
const headersList = mapToHeaders(headers, assertValidPseudoHeaderResponse);
|
const headersList = buildNgHeaderString(headers, assertValidPseudoHeaderResponse);
|
||||||
this[kSentHeaders] = headers;
|
this[kSentHeaders] = headers;
|
||||||
|
|
||||||
state.flags |= STREAM_FLAGS_HEADERS_SENT;
|
state.flags |= STREAM_FLAGS_HEADERS_SENT;
|
||||||
@ -3079,7 +3069,7 @@ class ServerHttp2Stream extends Http2Stream {
|
|||||||
|
|
||||||
this[kUpdateTimer]();
|
this[kUpdateTimer]();
|
||||||
|
|
||||||
const headersList = mapToHeaders(headers, assertValidPseudoHeaderResponse);
|
const headersList = buildNgHeaderString(headers, assertValidPseudoHeaderResponse);
|
||||||
if (!this[kInfoHeaders])
|
if (!this[kInfoHeaders])
|
||||||
this[kInfoHeaders] = [headers];
|
this[kInfoHeaders] = [headers];
|
||||||
else
|
else
|
||||||
|
@ -6,6 +6,7 @@ const {
|
|||||||
MathMax,
|
MathMax,
|
||||||
Number,
|
Number,
|
||||||
NumberIsNaN,
|
NumberIsNaN,
|
||||||
|
ObjectAssign,
|
||||||
ObjectKeys,
|
ObjectKeys,
|
||||||
SafeSet,
|
SafeSet,
|
||||||
String,
|
String,
|
||||||
@ -13,9 +14,16 @@ const {
|
|||||||
Symbol,
|
Symbol,
|
||||||
} = primordials;
|
} = primordials;
|
||||||
|
|
||||||
|
const {
|
||||||
|
_checkIsHttpToken: checkIsHttpToken,
|
||||||
|
} = require('_http_common');
|
||||||
|
|
||||||
const binding = internalBinding('http2');
|
const binding = internalBinding('http2');
|
||||||
const {
|
const {
|
||||||
codes: {
|
codes: {
|
||||||
|
ERR_HTTP2_CONNECT_AUTHORITY,
|
||||||
|
ERR_HTTP2_CONNECT_PATH,
|
||||||
|
ERR_HTTP2_CONNECT_SCHEME,
|
||||||
ERR_HTTP2_HEADER_SINGLE_VALUE,
|
ERR_HTTP2_HEADER_SINGLE_VALUE,
|
||||||
ERR_HTTP2_INVALID_CONNECTION_HEADERS,
|
ERR_HTTP2_INVALID_CONNECTION_HEADERS,
|
||||||
ERR_HTTP2_INVALID_PSEUDOHEADER: { HideStackFramesError: ERR_HTTP2_INVALID_PSEUDOHEADER },
|
ERR_HTTP2_INVALID_PSEUDOHEADER: { HideStackFramesError: ERR_HTTP2_INVALID_PSEUDOHEADER },
|
||||||
@ -29,8 +37,10 @@ const {
|
|||||||
kIsNodeError,
|
kIsNodeError,
|
||||||
} = require('internal/errors');
|
} = require('internal/errors');
|
||||||
|
|
||||||
|
const kAuthority = Symbol('authority');
|
||||||
const kSensitiveHeaders = Symbol('sensitiveHeaders');
|
const kSensitiveHeaders = Symbol('sensitiveHeaders');
|
||||||
const kSocket = Symbol('socket');
|
const kSocket = Symbol('socket');
|
||||||
|
const kProtocol = Symbol('protocol');
|
||||||
const kProxySocket = Symbol('proxySocket');
|
const kProxySocket = Symbol('proxySocket');
|
||||||
const kRequest = Symbol('request');
|
const kRequest = Symbol('request');
|
||||||
|
|
||||||
@ -91,6 +101,7 @@ const {
|
|||||||
HTTP2_HEADER_KEEP_ALIVE,
|
HTTP2_HEADER_KEEP_ALIVE,
|
||||||
HTTP2_HEADER_PROXY_CONNECTION,
|
HTTP2_HEADER_PROXY_CONNECTION,
|
||||||
|
|
||||||
|
HTTP2_METHOD_CONNECT,
|
||||||
HTTP2_METHOD_DELETE,
|
HTTP2_METHOD_DELETE,
|
||||||
HTTP2_METHOD_GET,
|
HTTP2_METHOD_GET,
|
||||||
HTTP2_METHOD_HEAD,
|
HTTP2_METHOD_HEAD,
|
||||||
@ -601,35 +612,155 @@ const assertValidPseudoHeaderTrailer = hideStackFrames((key) => {
|
|||||||
throw new ERR_HTTP2_INVALID_PSEUDOHEADER(key);
|
throw new ERR_HTTP2_INVALID_PSEUDOHEADER(key);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a request headers array, validates it and sets defaults, and returns
|
||||||
|
* the resulting headers in NgHeaders string list format.
|
||||||
|
*/
|
||||||
|
function prepareRequestHeadersArray(headers, session) {
|
||||||
|
let method;
|
||||||
|
let scheme;
|
||||||
|
let authority;
|
||||||
|
let path;
|
||||||
|
let protocol;
|
||||||
|
|
||||||
|
// Extract the key psuedo header values from the headers array
|
||||||
|
for (let i = 0; i < headers.length; i += 2) {
|
||||||
|
if (headers[i][0] !== ':') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const header = headers[i].toLowerCase();
|
||||||
|
const value = headers[i + 1];
|
||||||
|
|
||||||
|
if (header === HTTP2_HEADER_METHOD) {
|
||||||
|
method = value;
|
||||||
|
} else if (header === HTTP2_HEADER_SCHEME) {
|
||||||
|
scheme = value;
|
||||||
|
} else if (header === HTTP2_HEADER_AUTHORITY) {
|
||||||
|
authority = value;
|
||||||
|
} else if (header === HTTP2_HEADER_PATH) {
|
||||||
|
path = value;
|
||||||
|
} else if (header === HTTP2_HEADER_PROTOCOL) {
|
||||||
|
protocol = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We then build an array of any missing pseudo headers, to prepend
|
||||||
|
// default values to the given header array:
|
||||||
|
const additionalPsuedoHeaders = [];
|
||||||
|
|
||||||
|
if (method === undefined) {
|
||||||
|
method = HTTP2_METHOD_GET;
|
||||||
|
additionalPsuedoHeaders.push(HTTP2_HEADER_METHOD, method);
|
||||||
|
}
|
||||||
|
|
||||||
|
const connect = method === HTTP2_METHOD_CONNECT;
|
||||||
|
|
||||||
|
if (!connect || protocol !== undefined) {
|
||||||
|
if (authority === undefined && headers[HTTP2_HEADER_HOST] === undefined) {
|
||||||
|
authority = session[kAuthority];
|
||||||
|
additionalPsuedoHeaders.push(HTTP2_HEADER_AUTHORITY, authority);
|
||||||
|
}
|
||||||
|
if (scheme === undefined) {
|
||||||
|
scheme = session[kProtocol].slice(0, -1);
|
||||||
|
additionalPsuedoHeaders.push(HTTP2_HEADER_SCHEME, scheme);
|
||||||
|
}
|
||||||
|
if (path === undefined) {
|
||||||
|
additionalPsuedoHeaders.push(HTTP2_HEADER_PATH, '/');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (authority === undefined)
|
||||||
|
throw new ERR_HTTP2_CONNECT_AUTHORITY();
|
||||||
|
if (scheme !== undefined)
|
||||||
|
throw new ERR_HTTP2_CONNECT_SCHEME();
|
||||||
|
if (path !== undefined)
|
||||||
|
throw new ERR_HTTP2_CONNECT_PATH();
|
||||||
|
}
|
||||||
|
|
||||||
|
const headersList = buildNgHeaderString(
|
||||||
|
additionalPsuedoHeaders.length ?
|
||||||
|
additionalPsuedoHeaders.concat(headers) :
|
||||||
|
headers,
|
||||||
|
assertValidPseudoHeader,
|
||||||
|
headers[kSensitiveHeaders],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
headersList,
|
||||||
|
scheme,
|
||||||
|
authority: authority ?? headers[HTTP2_HEADER_HOST],
|
||||||
|
method,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Takes a request headers object, validates it and sets defaults, and returns
|
||||||
|
* the resulting headers in object format and NgHeaders string list format.
|
||||||
|
*/
|
||||||
|
function prepareRequestHeadersObject(headers, session) {
|
||||||
|
const headersObject = ObjectAssign({ __proto__: null }, headers);
|
||||||
|
|
||||||
|
if (headersObject[HTTP2_HEADER_METHOD] === undefined) {
|
||||||
|
headersObject[HTTP2_HEADER_METHOD] = HTTP2_METHOD_GET;
|
||||||
|
}
|
||||||
|
|
||||||
|
const connect = headersObject[HTTP2_HEADER_METHOD] === HTTP2_METHOD_CONNECT;
|
||||||
|
|
||||||
|
if (!connect || headersObject[HTTP2_HEADER_PROTOCOL] !== undefined) {
|
||||||
|
if (getAuthority(headersObject) === undefined)
|
||||||
|
headersObject[HTTP2_HEADER_AUTHORITY] = session[kAuthority];
|
||||||
|
if (headersObject[HTTP2_HEADER_SCHEME] === undefined)
|
||||||
|
headersObject[HTTP2_HEADER_SCHEME] = session[kProtocol].slice(0, -1);
|
||||||
|
if (headersObject[HTTP2_HEADER_PATH] === undefined)
|
||||||
|
headersObject[HTTP2_HEADER_PATH] = '/';
|
||||||
|
} else {
|
||||||
|
if (headersObject[HTTP2_HEADER_AUTHORITY] === undefined)
|
||||||
|
throw new ERR_HTTP2_CONNECT_AUTHORITY();
|
||||||
|
if (headersObject[HTTP2_HEADER_SCHEME] !== undefined)
|
||||||
|
throw new ERR_HTTP2_CONNECT_SCHEME();
|
||||||
|
if (headersObject[HTTP2_HEADER_PATH] !== undefined)
|
||||||
|
throw new ERR_HTTP2_CONNECT_PATH();
|
||||||
|
}
|
||||||
|
|
||||||
|
const headersList = buildNgHeaderString(headersObject);
|
||||||
|
|
||||||
|
return {
|
||||||
|
headersObject,
|
||||||
|
headersList,
|
||||||
|
scheme: headersObject[HTTP2_HEADER_SCHEME],
|
||||||
|
authority: getAuthority(headersObject),
|
||||||
|
method: headersObject[HTTP2_HEADER_METHOD],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const emptyArray = [];
|
const emptyArray = [];
|
||||||
const kNeverIndexFlag = StringFromCharCode(NGHTTP2_NV_FLAG_NO_INDEX);
|
const kNeverIndexFlag = StringFromCharCode(NGHTTP2_NV_FLAG_NO_INDEX);
|
||||||
const kNoHeaderFlags = StringFromCharCode(NGHTTP2_NV_FLAG_NONE);
|
const kNoHeaderFlags = StringFromCharCode(NGHTTP2_NV_FLAG_NONE);
|
||||||
function mapToHeaders(map,
|
|
||||||
assertValuePseudoHeader = assertValidPseudoHeader) {
|
/**
|
||||||
|
* Builds an NgHeader string + header count value, validating the header key
|
||||||
|
* format, rejecting illegal header configurations, and marking sensitive headers
|
||||||
|
* that should not be indexed en route. This takes either a flat map of
|
||||||
|
* raw headers ([k1, v1, k2, v2]) or a header object ({ k1: v1, k2: [v2, v3] }).
|
||||||
|
*/
|
||||||
|
function buildNgHeaderString(arrayOrMap,
|
||||||
|
assertValuePseudoHeader = assertValidPseudoHeader,
|
||||||
|
sensitiveHeaders = arrayOrMap[kSensitiveHeaders]) {
|
||||||
let headers = '';
|
let headers = '';
|
||||||
let pseudoHeaders = '';
|
let pseudoHeaders = '';
|
||||||
let count = 0;
|
let count = 0;
|
||||||
const keys = ObjectKeys(map);
|
|
||||||
const singles = new SafeSet();
|
const singles = new SafeSet();
|
||||||
let i, j;
|
const neverIndex = (sensitiveHeaders || emptyArray).map((v) => v.toLowerCase());
|
||||||
let isArray;
|
|
||||||
let key;
|
function processHeader(key, value) {
|
||||||
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();
|
key = key.toLowerCase();
|
||||||
isSingleValueHeader = kSingleValueHeaders.has(key);
|
const isSingleValueHeader = kSingleValueHeaders.has(key);
|
||||||
isArray = ArrayIsArray(value);
|
let isArray = ArrayIsArray(value);
|
||||||
if (isArray) {
|
if (isArray) {
|
||||||
switch (value.length) {
|
switch (value.length) {
|
||||||
case 0:
|
case 0:
|
||||||
continue;
|
return;
|
||||||
case 1:
|
case 1:
|
||||||
value = String(value[0]);
|
value = String(value[0]);
|
||||||
isArray = false;
|
isArray = false;
|
||||||
@ -650,31 +781,50 @@ function mapToHeaders(map,
|
|||||||
kNeverIndexFlag :
|
kNeverIndexFlag :
|
||||||
kNoHeaderFlags;
|
kNoHeaderFlags;
|
||||||
if (key[0] === ':') {
|
if (key[0] === ':') {
|
||||||
err = assertValuePseudoHeader(key);
|
const err = assertValuePseudoHeader(key);
|
||||||
if (err !== undefined)
|
if (err !== undefined)
|
||||||
throw err;
|
throw err;
|
||||||
pseudoHeaders += `${key}\0${value}\0${flags}`;
|
pseudoHeaders += `${key}\0${value}\0${flags}`;
|
||||||
count++;
|
count++;
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
if (key.includes(' ')) {
|
if (!checkIsHttpToken(key)) {
|
||||||
throw new ERR_INVALID_HTTP_TOKEN('Header name', key);
|
throw new ERR_INVALID_HTTP_TOKEN('Header name', key);
|
||||||
}
|
}
|
||||||
if (isIllegalConnectionSpecificHeader(key, value)) {
|
if (isIllegalConnectionSpecificHeader(key, value)) {
|
||||||
throw new ERR_HTTP2_INVALID_CONNECTION_HEADERS(key);
|
throw new ERR_HTTP2_INVALID_CONNECTION_HEADERS(key);
|
||||||
}
|
}
|
||||||
if (isArray) {
|
if (isArray) {
|
||||||
for (j = 0; j < value.length; ++j) {
|
for (let j = 0; j < value.length; ++j) {
|
||||||
const val = String(value[j]);
|
const val = String(value[j]);
|
||||||
headers += `${key}\0${val}\0${flags}`;
|
headers += `${key}\0${val}\0${flags}`;
|
||||||
}
|
}
|
||||||
count += value.length;
|
count += value.length;
|
||||||
continue;
|
return;
|
||||||
}
|
}
|
||||||
headers += `${key}\0${value}\0${flags}`;
|
headers += `${key}\0${value}\0${flags}`;
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ArrayIsArray(arrayOrMap)) {
|
||||||
|
for (let i = 0; i < arrayOrMap.length; i += 2) {
|
||||||
|
const key = arrayOrMap[i];
|
||||||
|
const value = arrayOrMap[i + 1];
|
||||||
|
if (value === undefined || key === '')
|
||||||
|
continue;
|
||||||
|
processHeader(key, value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const keys = ObjectKeys(arrayOrMap);
|
||||||
|
for (let i = 0; i < keys.length; ++i) {
|
||||||
|
const key = keys[i];
|
||||||
|
const value = arrayOrMap[key];
|
||||||
|
if (value === undefined || key === '')
|
||||||
|
continue;
|
||||||
|
processHeader(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return [pseudoHeaders + headers, count];
|
return [pseudoHeaders + headers, count];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -801,19 +951,23 @@ module.exports = {
|
|||||||
assertValidPseudoHeaderResponse,
|
assertValidPseudoHeaderResponse,
|
||||||
assertValidPseudoHeaderTrailer,
|
assertValidPseudoHeaderTrailer,
|
||||||
assertWithinRange,
|
assertWithinRange,
|
||||||
|
buildNgHeaderString,
|
||||||
getAuthority,
|
getAuthority,
|
||||||
getDefaultSettings,
|
getDefaultSettings,
|
||||||
getSessionState,
|
getSessionState,
|
||||||
getSettings,
|
getSettings,
|
||||||
getStreamState,
|
getStreamState,
|
||||||
isPayloadMeaningless,
|
isPayloadMeaningless,
|
||||||
|
kAuthority,
|
||||||
kSensitiveHeaders,
|
kSensitiveHeaders,
|
||||||
kSocket,
|
kSocket,
|
||||||
|
kProtocol,
|
||||||
kProxySocket,
|
kProxySocket,
|
||||||
kRequest,
|
kRequest,
|
||||||
mapToHeaders,
|
|
||||||
MAX_ADDITIONAL_SETTINGS,
|
MAX_ADDITIONAL_SETTINGS,
|
||||||
NghttpError,
|
NghttpError,
|
||||||
|
prepareRequestHeadersArray,
|
||||||
|
prepareRequestHeadersObject,
|
||||||
remoteCustomSettingsToBuffer,
|
remoteCustomSettingsToBuffer,
|
||||||
sessionName,
|
sessionName,
|
||||||
toHeaderObject,
|
toHeaderObject,
|
||||||
|
@ -240,7 +240,7 @@ enum http_status_codes {
|
|||||||
V(VERSION_CONTROL, "VERSION-CONTROL")
|
V(VERSION_CONTROL, "VERSION-CONTROL")
|
||||||
|
|
||||||
// NgHeaders takes as input a block of headers provided by the
|
// NgHeaders takes as input a block of headers provided by the
|
||||||
// JavaScript side (see http2's mapToHeaders function) and
|
// JavaScript side (see http2's buildNgHeaderString function) and
|
||||||
// converts it into a array of ng header structs. This is done
|
// converts it into a array of ng header structs. This is done
|
||||||
// generically to handle both http/2 and (in the future) http/3,
|
// generically to handle both http/2 and (in the future) http/3,
|
||||||
// which use nearly identical structs. The template parameter
|
// which use nearly identical structs. The template parameter
|
||||||
|
@ -8,7 +8,7 @@ if (!common.hasCrypto) { common.skip('missing crypto'); }
|
|||||||
// Capitalized headers
|
// Capitalized headers
|
||||||
|
|
||||||
const http2 = require('http2');
|
const http2 = require('http2');
|
||||||
const { throws, strictEqual } = require('assert');
|
const { throws } = require('assert');
|
||||||
|
|
||||||
{
|
{
|
||||||
const server = http2.createServer(common.mustCall((req, res) => {
|
const server = http2.createServer(common.mustCall((req, res) => {
|
||||||
@ -42,45 +42,40 @@ const { throws, strictEqual } = require('assert');
|
|||||||
const server = http2.createServer();
|
const server = http2.createServer();
|
||||||
server.listen(0, common.mustCall(() => {
|
server.listen(0, common.mustCall(() => {
|
||||||
const session = http2.connect(`http://localhost:${server.address().port}`);
|
const session = http2.connect(`http://localhost:${server.address().port}`);
|
||||||
session.on('error', common.mustCall((e) => {
|
|
||||||
strictEqual(e.code, 'ERR_INVALID_HTTP_TOKEN');
|
|
||||||
server.close();
|
|
||||||
}));
|
|
||||||
throws(() => {
|
throws(() => {
|
||||||
session.request({ 't est': 123 });
|
session.request({ 't est': 123 });
|
||||||
}, {
|
}, {
|
||||||
code: 'ERR_INVALID_HTTP_TOKEN'
|
code: 'ERR_INVALID_HTTP_TOKEN'
|
||||||
});
|
});
|
||||||
|
session.close();
|
||||||
|
server.close();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const server = http2.createServer();
|
const server = http2.createServer();
|
||||||
server.listen(0, common.mustCall(() => {
|
server.listen(0, common.mustCall(() => {
|
||||||
const session = http2.connect(`http://localhost:${server.address().port}`);
|
const session = http2.connect(`http://localhost:${server.address().port}`);
|
||||||
session.on('error', common.mustCall((e) => {
|
|
||||||
strictEqual(e.code, 'ERR_INVALID_HTTP_TOKEN');
|
|
||||||
server.close();
|
|
||||||
}));
|
|
||||||
throws(() => {
|
throws(() => {
|
||||||
session.request({ ' test': 123 });
|
session.request({ ' test': 123 });
|
||||||
}, {
|
}, {
|
||||||
code: 'ERR_INVALID_HTTP_TOKEN'
|
code: 'ERR_INVALID_HTTP_TOKEN'
|
||||||
});
|
});
|
||||||
|
session.close();
|
||||||
|
server.close();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const server = http2.createServer();
|
const server = http2.createServer();
|
||||||
server.listen(0, common.mustCall(() => {
|
server.listen(0, common.mustCall(() => {
|
||||||
const session4 = http2.connect(`http://localhost:${server.address().port}`);
|
const session = http2.connect(`http://localhost:${server.address().port}`);
|
||||||
throws(() => {
|
throws(() => {
|
||||||
session4.request({ ':test': 123 });
|
session.request({ ':test': 123 });
|
||||||
}, {
|
}, {
|
||||||
code: 'ERR_HTTP2_INVALID_PSEUDOHEADER'
|
code: 'ERR_HTTP2_INVALID_PSEUDOHEADER'
|
||||||
});
|
});
|
||||||
session4.close();
|
session.close();
|
||||||
server.close();
|
server.close();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
@ -14,10 +14,8 @@ server1.listen(0, common.mustCall(() => {
|
|||||||
}, {
|
}, {
|
||||||
code: 'ERR_INVALID_HTTP_TOKEN'
|
code: 'ERR_INVALID_HTTP_TOKEN'
|
||||||
});
|
});
|
||||||
session.on('error', common.mustCall((e) => {
|
session.close();
|
||||||
assert.strictEqual(e.code, 'ERR_INVALID_HTTP_TOKEN');
|
server1.close();
|
||||||
server1.close();
|
|
||||||
}));
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const server2 = http2.createServer(common.mustCall((req, res) => {
|
const server2 = http2.createServer(common.mustCall((req, res) => {
|
||||||
|
48
test/parallel/test-http2-raw-headers.js
Normal file
48
test/parallel/test-http2-raw-headers.js
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const common = require('../common');
|
||||||
|
if (!common.hasCrypto)
|
||||||
|
common.skip('missing crypto');
|
||||||
|
const assert = require('assert');
|
||||||
|
const http2 = require('http2');
|
||||||
|
|
||||||
|
{
|
||||||
|
const server = http2.createServer();
|
||||||
|
server.on('stream', common.mustCall((stream, headers, flags, rawHeaders) => {
|
||||||
|
assert.deepStrictEqual(rawHeaders, [
|
||||||
|
':path', '/foobar',
|
||||||
|
':scheme', 'http',
|
||||||
|
':authority', `localhost:${server.address().port}`,
|
||||||
|
':method', 'GET',
|
||||||
|
'a', 'b',
|
||||||
|
'x-foo', 'bar',
|
||||||
|
'a', 'c',
|
||||||
|
]);
|
||||||
|
stream.respond({
|
||||||
|
':status': 200
|
||||||
|
});
|
||||||
|
stream.end();
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
server.listen(0, common.mustCall(() => {
|
||||||
|
const port = server.address().port;
|
||||||
|
const client = http2.connect(`http://localhost:${port}`);
|
||||||
|
|
||||||
|
const req = client.request([
|
||||||
|
':path', '/foobar',
|
||||||
|
':scheme', 'http',
|
||||||
|
':authority', `localhost:${server.address().port}`,
|
||||||
|
':method', 'GET',
|
||||||
|
'a', 'b',
|
||||||
|
'x-FOO', 'bar',
|
||||||
|
'a', 'c',
|
||||||
|
]).end();
|
||||||
|
|
||||||
|
req.on('response', common.mustCall((headers) => {
|
||||||
|
assert.strictEqual(headers[':status'], 200);
|
||||||
|
client.close();
|
||||||
|
server.close();
|
||||||
|
}));
|
||||||
|
}));
|
||||||
|
}
|
@ -45,3 +45,41 @@ const { duplexPair } = require('stream');
|
|||||||
req.resume();
|
req.resume();
|
||||||
req.end();
|
req.end();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const server = http2.createServer();
|
||||||
|
server.on('stream', common.mustCall((stream, headers) => {
|
||||||
|
assert.deepStrictEqual(
|
||||||
|
headers[http2.sensitiveHeaders],
|
||||||
|
['secret']
|
||||||
|
);
|
||||||
|
stream.respond({ ':status': 200 });
|
||||||
|
stream.end();
|
||||||
|
}));
|
||||||
|
|
||||||
|
const [ clientSide, serverSide ] = duplexPair();
|
||||||
|
server.emit('connection', serverSide);
|
||||||
|
|
||||||
|
const client = http2.connect('http://localhost:80', {
|
||||||
|
createConnection: common.mustCall(() => clientSide)
|
||||||
|
});
|
||||||
|
|
||||||
|
const rawHeaders = [
|
||||||
|
':path', '/',
|
||||||
|
'secret', 'secret-value',
|
||||||
|
];
|
||||||
|
rawHeaders[http2.sensitiveHeaders] = ['secret'];
|
||||||
|
|
||||||
|
const req = client.request(rawHeaders);
|
||||||
|
|
||||||
|
req.on('response', common.mustCall((headers) => {
|
||||||
|
assert.strictEqual(headers[':status'], 200);
|
||||||
|
}));
|
||||||
|
|
||||||
|
req.on('end', common.mustCall(() => {
|
||||||
|
clientSide.destroy();
|
||||||
|
clientSide.end();
|
||||||
|
}));
|
||||||
|
req.resume();
|
||||||
|
req.end();
|
||||||
|
}
|
||||||
|
@ -5,19 +5,19 @@ require('../common');
|
|||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
|
|
||||||
// Tests the assertValidPseudoHeader function that is used within the
|
// Tests the assertValidPseudoHeader function that is used within the
|
||||||
// mapToHeaders function. The assert function is not exported so we
|
// buildNgHeaderString function. The assert function is not exported so we
|
||||||
// have to test it through mapToHeaders
|
// have to test it through buildNgHeaderString
|
||||||
|
|
||||||
const { mapToHeaders } = require('internal/http2/util');
|
const { buildNgHeaderString } = require('internal/http2/util');
|
||||||
|
|
||||||
// These should not throw
|
// These should not throw
|
||||||
mapToHeaders({ ':status': 'a' });
|
buildNgHeaderString({ ':status': 'a' });
|
||||||
mapToHeaders({ ':path': 'a' });
|
buildNgHeaderString({ ':path': 'a' });
|
||||||
mapToHeaders({ ':authority': 'a' });
|
buildNgHeaderString({ ':authority': 'a' });
|
||||||
mapToHeaders({ ':scheme': 'a' });
|
buildNgHeaderString({ ':scheme': 'a' });
|
||||||
mapToHeaders({ ':method': 'a' });
|
buildNgHeaderString({ ':method': 'a' });
|
||||||
|
|
||||||
assert.throws(() => mapToHeaders({ ':foo': 'a' }), {
|
assert.throws(() => buildNgHeaderString({ ':foo': 'a' }), {
|
||||||
code: 'ERR_HTTP2_INVALID_PSEUDOHEADER',
|
code: 'ERR_HTTP2_INVALID_PSEUDOHEADER',
|
||||||
name: 'TypeError',
|
name: 'TypeError',
|
||||||
message: '":foo" is an invalid pseudoheader or is used incorrectly'
|
message: '":foo" is an invalid pseudoheader or is used incorrectly'
|
||||||
|
@ -10,7 +10,7 @@ if (!common.hasCrypto)
|
|||||||
const assert = require('assert');
|
const assert = require('assert');
|
||||||
const {
|
const {
|
||||||
getAuthority,
|
getAuthority,
|
||||||
mapToHeaders,
|
buildNgHeaderString,
|
||||||
toHeaderObject
|
toHeaderObject
|
||||||
} = require('internal/http2/util');
|
} = require('internal/http2/util');
|
||||||
const { sensitiveHeaders } = require('http2');
|
const { sensitiveHeaders } = require('http2');
|
||||||
@ -106,7 +106,7 @@ const {
|
|||||||
};
|
};
|
||||||
|
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
mapToHeaders(headers),
|
buildNgHeaderString(headers),
|
||||||
[ [ ':path', 'abc\0', ':status', '200\0', 'abc', '1\0', 'xyz', '1\0',
|
[ [ ':path', 'abc\0', ':status', '200\0', 'abc', '1\0', 'xyz', '1\0',
|
||||||
'xyz', '2\0', 'xyz', '3\0', 'xyz', '4\0', 'bar', '1\0', '' ].join('\0'),
|
'xyz', '2\0', 'xyz', '3\0', 'xyz', '4\0', 'bar', '1\0', '' ].join('\0'),
|
||||||
8 ]
|
8 ]
|
||||||
@ -123,7 +123,7 @@ const {
|
|||||||
};
|
};
|
||||||
|
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
mapToHeaders(headers),
|
buildNgHeaderString(headers),
|
||||||
[ [ ':status', '200\0', ':path', 'abc\0', 'abc', '1\0', 'xyz', '1\0',
|
[ [ ':status', '200\0', ':path', 'abc\0', 'abc', '1\0', 'xyz', '1\0',
|
||||||
'xyz', '2\0', 'xyz', '3\0', 'xyz', '4\0', '' ].join('\0'), 7 ]
|
'xyz', '2\0', 'xyz', '3\0', 'xyz', '4\0', '' ].join('\0'), 7 ]
|
||||||
);
|
);
|
||||||
@ -140,7 +140,7 @@ const {
|
|||||||
};
|
};
|
||||||
|
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
mapToHeaders(headers),
|
buildNgHeaderString(headers),
|
||||||
[ [ ':status', '200\0', ':path', 'abc\0', 'abc', '1\0', 'xyz', '1\0',
|
[ [ ':status', '200\0', ':path', 'abc\0', 'abc', '1\0', 'xyz', '1\0',
|
||||||
'xyz', '2\0', 'xyz', '3\0', 'xyz', '4\0', '' ].join('\0'), 7 ]
|
'xyz', '2\0', 'xyz', '3\0', 'xyz', '4\0', '' ].join('\0'), 7 ]
|
||||||
);
|
);
|
||||||
@ -156,7 +156,7 @@ const {
|
|||||||
headers[':path'] = 'abc';
|
headers[':path'] = 'abc';
|
||||||
|
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
mapToHeaders(headers),
|
buildNgHeaderString(headers),
|
||||||
[ [ ':status', '200\0', ':path', 'abc\0', 'xyz', '1\0', 'xyz', '2\0',
|
[ [ ':status', '200\0', ':path', 'abc\0', 'xyz', '1\0', 'xyz', '2\0',
|
||||||
'xyz', '3\0', 'xyz', '4\0', '' ].join('\0'), 6 ]
|
'xyz', '3\0', 'xyz', '4\0', '' ].join('\0'), 6 ]
|
||||||
);
|
);
|
||||||
@ -169,7 +169,7 @@ const {
|
|||||||
'set-cookie': ['foo=bar']
|
'set-cookie': ['foo=bar']
|
||||||
};
|
};
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
mapToHeaders(headers),
|
buildNgHeaderString(headers),
|
||||||
[ [ 'set-cookie', 'foo=bar\0', '' ].join('\0'), 1 ]
|
[ [ 'set-cookie', 'foo=bar\0', '' ].join('\0'), 1 ]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -181,7 +181,7 @@ const {
|
|||||||
':statuS': 204,
|
':statuS': 204,
|
||||||
};
|
};
|
||||||
|
|
||||||
assert.throws(() => mapToHeaders(headers), {
|
assert.throws(() => buildNgHeaderString(headers), {
|
||||||
code: 'ERR_HTTP2_HEADER_SINGLE_VALUE',
|
code: 'ERR_HTTP2_HEADER_SINGLE_VALUE',
|
||||||
name: 'TypeError',
|
name: 'TypeError',
|
||||||
message: 'Header field ":status" must only have a single value'
|
message: 'Header field ":status" must only have a single value'
|
||||||
@ -199,7 +199,7 @@ const {
|
|||||||
};
|
};
|
||||||
|
|
||||||
assert.deepStrictEqual(
|
assert.deepStrictEqual(
|
||||||
mapToHeaders(headers),
|
buildNgHeaderString(headers),
|
||||||
[ ':status\x00200\x00\x00:path\x00abc\x00\x00abc\x001\x00\x00' +
|
[ ':status\x00200\x00\x00:path\x00abc\x00\x00abc\x001\x00\x00' +
|
||||||
'xyz\x001\x00\x01xyz\x002\x00\x01xyz\x003\x00\x01xyz\x004\x00\x01', 7 ]
|
'xyz\x001\x00\x01xyz\x002\x00\x01xyz\x003\x00\x01xyz\x004\x00\x01', 7 ]
|
||||||
);
|
);
|
||||||
@ -248,7 +248,7 @@ const {
|
|||||||
HTTP2_HEADER_X_CONTENT_TYPE_OPTIONS,
|
HTTP2_HEADER_X_CONTENT_TYPE_OPTIONS,
|
||||||
].forEach((name) => {
|
].forEach((name) => {
|
||||||
const msg = `Header field "${name}" must only have a single value`;
|
const msg = `Header field "${name}" must only have a single value`;
|
||||||
assert.throws(() => mapToHeaders({ [name]: [1, 2, 3] }), {
|
assert.throws(() => buildNgHeaderString({ [name]: [1, 2, 3] }), {
|
||||||
code: 'ERR_HTTP2_HEADER_SINGLE_VALUE',
|
code: 'ERR_HTTP2_HEADER_SINGLE_VALUE',
|
||||||
message: msg
|
message: msg
|
||||||
});
|
});
|
||||||
@ -285,7 +285,7 @@ const {
|
|||||||
HTTP2_HEADER_WWW_AUTHENTICATE,
|
HTTP2_HEADER_WWW_AUTHENTICATE,
|
||||||
HTTP2_HEADER_X_FRAME_OPTIONS,
|
HTTP2_HEADER_X_FRAME_OPTIONS,
|
||||||
].forEach((name) => {
|
].forEach((name) => {
|
||||||
assert(!(mapToHeaders({ [name]: [1, 2, 3] }) instanceof Error), name);
|
assert(!(buildNgHeaderString({ [name]: [1, 2, 3] }) instanceof Error), name);
|
||||||
});
|
});
|
||||||
|
|
||||||
[
|
[
|
||||||
@ -304,7 +304,7 @@ const {
|
|||||||
'Proxy-Connection',
|
'Proxy-Connection',
|
||||||
'Keep-Alive',
|
'Keep-Alive',
|
||||||
].forEach((name) => {
|
].forEach((name) => {
|
||||||
assert.throws(() => mapToHeaders({ [name]: 'abc' }), {
|
assert.throws(() => buildNgHeaderString({ [name]: 'abc' }), {
|
||||||
code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS',
|
code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS',
|
||||||
name: 'TypeError',
|
name: 'TypeError',
|
||||||
message: 'HTTP/1 Connection specific headers are forbidden: ' +
|
message: 'HTTP/1 Connection specific headers are forbidden: ' +
|
||||||
@ -312,7 +312,7 @@ const {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
assert.throws(() => mapToHeaders({ [HTTP2_HEADER_TE]: ['abc'] }), {
|
assert.throws(() => buildNgHeaderString({ [HTTP2_HEADER_TE]: ['abc'] }), {
|
||||||
code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS',
|
code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS',
|
||||||
name: 'TypeError',
|
name: 'TypeError',
|
||||||
message: 'HTTP/1 Connection specific headers are forbidden: ' +
|
message: 'HTTP/1 Connection specific headers are forbidden: ' +
|
||||||
@ -320,7 +320,7 @@ assert.throws(() => mapToHeaders({ [HTTP2_HEADER_TE]: ['abc'] }), {
|
|||||||
});
|
});
|
||||||
|
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => mapToHeaders({ [HTTP2_HEADER_TE]: ['abc', 'trailers'] }), {
|
() => buildNgHeaderString({ [HTTP2_HEADER_TE]: ['abc', 'trailers'] }), {
|
||||||
code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS',
|
code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS',
|
||||||
name: 'TypeError',
|
name: 'TypeError',
|
||||||
message: 'HTTP/1 Connection specific headers are forbidden: ' +
|
message: 'HTTP/1 Connection specific headers are forbidden: ' +
|
||||||
@ -328,13 +328,13 @@ assert.throws(
|
|||||||
});
|
});
|
||||||
|
|
||||||
// These should not throw
|
// These should not throw
|
||||||
mapToHeaders({ te: 'trailers' });
|
buildNgHeaderString({ te: 'trailers' });
|
||||||
mapToHeaders({ te: ['trailers'] });
|
buildNgHeaderString({ te: ['trailers'] });
|
||||||
|
|
||||||
// HTTP/2 encourages use of Host instead of :authority when converting
|
// HTTP/2 encourages use of Host instead of :authority when converting
|
||||||
// from HTTP/1 to HTTP/2, so we no longer disallow it.
|
// from HTTP/1 to HTTP/2, so we no longer disallow it.
|
||||||
// Refs: https://github.com/nodejs/node/issues/29858
|
// Refs: https://github.com/nodejs/node/issues/29858
|
||||||
mapToHeaders({ [HTTP2_HEADER_HOST]: 'abc' });
|
buildNgHeaderString({ [HTTP2_HEADER_HOST]: 'abc' });
|
||||||
|
|
||||||
// If both are present, the latter has priority
|
// If both are present, the latter has priority
|
||||||
assert.strictEqual(getAuthority({
|
assert.strictEqual(getAuthority({
|
||||||
|
Loading…
x
Reference in New Issue
Block a user