Browse Source

Upgrade http-parser

v0.7.4-release
Ryan Dahl 15 years ago
parent
commit
8825c74e7a
  1. 243
      deps/http_parser/http_parser.c
  2. 47
      deps/http_parser/http_parser.h
  3. 149
      deps/http_parser/test.c

243
deps/http_parser/http_parser.c

@ -77,7 +77,30 @@ do { \
#define CLOSE "close"
static const unsigned char lowcase[] =
static const char *method_strings[] =
{ "DELETE"
, "GET"
, "HEAD"
, "POST"
, "PUT"
, "CONNECT"
, "OPTIONS"
, "TRACE"
, "COPY"
, "LOCK"
, "MKCOL"
, "MOVE"
, "PROPFIND"
, "PROPPATCH"
, "UNLOCK"
, "REPORT"
, "MKACTIVITY"
, "CHECKOUT"
, "MERGE"
};
static const char lowcase[256] =
"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
"\0\0\0\0\0\0\0\0\0\0\0\0\0-\0\0" "0123456789\0\0\0\0\0\0"
"\0abcdefghijklmnopqrstuvwxyz\0\0\0\0_"
@ -248,35 +271,6 @@ enum flags
#endif
#define ngx_str3_cmp(m, c0, c1, c2) \
m[0] == c0 && m[1] == c1 && m[2] == c2
#define ngx_str3Ocmp(m, c0, c1, c2, c3) \
m[0] == c0 && m[2] == c2 && m[3] == c3
#define ngx_str4cmp(m, c0, c1, c2, c3) \
m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3
#define ngx_str5cmp(m, c0, c1, c2, c3, c4) \
m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 && m[4] == c4
#define ngx_str6cmp(m, c0, c1, c2, c3, c4, c5) \
m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 \
&& m[4] == c4 && m[5] == c5
#define ngx_str7_cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \
m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 \
&& m[4] == c4 && m[5] == c5 && m[6] == c6
#define ngx_str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7) \
m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 \
&& m[4] == c4 && m[5] == c5 && m[6] == c6 && m[7] == c7
#define ngx_str9cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8) \
m[0] == c0 && m[1] == c1 && m[2] == c2 && m[3] == c3 \
&& m[4] == c4 && m[5] == c5 && m[6] == c6 && m[7] == c7 && m[8] == c8
size_t http_parser_execute (http_parser *parser,
const http_parser_settings *settings,
const char *data,
@ -327,9 +321,10 @@ size_t http_parser_execute (http_parser *parser,
for (p=data, pe=data+len; p != pe; p++) {
ch = *p;
if (++nread > HTTP_MAX_HEADER_SIZE && PARSING_HEADER(state)) {
if (PARSING_HEADER(state)) {
++nread;
/* Buffer overflow attack */
goto error;
if (nread > HTTP_MAX_HEADER_SIZE) goto error;
}
switch (state) {
@ -353,10 +348,7 @@ size_t http_parser_execute (http_parser *parser,
state = s_res_or_resp_H;
else {
parser->type = HTTP_REQUEST;
if (ch < 'A' || 'Z' < ch) goto error;
parser->buffer[0] = ch;
index = 0;
state = s_req_method;
goto start_req_method_assign;
}
break;
}
@ -366,12 +358,10 @@ size_t http_parser_execute (http_parser *parser,
parser->type = HTTP_RESPONSE;
state = s_res_HT;
} else {
if (ch < 'A' || 'Z' < ch) goto error;
if (ch != 'E') goto error;
parser->type = HTTP_REQUEST;
parser->method = (enum http_method) 0;
parser->buffer[0] = 'H';
parser->buffer[1] = ch;
index = 1;
parser->method = HTTP_HEAD;
index = 2;
state = s_req_method;
}
break;
@ -534,128 +524,64 @@ size_t http_parser_execute (http_parser *parser,
if (ch < 'A' || 'Z' < ch) goto error;
start_req_method_assign:
parser->method = (enum http_method) 0;
index = 0;
parser->buffer[0] = ch;
index = 1;
switch (ch) {
case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break;
case 'D': parser->method = HTTP_DELETE; break;
case 'G': parser->method = HTTP_GET; break;
case 'H': parser->method = HTTP_HEAD; break;
case 'L': parser->method = HTTP_LOCK; break;
case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE */ break;
case 'O': parser->method = HTTP_OPTIONS; break;
case 'P': parser->method = HTTP_POST; /* or PROPFIND or PROPPATCH or PUT */ break;
case 'R': parser->method = HTTP_REPORT; break;
case 'T': parser->method = HTTP_TRACE; break;
case 'U': parser->method = HTTP_UNLOCK; break;
default: goto error;
}
state = s_req_method;
break;
}
case s_req_method:
if (ch == ' ') {
assert(index+1 < HTTP_PARSER_MAX_METHOD_LEN);
parser->buffer[index+1] = '\0';
switch (index+1) {
case 3:
if (ngx_str3_cmp(parser->buffer, 'G', 'E', 'T')) {
parser->method = HTTP_GET;
break;
}
if (ngx_str3_cmp(parser->buffer, 'P', 'U', 'T')) {
parser->method = HTTP_PUT;
break;
}
break;
case 4:
if (ngx_str4cmp(parser->buffer, 'P', 'O', 'S', 'T')) {
parser->method = HTTP_POST;
break;
}
if (ngx_str4cmp(parser->buffer, 'H', 'E', 'A', 'D')) {
parser->method = HTTP_HEAD;
break;
}
{
if (ch == '\0')
goto error;
if (ngx_str4cmp(parser->buffer, 'C', 'O', 'P', 'Y')) {
const char *matcher = method_strings[parser->method];
if (ch == ' ' && matcher[index] == '\0') {
state = s_req_spaces_before_url;
} else if (ch == matcher[index]) {
; // nada
} else if (parser->method == HTTP_CONNECT) {
if (index == 1 && ch == 'H') {
parser->method = HTTP_CHECKOUT;
} else if (index == 2 && ch == 'P') {
parser->method = HTTP_COPY;
break;
}
if (ngx_str4cmp(parser->buffer, 'M', 'O', 'V', 'E')) {
} else if (parser->method == HTTP_MKCOL) {
if (index == 1 && ch == 'O') {
parser->method = HTTP_MOVE;
break;
}
break;
case 5:
if (ngx_str5cmp(parser->buffer, 'M', 'K', 'C', 'O', 'L')) {
parser->method = HTTP_MKCOL;
break;
}
if (ngx_str5cmp(parser->buffer, 'T', 'R', 'A', 'C', 'E')) {
parser->method = HTTP_TRACE;
break;
}
break;
case 6:
if (ngx_str6cmp(parser->buffer, 'D', 'E', 'L', 'E', 'T', 'E')) {
parser->method = HTTP_DELETE;
break;
}
if (ngx_str6cmp(parser->buffer, 'U', 'N', 'L', 'O', 'C', 'K')) {
parser->method = HTTP_UNLOCK;
break;
}
break;
case 7:
if (ngx_str7_cmp(parser->buffer,
'O', 'P', 'T', 'I', 'O', 'N', 'S', '\0')) {
parser->method = HTTP_OPTIONS;
break;
}
if (ngx_str7_cmp(parser->buffer,
'C', 'O', 'N', 'N', 'E', 'C', 'T', '\0')) {
parser->method = HTTP_CONNECT;
break;
}
break;
case 8:
if (ngx_str8cmp(parser->buffer,
'P', 'R', 'O', 'P', 'F', 'I', 'N', 'D')) {
parser->method = HTTP_PROPFIND;
break;
}
break;
case 9:
if (ngx_str9cmp(parser->buffer,
'P', 'R', 'O', 'P', 'P', 'A', 'T', 'C', 'H')) {
} else if (index == 1 && ch == 'E') {
parser->method = HTTP_MERGE;
} else if (index == 2 && ch == 'A') {
parser->method = HTTP_MKACTIVITY;
}
} else if (index == 1 && parser->method == HTTP_POST && ch == 'R') {
parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */
} else if (index == 1 && parser->method == HTTP_POST && ch == 'U') {
parser->method = HTTP_PUT;
} else if (index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') {
parser->method = HTTP_PROPPATCH;
break;
}
break;
}
state = s_req_spaces_before_url;
break;
}
if (ch < 'A' || 'Z' < ch) goto error;
if (++index >= HTTP_PARSER_MAX_METHOD_LEN - 1) {
} else {
goto error;
}
parser->buffer[index] = ch;
++index;
break;
}
case s_req_spaces_before_url:
{
if (ch == ' ') break;
@ -1069,7 +995,7 @@ size_t http_parser_execute (http_parser *parser,
case s_header_field:
{
c = lowcase[(int)ch];
c = lowcase[(unsigned char)ch];
if (c) {
switch (header_state) {
@ -1205,7 +1131,7 @@ size_t http_parser_execute (http_parser *parser,
state = s_header_value;
index = 0;
c = lowcase[(int)ch];
c = lowcase[(unsigned char)ch];
if (!c) {
if (ch == CR) {
@ -1266,7 +1192,7 @@ size_t http_parser_execute (http_parser *parser,
case s_header_value:
{
c = lowcase[(int)ch];
c = lowcase[(unsigned char)ch];
if (!c) {
if (ch == CR) {
@ -1378,7 +1304,6 @@ size_t http_parser_execute (http_parser *parser,
break;
}
parser->body_read = 0;
nread = 0;
if (parser->flags & F_UPGRADE) parser->upgrade = 1;
@ -1439,12 +1364,12 @@ size_t http_parser_execute (http_parser *parser,
}
case s_body_identity:
to_read = MIN(pe - p, (ssize_t)(parser->content_length - parser->body_read));
to_read = MIN(pe - p, (ssize_t)parser->content_length);
if (to_read > 0) {
if (settings->on_body) settings->on_body(parser, p, to_read);
p += to_read - 1;
parser->body_read += to_read;
if (parser->body_read == parser->content_length) {
parser->content_length -= to_read;
if (parser->content_length == 0) {
CALLBACK2(message_complete);
state = NEW_MESSAGE();
}
@ -1457,7 +1382,6 @@ size_t http_parser_execute (http_parser *parser,
if (to_read > 0) {
if (settings->on_body) settings->on_body(parser, p, to_read);
p += to_read - 1;
parser->body_read += to_read;
}
break;
@ -1598,6 +1522,12 @@ http_should_keep_alive (http_parser *parser)
}
const char * http_method_str (enum http_method m)
{
return method_strings[m];
}
void
http_parser_init (http_parser *parser, enum http_parser_type t)
{
@ -1606,4 +1536,3 @@ http_parser_init (http_parser *parser, enum http_parser_type t)
parser->nread = 0;
parser->upgrade = 0;
}

47
deps/http_parser/http_parser.h

@ -63,29 +63,30 @@ typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
typedef int (*http_cb) (http_parser*);
/* Should be at least one longer than the longest request method */
#define HTTP_PARSER_MAX_METHOD_LEN 10
/* Request Methods */
enum http_method
{ HTTP_DELETE = 0x0001
, HTTP_GET = 0x0002
, HTTP_HEAD = 0x0004
, HTTP_POST = 0x0008
, HTTP_PUT = 0x0010
{ HTTP_DELETE = 0
, HTTP_GET
, HTTP_HEAD
, HTTP_POST
, HTTP_PUT
/* pathological */
, HTTP_CONNECT = 0x0020
, HTTP_OPTIONS = 0x0040
, HTTP_TRACE = 0x0080
, HTTP_CONNECT
, HTTP_OPTIONS
, HTTP_TRACE
/* webdav */
, HTTP_COPY = 0x0100
, HTTP_LOCK = 0x0200
, HTTP_MKCOL = 0x0400
, HTTP_MOVE = 0x0800
, HTTP_PROPFIND = 0x1000
, HTTP_PROPPATCH = 0x2000
, HTTP_UNLOCK = 0x4000
, HTTP_COPY
, HTTP_LOCK
, HTTP_MKCOL
, HTTP_MOVE
, HTTP_PROPFIND
, HTTP_PROPPATCH
, HTTP_UNLOCK
/* subversion */
, HTTP_REPORT
, HTTP_MKACTIVITY
, HTTP_CHECKOUT
, HTTP_MERGE
};
@ -102,15 +103,13 @@ struct http_parser {
char flags;
size_t nread;
ssize_t body_read;
ssize_t content_length;
/** READ-ONLY **/
unsigned short status_code; /* responses only */
unsigned short method; /* requests only */
unsigned short http_major;
unsigned short http_minor;
char buffer[HTTP_PARSER_MAX_METHOD_LEN];
unsigned short status_code; /* responses only */
unsigned char method; /* requests only */
/* 1 = Upgrade header was present and the parser has exited because of that.
* 0 = No upgrade header present.
@ -155,6 +154,8 @@ size_t http_parser_execute(http_parser *parser,
*/
int http_should_keep_alive(http_parser *parser);
/* Returns a string version of the HTTP method. */
const char *http_method_str(enum http_method);
#ifdef __cplusplus
}

149
deps/http_parser/test.c

@ -519,6 +519,25 @@ const struct message requests[] =
,.body= ""
}
#define REPORT_REQ 18
, {.name= "report request"
,.type= HTTP_REQUEST
,.raw= "REPORT /test HTTP/1.1\r\n"
"\r\n"
,.should_keep_alive= TRUE
,.message_complete_on_eof= FALSE
,.http_major= 1
,.http_minor= 1
,.method= HTTP_REPORT
,.query_string= ""
,.fragment= ""
,.request_path= "/test"
,.request_url= "/test"
,.num_headers= 0
,.headers= {}
,.body= ""
}
, {.name= NULL } /* sentinel */
};
@ -932,6 +951,19 @@ static http_parser_settings settings_count_body =
,.on_message_complete = message_complete_cb
};
static http_parser_settings settings_null =
{.on_message_begin = 0
,.on_header_field = 0
,.on_header_value = 0
,.on_path = 0
,.on_url = 0
,.on_fragment = 0
,.on_query_string = 0
,.on_body = 0
,.on_headers_complete = 0
,.on_message_complete = 0
};
void
parser_init (enum http_parser_type type)
{
@ -1168,23 +1200,80 @@ test_message_count_body (const struct message *message)
}
void
test_error (const char *buf)
test_simple (const char *buf, int should_pass)
{
parser_init(HTTP_REQUEST);
size_t parsed;
int pass;
parsed = parse(buf, strlen(buf));
if (parsed != strlen(buf)) goto out;
pass = (parsed == strlen(buf));
parsed = parse(NULL, 0);
if (parsed != 0) goto out;
pass &= (parsed == 0);
parser_free();
if (pass != should_pass) {
fprintf(stderr, "\n*** test_simple expected %s ***\n\n%s", should_pass ? "success" : "error", buf);
exit(1);
}
}
void
test_header_overflow_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.0 200 OK\r\n";
parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
assert(parsed == strlen(buf));
buf = "header-key: header-value\r\n";
int i;
for (i = 0; i < 10000; i++) {
if (http_parser_execute(&parser, &settings_null, buf, strlen(buf)) != strlen(buf)) {
//fprintf(stderr, "error found on iter %d\n", i);
return;
}
}
fprintf(stderr, "\n*** Error expected but none found ***\n\n%s", buf);
fprintf(stderr, "\n*** Error expected but none in header overflow test ***\n");
exit(1);
}
void
test_no_overflow_long_body (int req, size_t length)
{
http_parser parser;
http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
size_t parsed;
size_t i;
char buf1[3000];
size_t buf1len = sprintf(buf1, "%s\r\nConnection: Keep-Alive\r\nContent-Length: %zu\r\n\r\n",
req ? "POST / HTTP/1.0" : "HTTP/1.0 200 OK", length);
parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len);
if (parsed != buf1len)
goto err;
for (i = 0; i < length; i++) {
char foo = 'a';
parsed = http_parser_execute(&parser, &settings_null, &foo, 1);
if (parsed != 1)
goto err;
}
parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len);
if (parsed != buf1len) goto err;
return;
out:
parser_free();
err:
fprintf(stderr,
"\n*** error in test_no_overflow_long_body %s of length %zu ***\n",
req ? "REQUEST" : "RESPONSE",
length);
exit(1);
}
void
@ -1410,6 +1499,16 @@ main (void)
for (request_count = 0; requests[request_count].name; request_count++);
for (response_count = 0; responses[response_count].name; response_count++);
//// OVERFLOW CONDITIONS
test_header_overflow_error(HTTP_REQUEST);
test_no_overflow_long_body(HTTP_REQUEST, 1000);
test_no_overflow_long_body(HTTP_REQUEST, 100000);
test_header_overflow_error(HTTP_RESPONSE);
test_no_overflow_long_body(HTTP_RESPONSE, 1000);
test_no_overflow_long_body(HTTP_RESPONSE, 100000);
//// RESPONSES
for (i = 0; i < response_count; i++) {
@ -1476,8 +1575,36 @@ main (void)
/// REQUESTS
test_error("hello world");
test_error("GET / HTP/1.1\r\n\r\n");
test_simple("hello world", 0);
test_simple("GET / HTP/1.1\r\n\r\n", 0);
test_simple("ASDF / HTTP/1.1\r\n\r\n", 0);
test_simple("PROPPATCHA / HTTP/1.1\r\n\r\n", 0);
test_simple("GETA / HTTP/1.1\r\n\r\n", 0);
static const char *all_methods[] = {
"DELETE",
"GET",
"HEAD",
"POST",
"PUT",
"CONNECT",
"OPTIONS",
"TRACE",
"COPY",
"LOCK",
"MKCOL",
"MOVE",
"PROPFIND",
"PROPPATCH",
"UNLOCK",
0 };
const char **this_method;
for (this_method = all_methods; *this_method; this_method++) {
char buf[200];
sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method);
test_simple(buf, 1);
}
const char *dumbfuck2 =
"GET / HTTP/1.1\r\n"
@ -1514,7 +1641,7 @@ main (void)
"\tRA==\r\n"
"\t-----END CERTIFICATE-----\r\n"
"\r\n";
test_error(dumbfuck2);
test_simple(dumbfuck2, 0);
#if 0
// NOTE(Wed Nov 18 11:57:27 CET 2009) this seems okay. we just read body
@ -1526,7 +1653,7 @@ main (void)
"Accept: */*\r\n"
"\r\n"
"HELLO";
test_error(bad_get_no_headers_no_body);
test_simple(bad_get_no_headers_no_body, 0);
#endif
/* TODO sending junk and large headers gets rejected */

Loading…
Cancel
Save