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