Skip to content

Commit 375f355

Browse files
committed
deps: update http-parser to version 1.1
includes parsing improvements to ensure closer HTTP spec conformance PR-URL: nodejs-private/node-private#22
1 parent 85e1d9f commit 375f355

12 files changed

+393
-8
lines changed

deps/http_parser/http_parser.c

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,8 @@ enum http_host_state
387387
(IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')
388388
#endif
389389

390+
#define IS_HEADER_CHAR(ch) \
391+
(ch == CR || ch == LF || ch == 9 || (ch > 31 && ch != 127))
390392

391393
#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)
392394

@@ -590,6 +592,8 @@ size_t http_parser_execute (http_parser *parser,
590592
const char *url_mark = 0;
591593
const char *body_mark = 0;
592594

595+
const unsigned char lenient = parser->lenient_http_headers;
596+
593597
/* We're in an error state. Don't bother doing anything. */
594598
if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
595599
return 0;
@@ -1311,7 +1315,12 @@ size_t http_parser_execute (http_parser *parser,
13111315
|| c != CONTENT_LENGTH[parser->index]) {
13121316
parser->header_state = h_general;
13131317
} else if (parser->index == sizeof(CONTENT_LENGTH)-2) {
1314-
parser->header_state = h_content_length;
1318+
if (parser->flags & F_CONTENTLENGTH) {
1319+
SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
1320+
goto error;
1321+
}
1322+
parser->header_state = h_content_length;
1323+
parser->flags |= F_CONTENTLENGTH;
13151324
}
13161325
break;
13171326

@@ -1457,6 +1466,11 @@ size_t http_parser_execute (http_parser *parser,
14571466
goto reexecute_byte;
14581467
}
14591468

1469+
if (!lenient && !IS_HEADER_CHAR(ch)) {
1470+
SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
1471+
goto error;
1472+
}
1473+
14601474
c = LOWER(ch);
14611475

14621476
switch (parser->header_state) {
@@ -1541,7 +1555,10 @@ size_t http_parser_execute (http_parser *parser,
15411555

15421556
case s_header_almost_done:
15431557
{
1544-
STRICT_CHECK(ch != LF);
1558+
if (ch != LF) {
1559+
SET_ERRNO(HPE_LF_EXPECTED);
1560+
goto error;
1561+
}
15451562

15461563
parser->state = s_header_value_lws;
15471564

@@ -1585,6 +1602,14 @@ size_t http_parser_execute (http_parser *parser,
15851602
break;
15861603
}
15871604

1605+
/* Cannot use chunked encoding and a content-length header together
1606+
per the HTTP specification. */
1607+
if ((parser->flags & F_CHUNKED) &&
1608+
(parser->flags & F_CONTENTLENGTH)) {
1609+
SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
1610+
goto error;
1611+
}
1612+
15881613
parser->state = s_headers_done;
15891614

15901615
/* Set this here so that on_headers_complete() callbacks can see it */

deps/http_parser/http_parser.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ extern "C" {
2525
#endif
2626

2727
#define HTTP_PARSER_VERSION_MAJOR 1
28-
#define HTTP_PARSER_VERSION_MINOR 0
28+
#define HTTP_PARSER_VERSION_MINOR 1
2929

3030
#include <sys/types.h>
3131
#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600)
@@ -137,6 +137,7 @@ enum flags
137137
, F_TRAILING = 1 << 3
138138
, F_UPGRADE = 1 << 4
139139
, F_SKIPBODY = 1 << 5
140+
, F_CONTENTLENGTH = 1 << 6
140141
};
141142

142143

@@ -176,6 +177,8 @@ enum flags
176177
XX(INVALID_HEADER_TOKEN, "invalid character in header") \
177178
XX(INVALID_CONTENT_LENGTH, \
178179
"invalid character in content-length header") \
180+
XX(UNEXPECTED_CONTENT_LENGTH, \
181+
"unexpected content-length header") \
179182
XX(INVALID_CHUNK_SIZE, \
180183
"invalid character in chunk size header") \
181184
XX(INVALID_CONSTANT, "invalid constant string") \
@@ -207,10 +210,11 @@ enum http_errno {
207210
struct http_parser {
208211
/** PRIVATE **/
209212
unsigned char type : 2; /* enum http_parser_type */
210-
unsigned char flags : 6; /* F_* values from 'flags' enum; semi-public */
213+
unsigned char flags : 7; /* F_* values from 'flags' enum; semi-public */
211214
unsigned char state; /* enum state from http_parser.c */
212-
unsigned char header_state; /* enum header_state from http_parser.c */
213-
unsigned char index; /* index into current matcher */
215+
unsigned char header_state : 7; /* enum header_state from http_parser.c */
216+
unsigned char index : 7; /* index into current matcher */
217+
unsigned char lenient_http_headers : 1;
214218

215219
uint32_t nread; /* # bytes read in various scenarios */
216220
uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */

deps/http_parser/test.c

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2651,6 +2651,156 @@ test_simple (const char *buf, enum http_errno err_expected)
26512651
}
26522652
}
26532653

2654+
void
2655+
test_invalid_header_content (int req, const char* str)
2656+
{
2657+
http_parser parser;
2658+
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
2659+
size_t parsed;
2660+
const char *buf;
2661+
buf = req ?
2662+
"GET / HTTP/1.1\r\n" :
2663+
"HTTP/1.1 200 OK\r\n";
2664+
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
2665+
assert(parsed == strlen(buf));
2666+
2667+
buf = str;
2668+
size_t buflen = strlen(buf);
2669+
2670+
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
2671+
if (parsed != buflen) {
2672+
assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_HEADER_TOKEN);
2673+
return;
2674+
}
2675+
2676+
fprintf(stderr,
2677+
"\n*** Error expected but none in invalid header content test ***\n");
2678+
abort();
2679+
}
2680+
2681+
void
2682+
test_invalid_header_field_content_error (int req)
2683+
{
2684+
test_invalid_header_content(req, "Foo: F\01ailure");
2685+
test_invalid_header_content(req, "Foo: B\02ar");
2686+
}
2687+
2688+
void
2689+
test_invalid_header_field (int req, const char* str)
2690+
{
2691+
http_parser parser;
2692+
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
2693+
size_t parsed;
2694+
const char *buf;
2695+
buf = req ?
2696+
"GET / HTTP/1.1\r\n" :
2697+
"HTTP/1.1 200 OK\r\n";
2698+
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
2699+
assert(parsed == strlen(buf));
2700+
2701+
buf = str;
2702+
size_t buflen = strlen(buf);
2703+
2704+
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
2705+
if (parsed != buflen) {
2706+
assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_HEADER_TOKEN);
2707+
return;
2708+
}
2709+
2710+
fprintf(stderr,
2711+
"\n*** Error expected but none in invalid header token test ***\n");
2712+
abort();
2713+
}
2714+
2715+
void
2716+
test_invalid_header_field_token_error (int req)
2717+
{
2718+
test_invalid_header_field(req, "Fo@: Failure");
2719+
test_invalid_header_field(req, "Foo\01\test: Bar");
2720+
}
2721+
2722+
void
2723+
test_double_content_length_error (int req)
2724+
{
2725+
http_parser parser;
2726+
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
2727+
size_t parsed;
2728+
const char *buf;
2729+
buf = req ?
2730+
"GET / HTTP/1.1\r\n" :
2731+
"HTTP/1.1 200 OK\r\n";
2732+
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
2733+
assert(parsed == strlen(buf));
2734+
2735+
buf = "Content-Length: 0\r\nContent-Length: 1\r\n\r\n";
2736+
size_t buflen = strlen(buf);
2737+
2738+
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
2739+
if (parsed != buflen) {
2740+
assert(HTTP_PARSER_ERRNO(&parser) == HPE_UNEXPECTED_CONTENT_LENGTH);
2741+
return;
2742+
}
2743+
2744+
fprintf(stderr,
2745+
"\n*** Error expected but none in double content-length test ***\n");
2746+
abort();
2747+
}
2748+
2749+
void
2750+
test_chunked_content_length_error (int req)
2751+
{
2752+
http_parser parser;
2753+
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
2754+
size_t parsed;
2755+
const char *buf;
2756+
buf = req ?
2757+
"GET / HTTP/1.1\r\n" :
2758+
"HTTP/1.1 200 OK\r\n";
2759+
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
2760+
assert(parsed == strlen(buf));
2761+
2762+
buf = "Transfer-Encoding: chunked\r\nContent-Length: 1\r\n\r\n";
2763+
size_t buflen = strlen(buf);
2764+
2765+
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
2766+
if (parsed != buflen) {
2767+
assert(HTTP_PARSER_ERRNO(&parser) == HPE_UNEXPECTED_CONTENT_LENGTH);
2768+
return;
2769+
}
2770+
2771+
fprintf(stderr,
2772+
"\n*** Error expected but none in chunked content-length test ***\n");
2773+
abort();
2774+
}
2775+
2776+
void
2777+
test_header_cr_no_lf_error (int req)
2778+
{
2779+
http_parser parser;
2780+
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
2781+
size_t parsed;
2782+
const char *buf;
2783+
buf = req ?
2784+
"GET / HTTP/1.1\r\n" :
2785+
"HTTP/1.1 200 OK\r\n";
2786+
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
2787+
assert(parsed == strlen(buf));
2788+
2789+
buf = "Foo: 1\rBar: 1\r\n\r\n";
2790+
size_t buflen = strlen(buf);
2791+
2792+
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
2793+
if (parsed != buflen) {
2794+
assert(HTTP_PARSER_ERRNO(&parser) == HPE_LF_EXPECTED);
2795+
return;
2796+
}
2797+
2798+
fprintf(stderr,
2799+
"\n*** Error expected but none in header whitespace test ***\n");
2800+
abort();
2801+
}
2802+
2803+
26542804
void
26552805
test_header_overflow_error (int req)
26562806
{
@@ -3048,6 +3198,19 @@ main (void)
30483198
test_header_content_length_overflow_error();
30493199
test_chunk_content_length_overflow_error();
30503200

3201+
//// HEADER FIELD CONDITIONS
3202+
test_double_content_length_error(HTTP_REQUEST);
3203+
test_chunked_content_length_error(HTTP_REQUEST);
3204+
test_header_cr_no_lf_error(HTTP_REQUEST);
3205+
test_invalid_header_field_token_error(HTTP_REQUEST);
3206+
test_invalid_header_field_content_error(HTTP_REQUEST);
3207+
test_double_content_length_error(HTTP_RESPONSE);
3208+
test_chunked_content_length_error(HTTP_RESPONSE);
3209+
test_header_cr_no_lf_error(HTTP_RESPONSE);
3210+
test_invalid_header_field_token_error(HTTP_RESPONSE);
3211+
test_invalid_header_field_content_error(HTTP_RESPONSE);
3212+
3213+
30513214
//// RESPONSES
30523215

30533216
for (i = 0; i < response_count; i++) {

src/node_http_parser.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "v8.h"
2525
#include "node.h"
2626
#include "node_buffer.h"
27+
#include "node_revert.h"
2728

2829
#include <string.h> /* strdup() */
2930
#if !defined(_MSC_VER)
@@ -546,6 +547,8 @@ class Parser : public ObjectWrap {
546547

547548
void Init(enum http_parser_type type) {
548549
http_parser_init(&parser_, type);
550+
/* Allow the strict http header parsing to be reverted */
551+
parser_.lenient_http_headers = IsReverted(REVERT_CVE_2016_2216) ? 1 : 0;
549552
url_.Reset();
550553
num_fields_ = 0;
551554
num_values_ = 0;

src/node_revert.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@
3232
* For *master* this list should always be empty!
3333
*
3434
**/
35-
#define REVERSIONS(XX)
36-
// XX(CVE_2016_PEND, "CVE-2016-PEND", "Vulnerability Title")
35+
#define REVERSIONS(XX) \
36+
XX(CVE_2016_2216, "CVE-2016-2216", "Strict HTTP Header Parsing")
3737

3838
namespace node {
3939

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
var common = require('../common');
2+
var http = require('http');
3+
var net = require('net');
4+
var assert = require('assert');
5+
6+
var reqstr = 'HTTP/1.1 200 OK\r\n' +
7+
'Content-Length: 1\r\n' +
8+
'Transfer-Encoding: chunked\r\n\r\n';
9+
10+
var server = net.createServer(function(socket) {
11+
socket.write(reqstr);
12+
});
13+
14+
server.listen(common.PORT, function() {
15+
// The callback should not be called because the server is sending
16+
// both a Content-Length header and a Transfer-Encoding: chunked
17+
// header, which is a violation of the HTTP spec.
18+
var req = http.get({port:common.PORT}, function(res) {
19+
assert.fail(null, null, 'callback should not be called');
20+
});
21+
req.on('error', common.mustCall(function(err) {
22+
assert(/^Parse Error/.test(err.message));
23+
assert.equal(err.code, 'HPE_UNEXPECTED_CONTENT_LENGTH');
24+
server.close();
25+
}));
26+
});
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
var common = require('../common');
2+
var http = require('http');
3+
var net = require('net');
4+
var assert = require('assert');
5+
6+
var reqstr = 'HTTP/1.1 200 OK\r\n' +
7+
'Foo: Bar\r' +
8+
'Content-Length: 1\r\n\r\n';
9+
10+
var server = net.createServer(function(socket) {
11+
socket.write(reqstr);
12+
});
13+
14+
server.listen(common.PORT, function() {
15+
// The callback should not be called because the server is sending a
16+
// header field that ends only in \r with no following \n
17+
var req = http.get({port:common.PORT}, function(res) {
18+
assert.fail(null, null, 'callback should not be called');
19+
});
20+
req.on('error', common.mustCall(function(err) {
21+
assert(/^Parse Error/.test(err.message));
22+
assert.equal(err.code, 'HPE_LF_EXPECTED');
23+
server.close();
24+
}));
25+
});

0 commit comments

Comments
 (0)