Update http-parser
This commit is contained in:
parent
ca261a7971
commit
bda352bf73
|
@ -123,7 +123,7 @@ do { \
|
|||
FOR##_mark = NULL; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
|
||||
/* Run the data callback FOR and consume the current byte */
|
||||
#define CALLBACK_DATA(FOR) \
|
||||
CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1)
|
||||
|
@ -435,6 +435,12 @@ enum http_host_state
|
|||
(IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_')
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Verify that a char is a valid visible (printable) US-ASCII
|
||||
* character or %x80-FF
|
||||
**/
|
||||
#define IS_HEADER_CHAR(ch) \
|
||||
(ch == CR || ch == LF || ch == 9 || ((unsigned char)ch > 31 && ch != 127))
|
||||
|
||||
#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)
|
||||
|
||||
|
@ -639,6 +645,7 @@ size_t http_parser_execute (http_parser *parser,
|
|||
const char *body_mark = 0;
|
||||
const char *status_mark = 0;
|
||||
enum state p_state = (enum state) parser->state;
|
||||
const unsigned int lenient = parser->lenient_http_headers;
|
||||
|
||||
/* We're in an error state. Don't bother doing anything. */
|
||||
if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
|
||||
|
@ -1000,89 +1007,40 @@ reexecute:
|
|||
UPDATE_STATE(s_req_spaces_before_url);
|
||||
} else if (ch == matcher[parser->index]) {
|
||||
; /* nada */
|
||||
} else if (parser->method == HTTP_CONNECT) {
|
||||
if (parser->index == 1 && ch == 'H') {
|
||||
parser->method = HTTP_CHECKOUT;
|
||||
} else if (parser->index == 2 && ch == 'P') {
|
||||
parser->method = HTTP_COPY;
|
||||
} else {
|
||||
SET_ERRNO(HPE_INVALID_METHOD);
|
||||
goto error;
|
||||
}
|
||||
} else if (parser->method == HTTP_MKCOL) {
|
||||
if (parser->index == 1 && ch == 'O') {
|
||||
parser->method = HTTP_MOVE;
|
||||
} else if (parser->index == 1 && ch == 'E') {
|
||||
parser->method = HTTP_MERGE;
|
||||
} else if (parser->index == 1 && ch == '-') {
|
||||
parser->method = HTTP_MSEARCH;
|
||||
} else if (parser->index == 2 && ch == 'A') {
|
||||
parser->method = HTTP_MKACTIVITY;
|
||||
} else if (parser->index == 3 && ch == 'A') {
|
||||
parser->method = HTTP_MKCALENDAR;
|
||||
} else {
|
||||
SET_ERRNO(HPE_INVALID_METHOD);
|
||||
goto error;
|
||||
}
|
||||
} else if (parser->method == HTTP_SUBSCRIBE) {
|
||||
if (parser->index == 1 && ch == 'E') {
|
||||
parser->method = HTTP_SEARCH;
|
||||
} else {
|
||||
SET_ERRNO(HPE_INVALID_METHOD);
|
||||
goto error;
|
||||
}
|
||||
} else if (parser->method == HTTP_REPORT) {
|
||||
if (parser->index == 2 && ch == 'B') {
|
||||
parser->method = HTTP_REBIND;
|
||||
} else {
|
||||
} else if (IS_ALPHA(ch)) {
|
||||
|
||||
switch (parser->method << 16 | parser->index << 8 | ch) {
|
||||
#define XX(meth, pos, ch, new_meth) \
|
||||
case (HTTP_##meth << 16 | pos << 8 | ch): \
|
||||
parser->method = HTTP_##new_meth; break;
|
||||
|
||||
XX(POST, 1, 'U', PUT)
|
||||
XX(POST, 1, 'A', PATCH)
|
||||
XX(CONNECT, 1, 'H', CHECKOUT)
|
||||
XX(CONNECT, 2, 'P', COPY)
|
||||
XX(MKCOL, 1, 'O', MOVE)
|
||||
XX(MKCOL, 1, 'E', MERGE)
|
||||
XX(MKCOL, 2, 'A', MKACTIVITY)
|
||||
XX(MKCOL, 3, 'A', MKCALENDAR)
|
||||
XX(SUBSCRIBE, 1, 'E', SEARCH)
|
||||
XX(REPORT, 2, 'B', REBIND)
|
||||
XX(POST, 1, 'R', PROPFIND)
|
||||
XX(PROPFIND, 4, 'P', PROPPATCH)
|
||||
XX(PUT, 2, 'R', PURGE)
|
||||
XX(LOCK, 1, 'I', LINK)
|
||||
XX(UNLOCK, 2, 'S', UNSUBSCRIBE)
|
||||
XX(UNLOCK, 2, 'B', UNBIND)
|
||||
XX(UNLOCK, 3, 'I', UNLINK)
|
||||
#undef XX
|
||||
|
||||
default:
|
||||
SET_ERRNO(HPE_INVALID_METHOD);
|
||||
goto error;
|
||||
}
|
||||
} else if (parser->index == 1) {
|
||||
if (parser->method == HTTP_POST) {
|
||||
if (ch == 'R') {
|
||||
parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */
|
||||
} else if (ch == 'U') {
|
||||
parser->method = HTTP_PUT; /* or HTTP_PURGE */
|
||||
} else if (ch == 'A') {
|
||||
parser->method = HTTP_PATCH;
|
||||
} else {
|
||||
SET_ERRNO(HPE_INVALID_METHOD);
|
||||
goto error;
|
||||
}
|
||||
} else if (parser->method == HTTP_LOCK) {
|
||||
if (ch == 'I') {
|
||||
parser->method = HTTP_LINK;
|
||||
} else {
|
||||
SET_ERRNO(HPE_INVALID_METHOD);
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
} else if (parser->index == 2) {
|
||||
if (parser->method == HTTP_PUT) {
|
||||
if (ch == 'R') {
|
||||
parser->method = HTTP_PURGE;
|
||||
} else {
|
||||
SET_ERRNO(HPE_INVALID_METHOD);
|
||||
goto error;
|
||||
}
|
||||
} else if (parser->method == HTTP_UNLOCK) {
|
||||
if (ch == 'S') {
|
||||
parser->method = HTTP_UNSUBSCRIBE;
|
||||
} else if(ch == 'B') {
|
||||
parser->method = HTTP_UNBIND;
|
||||
} else {
|
||||
SET_ERRNO(HPE_INVALID_METHOD);
|
||||
goto error;
|
||||
}
|
||||
} else {
|
||||
SET_ERRNO(HPE_INVALID_METHOD);
|
||||
goto error;
|
||||
}
|
||||
} else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') {
|
||||
parser->method = HTTP_PROPPATCH;
|
||||
} else if (parser->index == 3 && parser->method == HTTP_UNLOCK && ch == 'I') {
|
||||
parser->method = HTTP_UNLINK;
|
||||
} else if (ch == '-' &&
|
||||
parser->index == 1 &&
|
||||
parser->method == HTTP_MKCOL) {
|
||||
parser->method = HTTP_MSEARCH;
|
||||
} else {
|
||||
SET_ERRNO(HPE_INVALID_METHOD);
|
||||
goto error;
|
||||
|
@ -1408,7 +1366,12 @@ reexecute:
|
|||
|| c != CONTENT_LENGTH[parser->index]) {
|
||||
parser->header_state = h_general;
|
||||
} else if (parser->index == sizeof(CONTENT_LENGTH)-2) {
|
||||
if (parser->flags & F_CONTENTLENGTH) {
|
||||
SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
|
||||
goto error;
|
||||
}
|
||||
parser->header_state = h_content_length;
|
||||
parser->flags |= F_CONTENTLENGTH;
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -1560,6 +1523,11 @@ reexecute:
|
|||
REEXECUTE();
|
||||
}
|
||||
|
||||
if (!lenient && !IS_HEADER_CHAR(ch)) {
|
||||
SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
|
||||
goto error;
|
||||
}
|
||||
|
||||
c = LOWER(ch);
|
||||
|
||||
switch (h_state) {
|
||||
|
@ -1727,7 +1695,10 @@ reexecute:
|
|||
|
||||
case s_header_almost_done:
|
||||
{
|
||||
STRICT_CHECK(ch != LF);
|
||||
if (UNLIKELY(ch != LF)) {
|
||||
SET_ERRNO(HPE_LF_EXPECTED);
|
||||
goto error;
|
||||
}
|
||||
|
||||
UPDATE_STATE(s_header_value_lws);
|
||||
break;
|
||||
|
@ -1811,6 +1782,14 @@ reexecute:
|
|||
REEXECUTE();
|
||||
}
|
||||
|
||||
/* Cannot use chunked encoding and a content-length header together
|
||||
per the HTTP specification. */
|
||||
if ((parser->flags & F_CHUNKED) &&
|
||||
(parser->flags & F_CONTENTLENGTH)) {
|
||||
SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
|
||||
goto error;
|
||||
}
|
||||
|
||||
UPDATE_STATE(s_headers_done);
|
||||
|
||||
/* Set this here so that on_headers_complete() callbacks can see it */
|
||||
|
|
|
@ -27,7 +27,7 @@ extern "C" {
|
|||
/* Also update SONAME in the Makefile whenever you change these. */
|
||||
#define HTTP_PARSER_VERSION_MAJOR 2
|
||||
#define HTTP_PARSER_VERSION_MINOR 6
|
||||
#define HTTP_PARSER_VERSION_PATCH 0
|
||||
#define HTTP_PARSER_VERSION_PATCH 2
|
||||
|
||||
#include <sys/types.h>
|
||||
#if defined(_WIN32) && !defined(__MINGW32__) && \
|
||||
|
@ -148,6 +148,7 @@ enum flags
|
|||
, F_TRAILING = 1 << 4
|
||||
, F_UPGRADE = 1 << 5
|
||||
, F_SKIPBODY = 1 << 6
|
||||
, F_CONTENTLENGTH = 1 << 7
|
||||
};
|
||||
|
||||
|
||||
|
@ -190,6 +191,8 @@ enum flags
|
|||
XX(INVALID_HEADER_TOKEN, "invalid character in header") \
|
||||
XX(INVALID_CONTENT_LENGTH, \
|
||||
"invalid character in content-length header") \
|
||||
XX(UNEXPECTED_CONTENT_LENGTH, \
|
||||
"unexpected content-length header") \
|
||||
XX(INVALID_CHUNK_SIZE, \
|
||||
"invalid character in chunk size header") \
|
||||
XX(INVALID_CONSTANT, "invalid constant string") \
|
||||
|
@ -214,10 +217,11 @@ enum http_errno {
|
|||
struct http_parser {
|
||||
/** PRIVATE **/
|
||||
unsigned int type : 2; /* enum http_parser_type */
|
||||
unsigned int flags : 7; /* F_* values from 'flags' enum; semi-public */
|
||||
unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */
|
||||
unsigned int state : 7; /* enum state from http_parser.c */
|
||||
unsigned int header_state : 8; /* enum header_state from http_parser.c */
|
||||
unsigned int index : 8; /* index into current matcher */
|
||||
unsigned int header_state : 7; /* enum header_state from http_parser.c */
|
||||
unsigned int index : 7; /* index into current matcher */
|
||||
unsigned int lenient_http_headers : 1;
|
||||
|
||||
uint32_t nread; /* # bytes read in various scenarios */
|
||||
uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */
|
||||
|
|
|
@ -2444,7 +2444,7 @@ upgrade_message_fix(char *body, const size_t nread, const size_t nmsgs, ...) {
|
|||
va_list ap;
|
||||
size_t i;
|
||||
size_t off = 0;
|
||||
|
||||
|
||||
va_start(ap, nmsgs);
|
||||
|
||||
for (i = 0; i < nmsgs; i++) {
|
||||
|
@ -3270,6 +3270,155 @@ test_simple (const char *buf, enum http_errno err_expected)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
test_invalid_header_content (int req, const char* str)
|
||||
{
|
||||
http_parser parser;
|
||||
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
|
||||
size_t parsed;
|
||||
const char *buf;
|
||||
buf = req ?
|
||||
"GET / HTTP/1.1\r\n" :
|
||||
"HTTP/1.1 200 OK\r\n";
|
||||
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
|
||||
assert(parsed == strlen(buf));
|
||||
|
||||
buf = str;
|
||||
size_t buflen = strlen(buf);
|
||||
|
||||
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
|
||||
if (parsed != buflen) {
|
||||
assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_HEADER_TOKEN);
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(stderr,
|
||||
"\n*** Error expected but none in invalid header content test ***\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
void
|
||||
test_invalid_header_field_content_error (int req)
|
||||
{
|
||||
test_invalid_header_content(req, "Foo: F\01ailure");
|
||||
test_invalid_header_content(req, "Foo: B\02ar");
|
||||
}
|
||||
|
||||
void
|
||||
test_invalid_header_field (int req, const char* str)
|
||||
{
|
||||
http_parser parser;
|
||||
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
|
||||
size_t parsed;
|
||||
const char *buf;
|
||||
buf = req ?
|
||||
"GET / HTTP/1.1\r\n" :
|
||||
"HTTP/1.1 200 OK\r\n";
|
||||
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
|
||||
assert(parsed == strlen(buf));
|
||||
|
||||
buf = str;
|
||||
size_t buflen = strlen(buf);
|
||||
|
||||
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
|
||||
if (parsed != buflen) {
|
||||
assert(HTTP_PARSER_ERRNO(&parser) == HPE_INVALID_HEADER_TOKEN);
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(stderr,
|
||||
"\n*** Error expected but none in invalid header token test ***\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
void
|
||||
test_invalid_header_field_token_error (int req)
|
||||
{
|
||||
test_invalid_header_field(req, "Fo@: Failure");
|
||||
test_invalid_header_field(req, "Foo\01\test: Bar");
|
||||
}
|
||||
|
||||
void
|
||||
test_double_content_length_error (int req)
|
||||
{
|
||||
http_parser parser;
|
||||
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
|
||||
size_t parsed;
|
||||
const char *buf;
|
||||
buf = req ?
|
||||
"GET / HTTP/1.1\r\n" :
|
||||
"HTTP/1.1 200 OK\r\n";
|
||||
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
|
||||
assert(parsed == strlen(buf));
|
||||
|
||||
buf = "Content-Length: 0\r\nContent-Length: 1\r\n\r\n";
|
||||
size_t buflen = strlen(buf);
|
||||
|
||||
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
|
||||
if (parsed != buflen) {
|
||||
assert(HTTP_PARSER_ERRNO(&parser) == HPE_UNEXPECTED_CONTENT_LENGTH);
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(stderr,
|
||||
"\n*** Error expected but none in double content-length test ***\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
void
|
||||
test_chunked_content_length_error (int req)
|
||||
{
|
||||
http_parser parser;
|
||||
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
|
||||
size_t parsed;
|
||||
const char *buf;
|
||||
buf = req ?
|
||||
"GET / HTTP/1.1\r\n" :
|
||||
"HTTP/1.1 200 OK\r\n";
|
||||
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
|
||||
assert(parsed == strlen(buf));
|
||||
|
||||
buf = "Transfer-Encoding: chunked\r\nContent-Length: 1\r\n\r\n";
|
||||
size_t buflen = strlen(buf);
|
||||
|
||||
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
|
||||
if (parsed != buflen) {
|
||||
assert(HTTP_PARSER_ERRNO(&parser) == HPE_UNEXPECTED_CONTENT_LENGTH);
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(stderr,
|
||||
"\n*** Error expected but none in chunked content-length test ***\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
void
|
||||
test_header_cr_no_lf_error (int req)
|
||||
{
|
||||
http_parser parser;
|
||||
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
|
||||
size_t parsed;
|
||||
const char *buf;
|
||||
buf = req ?
|
||||
"GET / HTTP/1.1\r\n" :
|
||||
"HTTP/1.1 200 OK\r\n";
|
||||
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
|
||||
assert(parsed == strlen(buf));
|
||||
|
||||
buf = "Foo: 1\rBar: 1\r\n\r\n";
|
||||
size_t buflen = strlen(buf);
|
||||
|
||||
parsed = http_parser_execute(&parser, &settings_null, buf, buflen);
|
||||
if (parsed != buflen) {
|
||||
assert(HTTP_PARSER_ERRNO(&parser) == HPE_LF_EXPECTED);
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(stderr,
|
||||
"\n*** Error expected but none in header whitespace test ***\n");
|
||||
abort();
|
||||
}
|
||||
|
||||
void
|
||||
test_header_overflow_error (int req)
|
||||
{
|
||||
|
@ -3696,6 +3845,18 @@ main (void)
|
|||
test_header_content_length_overflow_error();
|
||||
test_chunk_content_length_overflow_error();
|
||||
|
||||
//// HEADER FIELD CONDITIONS
|
||||
test_double_content_length_error(HTTP_REQUEST);
|
||||
test_chunked_content_length_error(HTTP_REQUEST);
|
||||
test_header_cr_no_lf_error(HTTP_REQUEST);
|
||||
test_invalid_header_field_token_error(HTTP_REQUEST);
|
||||
test_invalid_header_field_content_error(HTTP_REQUEST);
|
||||
test_double_content_length_error(HTTP_RESPONSE);
|
||||
test_chunked_content_length_error(HTTP_RESPONSE);
|
||||
test_header_cr_no_lf_error(HTTP_RESPONSE);
|
||||
test_invalid_header_field_token_error(HTTP_RESPONSE);
|
||||
test_invalid_header_field_content_error(HTTP_RESPONSE);
|
||||
|
||||
//// RESPONSES
|
||||
|
||||
for (i = 0; i < response_count; i++) {
|
||||
|
@ -3772,6 +3933,11 @@ main (void)
|
|||
|
||||
test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION);
|
||||
|
||||
// Extended characters - see nodejs/test/parallel/test-http-headers-obstext.js
|
||||
test_simple("GET / HTTP/1.1\r\n"
|
||||
"Test: Düsseldorf\r\n",
|
||||
HPE_OK);
|
||||
|
||||
// Well-formed but incomplete
|
||||
test_simple("GET / HTTP/1.1\r\n"
|
||||
"Content-Type: text/plain\r\n"
|
||||
|
|
Loading…
Reference in New Issue