• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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