Skip to content

Commit 4f4c8ab

Browse files
committed
deps: update http-parser to version 2.6.1
includes parsing improvements to ensure closer HTTP spec conformance PR-URL: nodejs-private/node-private#26 Reviewed-By: Rod Vagg <[email protected]> Reviewed-By: Сковорода Никита Андреевич <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]>
1 parent d387591 commit 4f4c8ab

12 files changed

+409
-18
lines changed

deps/http_parser/Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,14 @@ PLATFORM ?= $(shell sh -c 'uname -s | tr "[A-Z]" "[a-z]"')
2222
HELPER ?=
2323
BINEXT ?=
2424
ifeq (darwin,$(PLATFORM))
25-
SONAME ?= libhttp_parser.2.6.0.dylib
25+
SONAME ?= libhttp_parser.2.6.1.dylib
2626
SOEXT ?= dylib
2727
else ifeq (wine,$(PLATFORM))
2828
CC = winegcc
2929
BINEXT = .exe.so
3030
HELPER = wine
3131
else
32-
SONAME ?= libhttp_parser.so.2.6.0
32+
SONAME ?= libhttp_parser.so.2.6.1
3333
SOEXT ?= so
3434
endif
3535

deps/http_parser/http_parser.c

+30-2
Original file line numberDiff line numberDiff line change
@@ -435,6 +435,12 @@ enum http_host_state
435435
(IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')
436436
#endif
437437

438+
/**
439+
* Verify that a char is a valid visible (printable) US-ASCII
440+
* character or %x80-FF
441+
**/
442+
#define IS_HEADER_CHAR(ch) \
443+
(ch == CR || ch == LF || ch == 9 || (ch > 31 && ch != 127))
438444

439445
#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)
440446

@@ -639,7 +645,8 @@ size_t http_parser_execute (http_parser *parser,
639645
const char *body_mark = 0;
640646
const char *status_mark = 0;
641647
enum state p_state = (enum state) parser->state;
642-
648+
const unsigned int lenient = parser->lenient_http_headers;
649+
643650
/* We're in an error state. Don't bother doing anything. */
644651
if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
645652
return 0;
@@ -1408,7 +1415,12 @@ size_t http_parser_execute (http_parser *parser,
14081415
|| c != CONTENT_LENGTH[parser->index]) {
14091416
parser->header_state = h_general;
14101417
} else if (parser->index == sizeof(CONTENT_LENGTH)-2) {
1418+
if (parser->flags & F_CONTENTLENGTH) {
1419+
SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
1420+
goto error;
1421+
}
14111422
parser->header_state = h_content_length;
1423+
parser->flags |= F_CONTENTLENGTH;
14121424
}
14131425
break;
14141426

@@ -1560,6 +1572,11 @@ size_t http_parser_execute (http_parser *parser,
15601572
REEXECUTE();
15611573
}
15621574

1575+
if (!lenient && !IS_HEADER_CHAR(ch)) {
1576+
SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
1577+
goto error;
1578+
}
1579+
15631580
c = LOWER(ch);
15641581

15651582
switch (h_state) {
@@ -1727,7 +1744,10 @@ size_t http_parser_execute (http_parser *parser,
17271744

17281745
case s_header_almost_done:
17291746
{
1730-
STRICT_CHECK(ch != LF);
1747+
if (UNLIKELY(ch != LF)) {
1748+
SET_ERRNO(HPE_LF_EXPECTED);
1749+
goto error;
1750+
}
17311751

17321752
UPDATE_STATE(s_header_value_lws);
17331753
break;
@@ -1811,6 +1831,14 @@ size_t http_parser_execute (http_parser *parser,
18111831
REEXECUTE();
18121832
}
18131833

1834+
/* Cannot use chunked encoding and a content-length header together
1835+
per the HTTP specification. */
1836+
if ((parser->flags & F_CHUNKED) &&
1837+
(parser->flags & F_CONTENTLENGTH)) {
1838+
SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
1839+
goto error;
1840+
}
1841+
18141842
UPDATE_STATE(s_headers_done);
18151843

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

deps/http_parser/http_parser.h

+8-4
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ extern "C" {
2727
/* Also update SONAME in the Makefile whenever you change these. */
2828
#define HTTP_PARSER_VERSION_MAJOR 2
2929
#define HTTP_PARSER_VERSION_MINOR 6
30-
#define HTTP_PARSER_VERSION_PATCH 0
30+
#define HTTP_PARSER_VERSION_PATCH 1
3131

3232
#include <sys/types.h>
3333
#if defined(_WIN32) && !defined(__MINGW32__) && \
@@ -148,6 +148,7 @@ enum flags
148148
, F_TRAILING = 1 << 4
149149
, F_UPGRADE = 1 << 5
150150
, F_SKIPBODY = 1 << 6
151+
, F_CONTENTLENGTH = 1 << 7
151152
};
152153

153154

@@ -190,6 +191,8 @@ enum flags
190191
XX(INVALID_HEADER_TOKEN, "invalid character in header") \
191192
XX(INVALID_CONTENT_LENGTH, \
192193
"invalid character in content-length header") \
194+
XX(UNEXPECTED_CONTENT_LENGTH, \
195+
"unexpected content-length header") \
193196
XX(INVALID_CHUNK_SIZE, \
194197
"invalid character in chunk size header") \
195198
XX(INVALID_CONSTANT, "invalid constant string") \
@@ -214,10 +217,11 @@ enum http_errno {
214217
struct http_parser {
215218
/** PRIVATE **/
216219
unsigned int type : 2; /* enum http_parser_type */
217-
unsigned int flags : 7; /* F_* values from 'flags' enum; semi-public */
220+
unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */
218221
unsigned int state : 7; /* enum state from http_parser.c */
219-
unsigned int header_state : 8; /* enum header_state from http_parser.c */
220-
unsigned int index : 8; /* index into current matcher */
222+
unsigned int header_state : 7; /* enum header_state from http_parser.c */
223+
unsigned int index : 7; /* index into current matcher */
224+
unsigned int lenient_http_headers : 1;
221225

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

deps/http_parser/test.c

+161
Original file line numberDiff line numberDiff line change
@@ -3270,6 +3270,155 @@ test_simple (const char *buf, enum http_errno err_expected)
32703270
}
32713271
}
32723272

3273+
void
3274+
test_invalid_header_content (int req, const char* str)
3275+
{
3276+
http_parser parser;
3277+
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
3278+
size_t parsed;
3279+
const char *buf;
3280+
buf = req ?
3281+
"GET / HTTP/1.1\r\n" :
3282+
"HTTP/1.1 200 OK\r\n";
3283+
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
3284+
assert(parsed == strlen(buf));
3285+
3286+
buf = str;
3287+
size_t buflen = strlen(buf);
3288+
3289+
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
3290+
if (parsed != buflen) {
3291+
assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_HEADER_TOKEN);
3292+
return;
3293+
}
3294+
3295+
fprintf(stderr,
3296+
"\n*** Error expected but none in invalid header content test ***\n");
3297+
abort();
3298+
}
3299+
3300+
void
3301+
test_invalid_header_field_content_error (int req)
3302+
{
3303+
test_invalid_header_content(req, "Foo: F\01ailure");
3304+
test_invalid_header_content(req, "Foo: B\02ar");
3305+
}
3306+
3307+
void
3308+
test_invalid_header_field (int req, const char* str)
3309+
{
3310+
http_parser parser;
3311+
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
3312+
size_t parsed;
3313+
const char *buf;
3314+
buf = req ?
3315+
"GET / HTTP/1.1\r\n" :
3316+
"HTTP/1.1 200 OK\r\n";
3317+
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
3318+
assert(parsed == strlen(buf));
3319+
3320+
buf = str;
3321+
size_t buflen = strlen(buf);
3322+
3323+
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
3324+
if (parsed != buflen) {
3325+
assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_HEADER_TOKEN);
3326+
return;
3327+
}
3328+
3329+
fprintf(stderr,
3330+
"\n*** Error expected but none in invalid header token test ***\n");
3331+
abort();
3332+
}
3333+
3334+
void
3335+
test_invalid_header_field_token_error (int req)
3336+
{
3337+
test_invalid_header_field(req, "Fo@: Failure");
3338+
test_invalid_header_field(req, "Foo\01\test: Bar");
3339+
}
3340+
3341+
void
3342+
test_double_content_length_error (int req)
3343+
{
3344+
http_parser parser;
3345+
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
3346+
size_t parsed;
3347+
const char *buf;
3348+
buf = req ?
3349+
"GET / HTTP/1.1\r\n" :
3350+
"HTTP/1.1 200 OK\r\n";
3351+
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
3352+
assert(parsed == strlen(buf));
3353+
3354+
buf = "Content-Length: 0\r\nContent-Length: 1\r\n\r\n";
3355+
size_t buflen = strlen(buf);
3356+
3357+
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
3358+
if (parsed != buflen) {
3359+
assert(HTTP_PARSER_ERRNO(&parser) == HPE_MULTIPLE_CONTENT_LENGTH);
3360+
return;
3361+
}
3362+
3363+
fprintf(stderr,
3364+
"\n*** Error expected but none in double content-length test ***\n");
3365+
abort();
3366+
}
3367+
3368+
void
3369+
test_chunked_content_length_error (int req)
3370+
{
3371+
http_parser parser;
3372+
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
3373+
size_t parsed;
3374+
const char *buf;
3375+
buf = req ?
3376+
"GET / HTTP/1.1\r\n" :
3377+
"HTTP/1.1 200 OK\r\n";
3378+
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
3379+
assert(parsed == strlen(buf));
3380+
3381+
buf = "Transfer-Encoding: chunked\r\nContent-Length: 1\r\n\r\n";
3382+
size_t buflen = strlen(buf);
3383+
3384+
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
3385+
if (parsed != buflen) {
3386+
assert(HTTP_PARSER_ERRNO(&parser) == HPE_CHUNKED_WITH_CONTENT_LENGTH);
3387+
return;
3388+
}
3389+
3390+
fprintf(stderr,
3391+
"\n*** Error expected but none in chunked content-length test ***\n");
3392+
abort();
3393+
}
3394+
3395+
void
3396+
test_header_cr_no_lf_error (int req)
3397+
{
3398+
http_parser parser;
3399+
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
3400+
size_t parsed;
3401+
const char *buf;
3402+
buf = req ?
3403+
"GET / HTTP/1.1\r\n" :
3404+
"HTTP/1.1 200 OK\r\n";
3405+
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
3406+
assert(parsed == strlen(buf));
3407+
3408+
buf = "Foo: 1\rBar: 1\r\n\r\n";
3409+
size_t buflen = strlen(buf);
3410+
3411+
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
3412+
if (parsed != buflen) {
3413+
assert(HTTP_PARSER_ERRNO(&parser) == HPE_LF_EXPECTED);
3414+
return;
3415+
}
3416+
3417+
fprintf(stderr,
3418+
"\n*** Error expected but none in header whitespace test ***\n");
3419+
abort();
3420+
}
3421+
32733422
void
32743423
test_header_overflow_error (int req)
32753424
{
@@ -3696,6 +3845,18 @@ main (void)
36963845
test_header_content_length_overflow_error();
36973846
test_chunk_content_length_overflow_error();
36983847

3848+
//// HEADER FIELD CONDITIONS
3849+
test_double_content_length_error(HTTP_REQUEST);
3850+
test_chunked_content_length_error(HTTP_REQUEST);
3851+
test_header_cr_no_lf_error(HTTP_REQUEST);
3852+
test_invalid_header_field_token_error(HTTP_REQUEST);
3853+
test_invalid_header_field_content_error(HTTP_REQUEST);
3854+
test_double_content_length_error(HTTP_RESPONSE);
3855+
test_chunked_content_length_error(HTTP_RESPONSE);
3856+
test_header_cr_no_lf_error(HTTP_RESPONSE);
3857+
test_invalid_header_field_token_error(HTTP_RESPONSE);
3858+
test_invalid_header_field_content_error(HTTP_RESPONSE);
3859+
36993860
//// RESPONSES
37003861

37013862
for (i = 0; i < response_count; i++) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const http = require('http');
5+
const net = require('net');
6+
const assert = require('assert');
7+
8+
const reqstr = 'HTTP/1.1 200 OK\r\n' +
9+
'Content-Length: 1\r\n' +
10+
'Transfer-Encoding: chunked\r\n\r\n';
11+
12+
const server = net.createServer((socket) => {
13+
socket.write(reqstr);
14+
});
15+
16+
server.listen(common.PORT, () => {
17+
// The callback should not be called because the server is sending
18+
// both a Content-Length header and a Transfer-Encoding: chunked
19+
// header, which is a violation of the HTTP spec.
20+
const req = http.get({port:common.PORT}, (res) => {
21+
assert.fail(null, null, 'callback should not be called');
22+
});
23+
req.on('error', common.mustCall((err) => {
24+
assert(/^Parse Error/.test(err.message));
25+
assert.equal(err.code, 'HPE_UNEXPECTED_CONTENT_LENGTH');
26+
server.close();
27+
}));
28+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const http = require('http');
5+
const net = require('net');
6+
const assert = require('assert');
7+
8+
const reqstr = 'HTTP/1.1 200 OK\r\n' +
9+
'Foo: Bar\r' +
10+
'Content-Length: 1\r\n\r\n';
11+
12+
const server = net.createServer((socket) => {
13+
socket.write(reqstr);
14+
});
15+
16+
server.listen(common.PORT, () => {
17+
// The callback should not be called because the server is sending a
18+
// header field that ends only in \r with no following \n
19+
const req = http.get({port:common.PORT}, (res) => {
20+
assert.fail(null, null, 'callback should not be called');
21+
});
22+
req.on('error', common.mustCall((err) => {
23+
assert(/^Parse Error/.test(err.message));
24+
assert.equal(err.code, 'HPE_LF_EXPECTED');
25+
server.close();
26+
}));
27+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
const http = require('http');
5+
const assert = require('assert');
6+
7+
// The callback should never be invoked because the server
8+
// should respond with a 400 Client Error when a double
9+
// Content-Length header is received.
10+
const server = http.createServer((req, res) => {
11+
assert(false, 'callback should not have been invoked');
12+
res.end();
13+
});
14+
server.on('clientError', common.mustCall((err, socket) => {
15+
assert(/^Parse Error/.test(err.message));
16+
assert.equal(err.code, 'HPE_UNEXPECTED_CONTENT_LENGTH');
17+
socket.destroy();
18+
}));
19+
20+
server.listen(common.PORT, () => {
21+
const req = http.get({
22+
port: common.PORT,
23+
// Send two content-length header values.
24+
headers: {'Content-Length': [1, 2]}},
25+
(res) => {
26+
assert.fail(null, null, 'an error should have occurred');
27+
server.close();
28+
}
29+
);
30+
req.on('error', common.mustCall(() => {
31+
server.close();
32+
}));
33+
});

0 commit comments

Comments
 (0)