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