1// Flags: --expose-internals 2'use strict'; 3 4// Tests the internal utility functions that are used to prepare headers 5// to pass to the internal binding layer and to build a header object. 6 7const common = require('../common'); 8if (!common.hasCrypto) 9 common.skip('missing crypto'); 10const assert = require('assert'); 11const { 12 getAuthority, 13 mapToHeaders, 14 toHeaderObject 15} = require('internal/http2/util'); 16const { sensitiveHeaders } = require('http2'); 17const { internalBinding } = require('internal/test/binding'); 18const { 19 HTTP2_HEADER_STATUS, 20 HTTP2_HEADER_METHOD, 21 HTTP2_HEADER_AUTHORITY, 22 HTTP2_HEADER_SCHEME, 23 HTTP2_HEADER_PATH, 24 HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS, 25 HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE, 26 HTTP2_HEADER_ACCESS_CONTROL_REQUEST_METHOD, 27 HTTP2_HEADER_AGE, 28 HTTP2_HEADER_AUTHORIZATION, 29 HTTP2_HEADER_CONTENT_ENCODING, 30 HTTP2_HEADER_CONTENT_LANGUAGE, 31 HTTP2_HEADER_CONTENT_LENGTH, 32 HTTP2_HEADER_CONTENT_LOCATION, 33 HTTP2_HEADER_CONTENT_MD5, 34 HTTP2_HEADER_CONTENT_RANGE, 35 HTTP2_HEADER_CONTENT_TYPE, 36 HTTP2_HEADER_DATE, 37 HTTP2_HEADER_DNT, 38 HTTP2_HEADER_ETAG, 39 HTTP2_HEADER_EXPIRES, 40 HTTP2_HEADER_FROM, 41 HTTP2_HEADER_HOST, 42 HTTP2_HEADER_IF_MATCH, 43 HTTP2_HEADER_IF_MODIFIED_SINCE, 44 HTTP2_HEADER_IF_NONE_MATCH, 45 HTTP2_HEADER_IF_RANGE, 46 HTTP2_HEADER_IF_UNMODIFIED_SINCE, 47 HTTP2_HEADER_LAST_MODIFIED, 48 HTTP2_HEADER_LOCATION, 49 HTTP2_HEADER_MAX_FORWARDS, 50 HTTP2_HEADER_PROXY_AUTHORIZATION, 51 HTTP2_HEADER_RANGE, 52 HTTP2_HEADER_REFERER, 53 HTTP2_HEADER_RETRY_AFTER, 54 HTTP2_HEADER_TK, 55 HTTP2_HEADER_UPGRADE_INSECURE_REQUESTS, 56 HTTP2_HEADER_USER_AGENT, 57 HTTP2_HEADER_X_CONTENT_TYPE_OPTIONS, 58 59 HTTP2_HEADER_ACCEPT_CHARSET, 60 HTTP2_HEADER_ACCEPT_ENCODING, 61 HTTP2_HEADER_ACCEPT_LANGUAGE, 62 HTTP2_HEADER_ACCEPT_RANGES, 63 HTTP2_HEADER_ACCEPT, 64 HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, 65 HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS, 66 HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, 67 HTTP2_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS, 68 HTTP2_HEADER_ACCESS_CONTROL_REQUEST_HEADERS, 69 HTTP2_HEADER_ALLOW, 70 HTTP2_HEADER_CACHE_CONTROL, 71 HTTP2_HEADER_CONTENT_DISPOSITION, 72 HTTP2_HEADER_COOKIE, 73 HTTP2_HEADER_EXPECT, 74 HTTP2_HEADER_FORWARDED, 75 HTTP2_HEADER_LINK, 76 HTTP2_HEADER_PREFER, 77 HTTP2_HEADER_PROXY_AUTHENTICATE, 78 HTTP2_HEADER_REFRESH, 79 HTTP2_HEADER_SERVER, 80 HTTP2_HEADER_SET_COOKIE, 81 HTTP2_HEADER_STRICT_TRANSPORT_SECURITY, 82 HTTP2_HEADER_TRAILER, 83 HTTP2_HEADER_VARY, 84 HTTP2_HEADER_VIA, 85 HTTP2_HEADER_WARNING, 86 HTTP2_HEADER_WWW_AUTHENTICATE, 87 HTTP2_HEADER_X_FRAME_OPTIONS, 88 89 HTTP2_HEADER_CONNECTION, 90 HTTP2_HEADER_UPGRADE, 91 HTTP2_HEADER_HTTP2_SETTINGS, 92 HTTP2_HEADER_TE, 93 HTTP2_HEADER_TRANSFER_ENCODING, 94 HTTP2_HEADER_KEEP_ALIVE, 95 HTTP2_HEADER_PROXY_CONNECTION 96} = internalBinding('http2').constants; 97 98{ 99 const headers = { 100 'abc': 1, 101 ':path': 'abc', 102 ':status': 200, 103 'xyz': [1, '2', { toString() { return '3'; } }, 4], 104 'foo': [], 105 'BAR': [1] 106 }; 107 108 assert.deepStrictEqual( 109 mapToHeaders(headers), 110 [ [ ':path', 'abc\0', ':status', '200\0', 'abc', '1\0', 'xyz', '1\0', 111 'xyz', '2\0', 'xyz', '3\0', 'xyz', '4\0', 'bar', '1\0', '' ].join('\0'), 112 8 ] 113 ); 114} 115 116{ 117 const headers = { 118 'abc': 1, 119 ':status': [200], 120 ':path': 'abc', 121 ':authority': [], 122 'xyz': [1, 2, 3, 4] 123 }; 124 125 assert.deepStrictEqual( 126 mapToHeaders(headers), 127 [ [ ':status', '200\0', ':path', 'abc\0', 'abc', '1\0', 'xyz', '1\0', 128 'xyz', '2\0', 'xyz', '3\0', 'xyz', '4\0', '' ].join('\0'), 7 ] 129 ); 130} 131 132{ 133 const headers = { 134 'abc': 1, 135 ':status': 200, 136 'xyz': [1, 2, 3, 4], 137 '': 1, 138 ':path': 'abc', 139 [Symbol('test')]: 1 // Symbol keys are ignored 140 }; 141 142 assert.deepStrictEqual( 143 mapToHeaders(headers), 144 [ [ ':status', '200\0', ':path', 'abc\0', 'abc', '1\0', 'xyz', '1\0', 145 'xyz', '2\0', 'xyz', '3\0', 'xyz', '4\0', '' ].join('\0'), 7 ] 146 ); 147} 148 149{ 150 // Only own properties are used 151 const base = { 'abc': 1 }; 152 const headers = Object.create(base); 153 headers[':status'] = 200; 154 headers.xyz = [1, 2, 3, 4]; 155 headers.foo = []; 156 headers[':path'] = 'abc'; 157 158 assert.deepStrictEqual( 159 mapToHeaders(headers), 160 [ [ ':status', '200\0', ':path', 'abc\0', 'xyz', '1\0', 'xyz', '2\0', 161 'xyz', '3\0', 'xyz', '4\0', '' ].join('\0'), 6 ] 162 ); 163} 164 165{ 166 // Arrays containing a single set-cookie value are handled correctly 167 // (https://github.com/nodejs/node/issues/16452) 168 const headers = { 169 'set-cookie': ['foo=bar'] 170 }; 171 assert.deepStrictEqual( 172 mapToHeaders(headers), 173 [ [ 'set-cookie', 'foo=bar\0', '' ].join('\0'), 1 ] 174 ); 175} 176 177{ 178 // pseudo-headers are only allowed a single value 179 const headers = { 180 ':status': 200, 181 ':statuS': 204, 182 }; 183 184 assert.throws(() => mapToHeaders(headers), { 185 code: 'ERR_HTTP2_HEADER_SINGLE_VALUE', 186 name: 'TypeError', 187 message: 'Header field ":status" must only have a single value' 188 }); 189} 190 191{ 192 const headers = { 193 'abc': 1, 194 ':status': [200], 195 ':path': 'abc', 196 ':authority': [], 197 'xyz': [1, 2, 3, 4], 198 [sensitiveHeaders]: ['xyz'] 199 }; 200 201 assert.deepStrictEqual( 202 mapToHeaders(headers), 203 [ ':status\x00200\x00\x00:path\x00abc\x00\x00abc\x001\x00\x00' + 204 'xyz\x001\x00\x01xyz\x002\x00\x01xyz\x003\x00\x01xyz\x004\x00\x01', 7 ] 205 ); 206} 207 208// The following are not allowed to have multiple values 209[ 210 HTTP2_HEADER_STATUS, 211 HTTP2_HEADER_METHOD, 212 HTTP2_HEADER_AUTHORITY, 213 HTTP2_HEADER_SCHEME, 214 HTTP2_HEADER_PATH, 215 HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS, 216 HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE, 217 HTTP2_HEADER_ACCESS_CONTROL_REQUEST_METHOD, 218 HTTP2_HEADER_AGE, 219 HTTP2_HEADER_AUTHORIZATION, 220 HTTP2_HEADER_CONTENT_ENCODING, 221 HTTP2_HEADER_CONTENT_LANGUAGE, 222 HTTP2_HEADER_CONTENT_LENGTH, 223 HTTP2_HEADER_CONTENT_LOCATION, 224 HTTP2_HEADER_CONTENT_MD5, 225 HTTP2_HEADER_CONTENT_RANGE, 226 HTTP2_HEADER_CONTENT_TYPE, 227 HTTP2_HEADER_DATE, 228 HTTP2_HEADER_DNT, 229 HTTP2_HEADER_ETAG, 230 HTTP2_HEADER_EXPIRES, 231 HTTP2_HEADER_FROM, 232 HTTP2_HEADER_HOST, 233 HTTP2_HEADER_IF_MATCH, 234 HTTP2_HEADER_IF_MODIFIED_SINCE, 235 HTTP2_HEADER_IF_NONE_MATCH, 236 HTTP2_HEADER_IF_RANGE, 237 HTTP2_HEADER_IF_UNMODIFIED_SINCE, 238 HTTP2_HEADER_LAST_MODIFIED, 239 HTTP2_HEADER_LOCATION, 240 HTTP2_HEADER_MAX_FORWARDS, 241 HTTP2_HEADER_PROXY_AUTHORIZATION, 242 HTTP2_HEADER_RANGE, 243 HTTP2_HEADER_REFERER, 244 HTTP2_HEADER_RETRY_AFTER, 245 HTTP2_HEADER_TK, 246 HTTP2_HEADER_UPGRADE_INSECURE_REQUESTS, 247 HTTP2_HEADER_USER_AGENT, 248 HTTP2_HEADER_X_CONTENT_TYPE_OPTIONS, 249].forEach((name) => { 250 const msg = `Header field "${name}" must only have a single value`; 251 assert.throws(() => mapToHeaders({ [name]: [1, 2, 3] }), { 252 code: 'ERR_HTTP2_HEADER_SINGLE_VALUE', 253 message: msg 254 }); 255}); 256 257[ 258 HTTP2_HEADER_ACCEPT_CHARSET, 259 HTTP2_HEADER_ACCEPT_ENCODING, 260 HTTP2_HEADER_ACCEPT_LANGUAGE, 261 HTTP2_HEADER_ACCEPT_RANGES, 262 HTTP2_HEADER_ACCEPT, 263 HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS, 264 HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS, 265 HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN, 266 HTTP2_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS, 267 HTTP2_HEADER_ACCESS_CONTROL_REQUEST_HEADERS, 268 HTTP2_HEADER_ALLOW, 269 HTTP2_HEADER_CACHE_CONTROL, 270 HTTP2_HEADER_CONTENT_DISPOSITION, 271 HTTP2_HEADER_COOKIE, 272 HTTP2_HEADER_EXPECT, 273 HTTP2_HEADER_FORWARDED, 274 HTTP2_HEADER_LINK, 275 HTTP2_HEADER_PREFER, 276 HTTP2_HEADER_PROXY_AUTHENTICATE, 277 HTTP2_HEADER_REFRESH, 278 HTTP2_HEADER_SERVER, 279 HTTP2_HEADER_SET_COOKIE, 280 HTTP2_HEADER_STRICT_TRANSPORT_SECURITY, 281 HTTP2_HEADER_TRAILER, 282 HTTP2_HEADER_VARY, 283 HTTP2_HEADER_VIA, 284 HTTP2_HEADER_WARNING, 285 HTTP2_HEADER_WWW_AUTHENTICATE, 286 HTTP2_HEADER_X_FRAME_OPTIONS, 287].forEach((name) => { 288 assert(!(mapToHeaders({ [name]: [1, 2, 3] }) instanceof Error), name); 289}); 290 291[ 292 HTTP2_HEADER_CONNECTION, 293 HTTP2_HEADER_UPGRADE, 294 HTTP2_HEADER_HTTP2_SETTINGS, 295 HTTP2_HEADER_TE, 296 HTTP2_HEADER_TRANSFER_ENCODING, 297 HTTP2_HEADER_PROXY_CONNECTION, 298 HTTP2_HEADER_KEEP_ALIVE, 299 'Connection', 300 'Upgrade', 301 'HTTP2-Settings', 302 'TE', 303 'Transfer-Encoding', 304 'Proxy-Connection', 305 'Keep-Alive', 306].forEach((name) => { 307 assert.throws(() => mapToHeaders({ [name]: 'abc' }), { 308 code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS', 309 name: 'TypeError', 310 message: 'HTTP/1 Connection specific headers are forbidden: ' + 311 `"${name.toLowerCase()}"` 312 }); 313}); 314 315assert.throws(() => mapToHeaders({ [HTTP2_HEADER_TE]: ['abc'] }), { 316 code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS', 317 name: 'TypeError', 318 message: 'HTTP/1 Connection specific headers are forbidden: ' + 319 `"${HTTP2_HEADER_TE}"` 320}); 321 322assert.throws( 323 () => mapToHeaders({ [HTTP2_HEADER_TE]: ['abc', 'trailers'] }), { 324 code: 'ERR_HTTP2_INVALID_CONNECTION_HEADERS', 325 name: 'TypeError', 326 message: 'HTTP/1 Connection specific headers are forbidden: ' + 327 `"${HTTP2_HEADER_TE}"` 328 }); 329 330// These should not throw 331mapToHeaders({ te: 'trailers' }); 332mapToHeaders({ te: ['trailers'] }); 333 334// HTTP/2 encourages use of Host instead of :authority when converting 335// from HTTP/1 to HTTP/2, so we no longer disallow it. 336// Refs: https://github.com/nodejs/node/issues/29858 337mapToHeaders({ [HTTP2_HEADER_HOST]: 'abc' }); 338 339// If both are present, the latter has priority 340assert.strictEqual(getAuthority({ 341 [HTTP2_HEADER_AUTHORITY]: 'abc', 342 [HTTP2_HEADER_HOST]: 'def' 343}), 'abc'); 344 345 346{ 347 const rawHeaders = [ 348 ':status', '200', 349 'cookie', 'foo', 350 'set-cookie', 'sc1', 351 'age', '10', 352 'x-multi', 'first', 353 ]; 354 const headers = toHeaderObject(rawHeaders); 355 assert.strictEqual(headers[':status'], 200); 356 assert.strictEqual(headers.cookie, 'foo'); 357 assert.deepStrictEqual(headers['set-cookie'], ['sc1']); 358 assert.strictEqual(headers.age, '10'); 359 assert.strictEqual(headers['x-multi'], 'first'); 360} 361 362{ 363 const rawHeaders = [ 364 ':status', '200', 365 ':status', '400', 366 'cookie', 'foo', 367 'cookie', 'bar', 368 'set-cookie', 'sc1', 369 'set-cookie', 'sc2', 370 'age', '10', 371 'age', '20', 372 'x-multi', 'first', 373 'x-multi', 'second', 374 ]; 375 const headers = toHeaderObject(rawHeaders); 376 assert.strictEqual(headers[':status'], 200); 377 assert.strictEqual(headers.cookie, 'foo; bar'); 378 assert.deepStrictEqual(headers['set-cookie'], ['sc1', 'sc2']); 379 assert.strictEqual(headers.age, '10'); 380 assert.strictEqual(headers['x-multi'], 'first, second'); 381} 382