http2: simplify mapToHeaders, stricter validation

No longer check whether key is a symbol as Object.keys does not
return symbols. No longer convert key to string as it is always
a string. Validate that only one value is passed for each
pseudo-header.

Extend illegal connection header message to include the name of
the problematic header.

Extend tests to cover this behaviour.

PR-URL: https://github.com/nodejs/node/pull/16575
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Anatoli Papirovski 2017-10-28 14:04:16 -04:00
parent ca82e3088d
commit 980ebd2d35
No known key found for this signature in database
GPG Key ID: 614E2E1ABEB4B2C0
4 changed files with 38 additions and 22 deletions

View File

@ -201,7 +201,7 @@ E('ERR_HTTP2_INFO_HEADERS_AFTER_RESPOND',
E('ERR_HTTP2_INFO_STATUS_NOT_ALLOWED', E('ERR_HTTP2_INFO_STATUS_NOT_ALLOWED',
'Informational status codes cannot be used'); 'Informational status codes cannot be used');
E('ERR_HTTP2_INVALID_CONNECTION_HEADERS', E('ERR_HTTP2_INVALID_CONNECTION_HEADERS',
'HTTP/1 Connection specific headers are forbidden'); 'HTTP/1 Connection specific headers are forbidden: "%s"');
E('ERR_HTTP2_INVALID_HEADER_VALUE', 'Value must not be undefined or null'); E('ERR_HTTP2_INVALID_HEADER_VALUE', 'Value must not be undefined or null');
E('ERR_HTTP2_INVALID_INFO_STATUS', E('ERR_HTTP2_INVALID_INFO_STATUS',
(code) => `Invalid informational status code: ${code}`); (code) => `Invalid informational status code: ${code}`);

View File

@ -399,10 +399,10 @@ function mapToHeaders(map,
for (var i = 0; i < keys.length; i++) { for (var i = 0; i < keys.length; i++) {
let key = keys[i]; let key = keys[i];
let value = map[key]; let value = map[key];
let val; if (value === undefined || key === '')
if (typeof key === 'symbol' || value === undefined || !key)
continue; continue;
key = String(key).toLowerCase(); key = key.toLowerCase();
const isSingleValueHeader = kSingleValueHeaders.has(key);
let isArray = Array.isArray(value); let isArray = Array.isArray(value);
if (isArray) { if (isArray) {
switch (value.length) { switch (value.length) {
@ -413,34 +413,35 @@ function mapToHeaders(map,
isArray = false; isArray = false;
break; break;
default: default:
if (kSingleValueHeaders.has(key)) if (isSingleValueHeader)
return new errors.Error('ERR_HTTP2_HEADER_SINGLE_VALUE', key); return new errors.Error('ERR_HTTP2_HEADER_SINGLE_VALUE', key);
} }
} else {
value = String(value);
}
if (isSingleValueHeader) {
if (singles.has(key))
return new errors.Error('ERR_HTTP2_HEADER_SINGLE_VALUE', key);
singles.add(key);
} }
if (key[0] === ':') { if (key[0] === ':') {
const err = assertValuePseudoHeader(key); const err = assertValuePseudoHeader(key);
if (err !== undefined) if (err !== undefined)
return err; return err;
ret = `${key}\0${String(value)}\0${ret}`; ret = `${key}\0${value}\0${ret}`;
count++; count++;
} else { } 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)) { if (isIllegalConnectionSpecificHeader(key, value)) {
return new errors.Error('ERR_HTTP2_INVALID_CONNECTION_HEADERS'); return new errors.Error('ERR_HTTP2_INVALID_CONNECTION_HEADERS', key);
} }
if (isArray) { if (isArray) {
for (var k = 0; k < value.length; k++) { for (var k = 0; k < value.length; k++) {
val = String(value[k]); const val = String(value[k]);
ret += `${key}\0${val}\0`; ret += `${key}\0${val}\0`;
} }
count += value.length; count += value.length;
} else { } else {
val = String(value); ret += `${key}\0${value}\0`;
ret += `${key}\0${val}\0`;
count++; count++;
} }
} }

View File

@ -31,7 +31,7 @@ server.on('stream', common.mustCall((stream, headers) => {
() => stream.pushStream({ 'connection': 'test' }, {}, () => {}), () => stream.pushStream({ 'connection': 'test' }, {}, () => {}),
{ {
code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS', code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS',
message: 'HTTP/1 Connection specific headers are forbidden' message: 'HTTP/1 Connection specific headers are forbidden: "connection"'
} }
); );

View File

@ -158,7 +158,7 @@ const {
// Arrays containing a single set-cookie value are handled correctly // Arrays containing a single set-cookie value are handled correctly
// (https://github.com/nodejs/node/issues/16452) // (https://github.com/nodejs/node/issues/16452)
const headers = { const headers = {
'set-cookie': 'foo=bar' 'set-cookie': ['foo=bar']
}; };
assert.deepStrictEqual( assert.deepStrictEqual(
mapToHeaders(headers), mapToHeaders(headers),
@ -166,6 +166,20 @@ const {
); );
} }
{
// pseudo-headers are only allowed a single value
const headers = {
':status': 200,
':statuS': 204,
};
common.expectsError({
code: 'ERR_HTTP2_HEADER_SINGLE_VALUE',
type: Error,
message: 'Header field ":status" must have only a single value'
})(mapToHeaders(headers));
}
// The following are not allowed to have multiple values // The following are not allowed to have multiple values
[ [
HTTP2_HEADER_STATUS, HTTP2_HEADER_STATUS,
@ -248,8 +262,6 @@ const {
assert(!(mapToHeaders({ [name]: [1, 2, 3] }) instanceof Error), name); assert(!(mapToHeaders({ [name]: [1, 2, 3] }) instanceof Error), name);
}); });
const regex =
/^HTTP\/1 Connection specific headers are forbidden$/;
[ [
HTTP2_HEADER_CONNECTION, HTTP2_HEADER_CONNECTION,
HTTP2_HEADER_UPGRADE, HTTP2_HEADER_UPGRADE,
@ -269,18 +281,21 @@ const regex =
].forEach((name) => { ].forEach((name) => {
common.expectsError({ common.expectsError({
code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS', code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS',
message: regex message: 'HTTP/1 Connection specific headers are forbidden: ' +
`"${name.toLowerCase()}"`
})(mapToHeaders({ [name]: 'abc' })); })(mapToHeaders({ [name]: 'abc' }));
}); });
common.expectsError({ common.expectsError({
code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS', code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS',
message: regex message: 'HTTP/1 Connection specific headers are forbidden: ' +
`"${HTTP2_HEADER_TE}"`
})(mapToHeaders({ [HTTP2_HEADER_TE]: ['abc'] })); })(mapToHeaders({ [HTTP2_HEADER_TE]: ['abc'] }));
common.expectsError({ common.expectsError({
code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS', code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS',
message: regex message: 'HTTP/1 Connection specific headers are forbidden: ' +
`"${HTTP2_HEADER_TE}"`
})(mapToHeaders({ [HTTP2_HEADER_TE]: ['abc', 'trailers'] })); })(mapToHeaders({ [HTTP2_HEADER_TE]: ['abc', 'trailers'] }));
assert(!(mapToHeaders({ te: 'trailers' }) instanceof Error)); assert(!(mapToHeaders({ te: 'trailers' }) instanceof Error));