• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22'use strict';
23const { mustCall, mustNotCall } = require('../common');
24const assert = require('assert');
25
26const { methods, HTTPParser } = require('_http_common');
27const { REQUEST, RESPONSE } = HTTPParser;
28
29const kOnHeaders = HTTPParser.kOnHeaders | 0;
30const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
31const kOnBody = HTTPParser.kOnBody | 0;
32const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;
33
34// The purpose of this test is not to check HTTP compliance but to test the
35// binding. Tests for pathological http messages should be submitted
36// upstream to https://github.com/joyent/http-parser for inclusion into
37// deps/http-parser/test.c
38
39
40function newParser(type) {
41  const parser = new HTTPParser();
42  parser.initialize(type, {});
43
44  parser.headers = [];
45  parser.url = '';
46
47  parser[kOnHeaders] = function(headers, url) {
48    parser.headers = parser.headers.concat(headers);
49    parser.url += url;
50  };
51
52  parser[kOnHeadersComplete] = function() {
53  };
54
55  parser[kOnBody] = mustNotCall('kOnBody should not be called');
56
57  parser[kOnMessageComplete] = function() {
58  };
59
60  return parser;
61}
62
63
64function expectBody(expected) {
65  return mustCall(function(buf) {
66    const body = String(buf);
67    assert.strictEqual(body, expected);
68  });
69}
70
71
72//
73// Simple request test.
74//
75{
76  const request = Buffer.from('GET /hello HTTP/1.1\r\n\r\n');
77
78  const onHeadersComplete = (versionMajor, versionMinor, headers,
79                             method, url) => {
80    assert.strictEqual(versionMajor, 1);
81    assert.strictEqual(versionMinor, 1);
82    assert.strictEqual(method, methods.indexOf('GET'));
83    assert.strictEqual(url || parser.url, '/hello');
84  };
85
86  const parser = newParser(REQUEST);
87  parser[kOnHeadersComplete] = mustCall(onHeadersComplete);
88  parser.execute(request, 0, request.length);
89
90  //
91  // Check that if we throw an error in the callbacks that error will be
92  // thrown from parser.execute()
93  //
94
95  parser[kOnHeadersComplete] = function() {
96    throw new Error('hello world');
97  };
98
99  parser.initialize(REQUEST, {});
100
101  assert.throws(
102    () => { parser.execute(request, 0, request.length); },
103    { name: 'Error', message: 'hello world' }
104  );
105}
106
107
108//
109// Simple response test.
110//
111{
112  const request = Buffer.from(
113    'HTTP/1.1 200 OK\r\n' +
114    'Content-Type: text/plain\r\n' +
115    'Content-Length: 4\r\n' +
116    '\r\n' +
117    'pong'
118  );
119
120  const onHeadersComplete = (versionMajor, versionMinor, headers,
121                             method, url, statusCode, statusMessage) => {
122    assert.strictEqual(method, undefined);
123    assert.strictEqual(versionMajor, 1);
124    assert.strictEqual(versionMinor, 1);
125    assert.strictEqual(statusCode, 200);
126    assert.strictEqual(statusMessage, 'OK');
127  };
128
129  const onBody = (buf) => {
130    const body = String(buf);
131    assert.strictEqual(body, 'pong');
132  };
133
134  const parser = newParser(RESPONSE);
135  parser[kOnHeadersComplete] = mustCall(onHeadersComplete);
136  parser[kOnBody] = mustCall(onBody);
137  parser.execute(request, 0, request.length);
138}
139
140
141//
142// Response with no headers.
143//
144{
145  const request = Buffer.from(
146    'HTTP/1.0 200 Connection established\r\n\r\n');
147
148  const onHeadersComplete = (versionMajor, versionMinor, headers,
149                             method, url, statusCode, statusMessage) => {
150    assert.strictEqual(versionMajor, 1);
151    assert.strictEqual(versionMinor, 0);
152    assert.strictEqual(method, undefined);
153    assert.strictEqual(statusCode, 200);
154    assert.strictEqual(statusMessage, 'Connection established');
155    assert.deepStrictEqual(headers || parser.headers, []);
156  };
157
158  const parser = newParser(RESPONSE);
159  parser[kOnHeadersComplete] = mustCall(onHeadersComplete);
160  parser.execute(request, 0, request.length);
161}
162
163
164//
165// Trailing headers.
166//
167{
168  const request = Buffer.from(
169    'POST /it HTTP/1.1\r\n' +
170    'Transfer-Encoding: chunked\r\n' +
171    '\r\n' +
172    '4\r\n' +
173    'ping\r\n' +
174    '0\r\n' +
175    'Vary: *\r\n' +
176    'Content-Type: text/plain\r\n' +
177    '\r\n'
178  );
179
180  let seen_body = false;
181
182  const onHeaders = (headers) => {
183    assert.ok(seen_body); // Trailers should come after the body
184    assert.deepStrictEqual(headers,
185                           ['Vary', '*', 'Content-Type', 'text/plain']);
186  };
187
188  const onHeadersComplete = (versionMajor, versionMinor, headers,
189                             method, url) => {
190    assert.strictEqual(method, methods.indexOf('POST'));
191    assert.strictEqual(url || parser.url, '/it');
192    assert.strictEqual(versionMajor, 1);
193    assert.strictEqual(versionMinor, 1);
194    // Expect to see trailing headers now
195    parser[kOnHeaders] = mustCall(onHeaders);
196  };
197
198  const onBody = (buf) => {
199    const body = String(buf);
200    assert.strictEqual(body, 'ping');
201    seen_body = true;
202  };
203
204  const parser = newParser(REQUEST);
205  parser[kOnHeadersComplete] = mustCall(onHeadersComplete);
206  parser[kOnBody] = mustCall(onBody);
207  parser.execute(request, 0, request.length);
208}
209
210
211//
212// Test header ordering.
213//
214{
215  const request = Buffer.from(
216    'GET / HTTP/1.0\r\n' +
217    'X-Filler: 1337\r\n' +
218    'X-Filler:   42\r\n' +
219    'X-Filler2:  42\r\n' +
220    '\r\n'
221  );
222
223  const onHeadersComplete = (versionMajor, versionMinor, headers,
224                             method) => {
225    assert.strictEqual(method, methods.indexOf('GET'));
226    assert.strictEqual(versionMajor, 1);
227    assert.strictEqual(versionMinor, 0);
228    assert.deepStrictEqual(
229      headers || parser.headers,
230      ['X-Filler', '1337', 'X-Filler', '42', 'X-Filler2', '42']);
231  };
232
233  const parser = newParser(REQUEST);
234  parser[kOnHeadersComplete] = mustCall(onHeadersComplete);
235  parser.execute(request, 0, request.length);
236}
237
238
239//
240// Test large number of headers
241//
242{
243  // 256 X-Filler headers
244  const lots_of_headers = 'X-Filler: 42\r\n'.repeat(256);
245
246  const request = Buffer.from(
247    'GET /foo/bar/baz?quux=42#1337 HTTP/1.0\r\n' +
248    lots_of_headers +
249    '\r\n'
250  );
251
252  const onHeadersComplete = (versionMajor, versionMinor, headers,
253                             method, url) => {
254    assert.strictEqual(method, methods.indexOf('GET'));
255    assert.strictEqual(url || parser.url, '/foo/bar/baz?quux=42#1337');
256    assert.strictEqual(versionMajor, 1);
257    assert.strictEqual(versionMinor, 0);
258
259    headers = headers || parser.headers;
260
261    assert.strictEqual(headers.length, 2 * 256); // 256 key/value pairs
262    for (let i = 0; i < headers.length; i += 2) {
263      assert.strictEqual(headers[i], 'X-Filler');
264      assert.strictEqual(headers[i + 1], '42');
265    }
266  };
267
268  const parser = newParser(REQUEST);
269  parser[kOnHeadersComplete] = mustCall(onHeadersComplete);
270  parser.execute(request, 0, request.length);
271}
272
273
274//
275// Test request body
276//
277{
278  const request = Buffer.from(
279    'POST /it HTTP/1.1\r\n' +
280    'Content-Type: application/x-www-form-urlencoded\r\n' +
281    'Content-Length: 15\r\n' +
282    '\r\n' +
283    'foo=42&bar=1337'
284  );
285
286  const onHeadersComplete = (versionMajor, versionMinor, headers,
287                             method, url) => {
288    assert.strictEqual(method, methods.indexOf('POST'));
289    assert.strictEqual(url || parser.url, '/it');
290    assert.strictEqual(versionMajor, 1);
291    assert.strictEqual(versionMinor, 1);
292  };
293
294  const onBody = (buf) => {
295    const body = String(buf);
296    assert.strictEqual(body, 'foo=42&bar=1337');
297  };
298
299  const parser = newParser(REQUEST);
300  parser[kOnHeadersComplete] = mustCall(onHeadersComplete);
301  parser[kOnBody] = mustCall(onBody);
302  parser.execute(request, 0, request.length);
303}
304
305
306//
307// Test chunked request body
308//
309{
310  const request = Buffer.from(
311    'POST /it HTTP/1.1\r\n' +
312    'Content-Type: text/plain\r\n' +
313    'Transfer-Encoding: chunked\r\n' +
314    '\r\n' +
315    '3\r\n' +
316    '123\r\n' +
317    '6\r\n' +
318    '123456\r\n' +
319    'A\r\n' +
320    '1234567890\r\n' +
321    '0\r\n'
322  );
323
324  const onHeadersComplete = (versionMajor, versionMinor, headers,
325                             method, url) => {
326    assert.strictEqual(method, methods.indexOf('POST'));
327    assert.strictEqual(url || parser.url, '/it');
328    assert.strictEqual(versionMajor, 1);
329    assert.strictEqual(versionMinor, 1);
330  };
331
332  let body_part = 0;
333  const body_parts = ['123', '123456', '1234567890'];
334
335  const onBody = (buf) => {
336    const body = String(buf);
337    assert.strictEqual(body, body_parts[body_part++]);
338  };
339
340  const parser = newParser(REQUEST);
341  parser[kOnHeadersComplete] = mustCall(onHeadersComplete);
342  parser[kOnBody] = mustCall(onBody, body_parts.length);
343  parser.execute(request, 0, request.length);
344}
345
346
347//
348// Test chunked request body spread over multiple buffers (packets)
349//
350{
351  let request = Buffer.from(
352    'POST /it HTTP/1.1\r\n' +
353    'Content-Type: text/plain\r\n' +
354    'Transfer-Encoding: chunked\r\n' +
355    '\r\n' +
356    '3\r\n' +
357    '123\r\n' +
358    '6\r\n' +
359    '123456\r\n'
360  );
361
362  const onHeadersComplete = (versionMajor, versionMinor, headers,
363                             method, url) => {
364    assert.strictEqual(method, methods.indexOf('POST'));
365    assert.strictEqual(url || parser.url, '/it');
366    assert.strictEqual(versionMajor, 1);
367    assert.strictEqual(versionMinor, 1);
368  };
369
370  let body_part = 0;
371  const body_parts =
372          ['123', '123456', '123456789', '123456789ABC', '123456789ABCDEF'];
373
374  const onBody = (buf) => {
375    const body = String(buf);
376    assert.strictEqual(body, body_parts[body_part++]);
377  };
378
379  const parser = newParser(REQUEST);
380  parser[kOnHeadersComplete] = mustCall(onHeadersComplete);
381  parser[kOnBody] = mustCall(onBody, body_parts.length);
382  parser.execute(request, 0, request.length);
383
384  request = Buffer.from(
385    '9\r\n' +
386    '123456789\r\n' +
387    'C\r\n' +
388    '123456789ABC\r\n' +
389    'F\r\n' +
390    '123456789ABCDEF\r\n' +
391    '0\r\n'
392  );
393
394  parser.execute(request, 0, request.length);
395}
396
397
398//
399// Stress test.
400//
401{
402  const request = Buffer.from(
403    'POST /helpme HTTP/1.1\r\n' +
404    'Content-Type: text/plain\r\n' +
405    'Transfer-Encoding: chunked\r\n' +
406    '\r\n' +
407    '3\r\n' +
408    '123\r\n' +
409    '6\r\n' +
410    '123456\r\n' +
411    '9\r\n' +
412    '123456789\r\n' +
413    'C\r\n' +
414    '123456789ABC\r\n' +
415    'F\r\n' +
416    '123456789ABCDEF\r\n' +
417    '0\r\n'
418  );
419
420  function test(a, b) {
421    const onHeadersComplete = (versionMajor, versionMinor, headers,
422                               method, url) => {
423      assert.strictEqual(method, methods.indexOf('POST'));
424      assert.strictEqual(url || parser.url, '/helpme');
425      assert.strictEqual(versionMajor, 1);
426      assert.strictEqual(versionMinor, 1);
427    };
428
429    let expected_body = '123123456123456789123456789ABC123456789ABCDEF';
430
431    const onBody = (buf) => {
432      const chunk = String(buf);
433      assert.strictEqual(expected_body.indexOf(chunk), 0);
434      expected_body = expected_body.slice(chunk.length);
435    };
436
437    const parser = newParser(REQUEST);
438    parser[kOnHeadersComplete] = mustCall(onHeadersComplete);
439    parser[kOnBody] = onBody;
440    parser.execute(a, 0, a.length);
441    parser.execute(b, 0, b.length);
442
443    assert.strictEqual(expected_body, '');
444  }
445
446  for (let i = 1; i < request.length - 1; ++i) {
447    const a = request.slice(0, i);
448    const b = request.slice(i);
449    test(a, b);
450  }
451}
452
453
454//
455// Byte by byte test.
456//
457{
458  const request = Buffer.from(
459    'POST /it HTTP/1.1\r\n' +
460    'Content-Type: text/plain\r\n' +
461    'Transfer-Encoding: chunked\r\n' +
462    '\r\n' +
463    '3\r\n' +
464    '123\r\n' +
465    '6\r\n' +
466    '123456\r\n' +
467    '9\r\n' +
468    '123456789\r\n' +
469    'C\r\n' +
470    '123456789ABC\r\n' +
471    'F\r\n' +
472    '123456789ABCDEF\r\n' +
473    '0\r\n'
474  );
475
476  const onHeadersComplete = (versionMajor, versionMinor, headers,
477                             method, url) => {
478    assert.strictEqual(method, methods.indexOf('POST'));
479    assert.strictEqual(url || parser.url, '/it');
480    assert.strictEqual(versionMajor, 1);
481    assert.strictEqual(versionMinor, 1);
482    assert.deepStrictEqual(
483      headers || parser.headers,
484      ['Content-Type', 'text/plain', 'Transfer-Encoding', 'chunked']);
485  };
486
487  let expected_body = '123123456123456789123456789ABC123456789ABCDEF';
488
489  const onBody = (buf) => {
490    const chunk = String(buf);
491    assert.strictEqual(expected_body.indexOf(chunk), 0);
492    expected_body = expected_body.slice(chunk.length);
493  };
494
495  const parser = newParser(REQUEST);
496  parser[kOnHeadersComplete] = mustCall(onHeadersComplete);
497  parser[kOnBody] = onBody;
498
499  for (let i = 0; i < request.length; ++i) {
500    parser.execute(request, i, 1);
501  }
502
503  assert.strictEqual(expected_body, '');
504}
505
506
507//
508// Test parser reinit sequence.
509//
510{
511  const req1 = Buffer.from(
512    'PUT /this HTTP/1.1\r\n' +
513    'Content-Type: text/plain\r\n' +
514    'Transfer-Encoding: chunked\r\n' +
515    '\r\n' +
516    '4\r\n' +
517    'ping\r\n' +
518    '0\r\n'
519  );
520
521  const req2 = Buffer.from(
522    'POST /that HTTP/1.0\r\n' +
523    'Content-Type: text/plain\r\n' +
524    'Content-Length: 4\r\n' +
525    '\r\n' +
526    'pong'
527  );
528
529  const onHeadersComplete1 = (versionMajor, versionMinor, headers,
530                              method, url) => {
531    assert.strictEqual(method, methods.indexOf('PUT'));
532    assert.strictEqual(url, '/this');
533    assert.strictEqual(versionMajor, 1);
534    assert.strictEqual(versionMinor, 1);
535    assert.deepStrictEqual(
536      headers,
537      ['Content-Type', 'text/plain', 'Transfer-Encoding', 'chunked']);
538  };
539
540  const onHeadersComplete2 = (versionMajor, versionMinor, headers,
541                              method, url) => {
542    assert.strictEqual(method, methods.indexOf('POST'));
543    assert.strictEqual(url, '/that');
544    assert.strictEqual(versionMajor, 1);
545    assert.strictEqual(versionMinor, 0);
546    assert.deepStrictEqual(
547      headers,
548      ['Content-Type', 'text/plain', 'Content-Length', '4']
549    );
550  };
551
552  const parser = newParser(REQUEST);
553  parser[kOnHeadersComplete] = onHeadersComplete1;
554  parser[kOnBody] = expectBody('ping');
555  parser.execute(req1, 0, req1.length);
556
557  parser.initialize(REQUEST, req2);
558  parser[kOnBody] = expectBody('pong');
559  parser[kOnHeadersComplete] = onHeadersComplete2;
560  parser.execute(req2, 0, req2.length);
561}
562
563// Test parser 'this' safety
564// https://github.com/joyent/node/issues/6690
565assert.throws(function() {
566  const request = Buffer.from('GET /hello HTTP/1.1\r\n\r\n');
567
568  const parser = newParser(REQUEST);
569  const notparser = { execute: parser.execute };
570  notparser.execute(request, 0, request.length);
571}, TypeError);
572