348 lines
9.1 KiB
JavaScript
348 lines
9.1 KiB
JavaScript
// Flags: --expose-internals
|
|
'use strict';
|
|
|
|
// Tests the internal utility functions that are used to prepare headers
|
|
// to pass to the internal binding layer and to build a header object.
|
|
|
|
const common = require('../common');
|
|
if (!common.hasCrypto)
|
|
common.skip('missing crypto');
|
|
const assert = require('assert');
|
|
const { mapToHeaders, toHeaderObject } = require('internal/http2/util');
|
|
const { internalBinding } = require('internal/test/binding');
|
|
const {
|
|
HTTP2_HEADER_STATUS,
|
|
HTTP2_HEADER_METHOD,
|
|
HTTP2_HEADER_AUTHORITY,
|
|
HTTP2_HEADER_SCHEME,
|
|
HTTP2_HEADER_PATH,
|
|
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_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,
|
|
|
|
HTTP2_HEADER_ACCEPT_CHARSET,
|
|
HTTP2_HEADER_ACCEPT_ENCODING,
|
|
HTTP2_HEADER_ACCEPT_LANGUAGE,
|
|
HTTP2_HEADER_ACCEPT_RANGES,
|
|
HTTP2_HEADER_ACCEPT,
|
|
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS,
|
|
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS,
|
|
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
HTTP2_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS,
|
|
HTTP2_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
|
|
HTTP2_HEADER_ALLOW,
|
|
HTTP2_HEADER_CACHE_CONTROL,
|
|
HTTP2_HEADER_CONTENT_DISPOSITION,
|
|
HTTP2_HEADER_COOKIE,
|
|
HTTP2_HEADER_EXPECT,
|
|
HTTP2_HEADER_FORWARDED,
|
|
HTTP2_HEADER_LINK,
|
|
HTTP2_HEADER_PREFER,
|
|
HTTP2_HEADER_PROXY_AUTHENTICATE,
|
|
HTTP2_HEADER_REFRESH,
|
|
HTTP2_HEADER_SERVER,
|
|
HTTP2_HEADER_SET_COOKIE,
|
|
HTTP2_HEADER_STRICT_TRANSPORT_SECURITY,
|
|
HTTP2_HEADER_TRAILER,
|
|
HTTP2_HEADER_VARY,
|
|
HTTP2_HEADER_VIA,
|
|
HTTP2_HEADER_WARNING,
|
|
HTTP2_HEADER_WWW_AUTHENTICATE,
|
|
HTTP2_HEADER_X_FRAME_OPTIONS,
|
|
|
|
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
|
|
} = internalBinding('http2').constants;
|
|
|
|
{
|
|
const headers = {
|
|
'abc': 1,
|
|
':status': 200,
|
|
':path': 'abc',
|
|
'xyz': [1, '2', { toString() { return '3'; } }, 4],
|
|
'foo': [],
|
|
'BAR': [1]
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
mapToHeaders(headers),
|
|
[ [ ':path', 'abc', ':status', '200', 'abc', '1', 'xyz', '1', 'xyz', '2',
|
|
'xyz', '3', 'xyz', '4', 'bar', '1', '' ].join('\0'), 8 ]
|
|
);
|
|
}
|
|
|
|
{
|
|
const headers = {
|
|
'abc': 1,
|
|
':path': 'abc',
|
|
':status': [200],
|
|
':authority': [],
|
|
'xyz': [1, 2, 3, 4]
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
mapToHeaders(headers),
|
|
[ [ ':status', '200', ':path', 'abc', 'abc', '1', 'xyz', '1', 'xyz', '2',
|
|
'xyz', '3', 'xyz', '4', '' ].join('\0'), 7 ]
|
|
);
|
|
}
|
|
|
|
{
|
|
const headers = {
|
|
'abc': 1,
|
|
':path': 'abc',
|
|
'xyz': [1, 2, 3, 4],
|
|
'': 1,
|
|
':status': 200,
|
|
[Symbol('test')]: 1 // Symbol keys are ignored
|
|
};
|
|
|
|
assert.deepStrictEqual(
|
|
mapToHeaders(headers),
|
|
[ [ ':status', '200', ':path', 'abc', 'abc', '1', 'xyz', '1', 'xyz', '2',
|
|
'xyz', '3', 'xyz', '4', '' ].join('\0'), 7 ]
|
|
);
|
|
}
|
|
|
|
{
|
|
// Only own properties are used
|
|
const base = { 'abc': 1 };
|
|
const headers = Object.create(base);
|
|
headers[':path'] = 'abc';
|
|
headers.xyz = [1, 2, 3, 4];
|
|
headers.foo = [];
|
|
headers[':status'] = 200;
|
|
|
|
assert.deepStrictEqual(
|
|
mapToHeaders(headers),
|
|
[ [ ':status', '200', ':path', 'abc', 'xyz', '1', 'xyz', '2', 'xyz', '3',
|
|
'xyz', '4', '' ].join('\0'), 6 ]
|
|
);
|
|
}
|
|
|
|
{
|
|
// Arrays containing a single set-cookie value are handled correctly
|
|
// (https://github.com/nodejs/node/issues/16452)
|
|
const headers = {
|
|
'set-cookie': ['foo=bar']
|
|
};
|
|
assert.deepStrictEqual(
|
|
mapToHeaders(headers),
|
|
[ [ 'set-cookie', 'foo=bar', '' ].join('\0'), 1 ]
|
|
);
|
|
}
|
|
|
|
{
|
|
// pseudo-headers are only allowed a single value
|
|
const headers = {
|
|
':status': 200,
|
|
':statuS': 204,
|
|
};
|
|
|
|
assert.throws(() => mapToHeaders(headers), {
|
|
code: 'ERR_HTTP2_HEADER_SINGLE_VALUE',
|
|
name: 'TypeError',
|
|
message: 'Header field ":status" must only have a single value'
|
|
});
|
|
}
|
|
|
|
// The following are not allowed to have multiple values
|
|
[
|
|
HTTP2_HEADER_STATUS,
|
|
HTTP2_HEADER_METHOD,
|
|
HTTP2_HEADER_AUTHORITY,
|
|
HTTP2_HEADER_SCHEME,
|
|
HTTP2_HEADER_PATH,
|
|
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_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
|
|
].forEach((name) => {
|
|
const msg = `Header field "${name}" must only have a single value`;
|
|
assert.throws(() => mapToHeaders({ [name]: [1, 2, 3] }), {
|
|
code: 'ERR_HTTP2_HEADER_SINGLE_VALUE',
|
|
message: msg
|
|
});
|
|
});
|
|
|
|
[
|
|
HTTP2_HEADER_ACCEPT_CHARSET,
|
|
HTTP2_HEADER_ACCEPT_ENCODING,
|
|
HTTP2_HEADER_ACCEPT_LANGUAGE,
|
|
HTTP2_HEADER_ACCEPT_RANGES,
|
|
HTTP2_HEADER_ACCEPT,
|
|
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS,
|
|
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS,
|
|
HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN,
|
|
HTTP2_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS,
|
|
HTTP2_HEADER_ACCESS_CONTROL_REQUEST_HEADERS,
|
|
HTTP2_HEADER_ALLOW,
|
|
HTTP2_HEADER_CACHE_CONTROL,
|
|
HTTP2_HEADER_CONTENT_DISPOSITION,
|
|
HTTP2_HEADER_COOKIE,
|
|
HTTP2_HEADER_EXPECT,
|
|
HTTP2_HEADER_FORWARDED,
|
|
HTTP2_HEADER_LINK,
|
|
HTTP2_HEADER_PREFER,
|
|
HTTP2_HEADER_PROXY_AUTHENTICATE,
|
|
HTTP2_HEADER_REFRESH,
|
|
HTTP2_HEADER_SERVER,
|
|
HTTP2_HEADER_SET_COOKIE,
|
|
HTTP2_HEADER_STRICT_TRANSPORT_SECURITY,
|
|
HTTP2_HEADER_TRAILER,
|
|
HTTP2_HEADER_VARY,
|
|
HTTP2_HEADER_VIA,
|
|
HTTP2_HEADER_WARNING,
|
|
HTTP2_HEADER_WWW_AUTHENTICATE,
|
|
HTTP2_HEADER_X_FRAME_OPTIONS
|
|
].forEach((name) => {
|
|
assert(!(mapToHeaders({ [name]: [1, 2, 3] }) instanceof Error), name);
|
|
});
|
|
|
|
[
|
|
HTTP2_HEADER_CONNECTION,
|
|
HTTP2_HEADER_UPGRADE,
|
|
HTTP2_HEADER_HTTP2_SETTINGS,
|
|
HTTP2_HEADER_TE,
|
|
HTTP2_HEADER_TRANSFER_ENCODING,
|
|
HTTP2_HEADER_HOST,
|
|
HTTP2_HEADER_PROXY_CONNECTION,
|
|
HTTP2_HEADER_KEEP_ALIVE,
|
|
'Connection',
|
|
'Upgrade',
|
|
'HTTP2-Settings',
|
|
'TE',
|
|
'Transfer-Encoding',
|
|
'Proxy-Connection',
|
|
'Keep-Alive'
|
|
].forEach((name) => {
|
|
assert.throws(() => mapToHeaders({ [name]: 'abc' }), {
|
|
code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS',
|
|
name: 'TypeError',
|
|
message: 'HTTP/1 Connection specific headers are forbidden: ' +
|
|
`"${name.toLowerCase()}"`
|
|
});
|
|
});
|
|
|
|
assert.throws(() => mapToHeaders({ [HTTP2_HEADER_TE]: ['abc'] }), {
|
|
code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS',
|
|
name: 'TypeError',
|
|
message: 'HTTP/1 Connection specific headers are forbidden: ' +
|
|
`"${HTTP2_HEADER_TE}"`
|
|
});
|
|
|
|
assert.throws(
|
|
() => mapToHeaders({ [HTTP2_HEADER_TE]: ['abc', 'trailers'] }), {
|
|
code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS',
|
|
name: 'TypeError',
|
|
message: 'HTTP/1 Connection specific headers are forbidden: ' +
|
|
`"${HTTP2_HEADER_TE}"`
|
|
});
|
|
|
|
// These should not throw
|
|
mapToHeaders({ te: 'trailers' });
|
|
mapToHeaders({ te: ['trailers'] });
|
|
|
|
|
|
{
|
|
const rawHeaders = [
|
|
':status', '200',
|
|
'cookie', 'foo',
|
|
'set-cookie', 'sc1',
|
|
'age', '10',
|
|
'x-multi', 'first'
|
|
];
|
|
const headers = toHeaderObject(rawHeaders);
|
|
assert.strictEqual(headers[':status'], 200);
|
|
assert.strictEqual(headers.cookie, 'foo');
|
|
assert.deepStrictEqual(headers['set-cookie'], ['sc1']);
|
|
assert.strictEqual(headers.age, '10');
|
|
assert.strictEqual(headers['x-multi'], 'first');
|
|
}
|
|
|
|
{
|
|
const rawHeaders = [
|
|
':status', '200',
|
|
':status', '400',
|
|
'cookie', 'foo',
|
|
'cookie', 'bar',
|
|
'set-cookie', 'sc1',
|
|
'set-cookie', 'sc2',
|
|
'age', '10',
|
|
'age', '20',
|
|
'x-multi', 'first',
|
|
'x-multi', 'second'
|
|
];
|
|
const headers = toHeaderObject(rawHeaders);
|
|
assert.strictEqual(headers[':status'], 200);
|
|
assert.strictEqual(headers.cookie, 'foo; bar');
|
|
assert.deepStrictEqual(headers['set-cookie'], ['sc1', 'sc2']);
|
|
assert.strictEqual(headers.age, '10');
|
|
assert.strictEqual(headers['x-multi'], 'first, second');
|
|
}
|