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