Permalink
Browse files

Upgrade http-parser

  • Loading branch information...
1 parent 2c0d91b commit 8825c74e7aea20f24bc2f6c165cb3ce3d5011b77 @ry ry committed Jun 24, 2010
Showing with 251 additions and 194 deletions.
  1. +89 −160 deps/http_parser/http_parser.c
  2. +24 −23 deps/http_parser/http_parser.h
  3. +138 −11 deps/http_parser/test.c
View
249 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 (ngx_str4cmp(parser->buffer, 'C', 'O', 'P', 'Y')) {
- parser->method = HTTP_COPY;
- break;
- }
-
- if (ngx_str4cmp(parser->buffer, 'M', 'O', 'V', 'E')) {
- 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')) {
- parser->method = HTTP_PROPPATCH;
- break;
- }
+ {
+ if (ch == '\0')
+ goto error;
- break;
- }
+ const char *matcher = method_strings[parser->method];
+ if (ch == ' ' && matcher[index] == '\0') {
state = s_req_spaces_before_url;
- break;
- }
-
- if (ch < 'A' || 'Z' < ch) goto error;
-
- if (++index >= HTTP_PARSER_MAX_METHOD_LEN - 1) {
+ } 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;
+ }
+ } else if (parser->method == HTTP_MKCOL) {
+ if (index == 1 && ch == 'O') {
+ parser->method = HTTP_MOVE;
+ } 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;
+ } 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;
}
-
View
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
}
View
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);
- fprintf(stderr, "\n*** Error expected but none found ***\n\n%s", buf);
+ 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 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 */

0 comments on commit 8825c74

Please sign in to comment.