diff --git a/lib/internal/http2/util.js b/lib/internal/http2/util.js index cffd509ed3..fcc2d87b73 100644 --- a/lib/internal/http2/util.js +++ b/lib/internal/http2/util.js @@ -43,6 +43,47 @@ const { HTTP2_HEADER_HOST } = binding.constants; +const kValidPseudoHeaders = new Set([ + HTTP2_HEADER_STATUS, + HTTP2_HEADER_METHOD, + HTTP2_HEADER_AUTHORITY, + HTTP2_HEADER_SCHEME, + HTTP2_HEADER_PATH +]); + +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_MAX_FORWARDS, + HTTP2_HEADER_PROXY_AUTHORIZATION, + HTTP2_HEADER_RANGE, + HTTP2_HEADER_REFERER, + HTTP2_HEADER_RETRY_AFTER, + HTTP2_HEADER_USER_AGENT +]); + // 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 @@ -180,61 +221,6 @@ function isIllegalConnectionSpecificHeader(name, value) { } } -function assertIllegalConnectionSpecificHeader(name, value, ctor) { - if (isIllegalConnectionSpecificHeader(name, value)) { - var err = new errors.Error('ERR_HTTP2_INVALID_CONNECTION_HEADERS'); - Error.captureStackTrace(err, ctor); - throw err; - } -} - -function assertValidPseudoHeader(name) { - switch (name) { - case HTTP2_HEADER_STATUS: - case HTTP2_HEADER_METHOD: - case HTTP2_HEADER_AUTHORITY: - case HTTP2_HEADER_SCHEME: - case HTTP2_HEADER_PATH: - return; - default: - throw new errors.Error('ERR_HTTP2_INVALID_PSEUDOHEADER', name); - } -} - -// TODO(jasnell): Find a faster way to do this -function assertSingleValueHeader(name) { - switch (name) { - case HTTP2_HEADER_AGE: - case HTTP2_HEADER_AUTHORIZATION: - case HTTP2_HEADER_CONTENT_ENCODING: - case HTTP2_HEADER_CONTENT_LANGUAGE: - case HTTP2_HEADER_CONTENT_LENGTH: - case HTTP2_HEADER_CONTENT_LOCATION: - case HTTP2_HEADER_CONTENT_MD5: - case HTTP2_HEADER_CONTENT_RANGE: - case HTTP2_HEADER_CONTENT_TYPE: - case HTTP2_HEADER_DATE: - case HTTP2_HEADER_ETAG: - case HTTP2_HEADER_EXPIRES: - case HTTP2_HEADER_FROM: - case HTTP2_HEADER_IF_MATCH: - case HTTP2_HEADER_IF_MODIFIED_SINCE: - case HTTP2_HEADER_IF_NONE_MATCH: - case HTTP2_HEADER_IF_RANGE: - case HTTP2_HEADER_IF_UNMODIFIED_SINCE: - case HTTP2_HEADER_LAST_MODIFIED: - case HTTP2_HEADER_MAX_FORWARDS: - case HTTP2_HEADER_PROXY_AUTHORIZATION: - case HTTP2_HEADER_RANGE: - case HTTP2_HEADER_REFERER: - case HTTP2_HEADER_RETRY_AFTER: - case HTTP2_HEADER_USER_AGENT: - throw new errors.Error('ERR_HTTP2_HEADER_SINGLE_VALUE', name); - default: - break; - } -} - function mapToHeaders(map) { var ret = []; var keys = Object.keys(map); @@ -244,21 +230,28 @@ function mapToHeaders(map) { var val; if (typeof key === 'symbol' || value === undefined || !key) continue; - var isArray = Array.isArray(value); - if (key[0] === ':') { - assertValidPseudoHeader(key); - if (isArray) { - if (value.length > 1) - throw new errors.Error('ERR_HTTP2_HEADER_SINGLE_VALUE', key); - value = value[0]; + 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)) + throw new errors.Error('ERR_HTTP2_HEADER_SINGLE_VALUE', key); } + } + if (key[0] === ':') { + if (!kValidPseudoHeaders.has(key)) + throw new errors.Error('ERR_HTTP2_INVALID_PSEUDOHEADER', key); ret.unshift([key, String(value)]); } else { - key = String(key).toLowerCase(); - assertIllegalConnectionSpecificHeader(key, value, mapToHeaders); + if (isIllegalConnectionSpecificHeader(key, value)) + throw new errors.Error('ERR_HTTP2_INVALID_CONNECTION_HEADERS'); if (isArray) { - if (value.length > 1) - assertSingleValueHeader(key); for (var k = 0; k < value.length; k++) { val = String(value[k]); ret.push([key, val]); @@ -301,7 +294,6 @@ function assertIsObject(value, name, types) { module.exports = { assertIsObject, - isIllegalConnectionSpecificHeader, emitErrorIfNecessary, getDefaultSettings, getSessionState, diff --git a/test/parallel/test-http2-util-headers-list.js b/test/parallel/test-http2-util-headers-list.js index e6e9fb87b1..e5b2ff3942 100644 --- a/test/parallel/test-http2-util-headers-list.js +++ b/test/parallel/test-http2-util-headers-list.js @@ -76,7 +76,9 @@ const { 'abc': 1, ':status': 200, ':path': 'abc', - 'xyz': [1, '2', { toString() { return '3'; } }, 4] + 'xyz': [1, '2', { toString() { return '3'; } }, 4], + 'foo': [], + 'BAR': [1] }; assert.deepStrictEqual(mapToHeaders(headers), [ @@ -86,7 +88,8 @@ const { [ 'xyz', '1' ], [ 'xyz', '2' ], [ 'xyz', '3' ], - [ 'xyz', '4' ] + [ 'xyz', '4' ], + [ 'bar', '1' ] ]); } @@ -94,7 +97,8 @@ const { const headers = { 'abc': 1, ':path': 'abc', - ':status': 200, + ':status': [200], + ':authority': [], 'xyz': [1, 2, 3, 4] };