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