From 8825c74e7aea20f24bc2f6c165cb3ce3d5011b77 Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 23 Jun 2010 21:10:13 -0700 Subject: [PATCH] Upgrade http-parser --- deps/http_parser/http_parser.c | 249 ++++++++++++--------------------- deps/http_parser/http_parser.h | 47 ++++--- deps/http_parser/test.c | 149 ++++++++++++++++++-- 3 files changed, 251 insertions(+), 194 deletions(-) diff --git a/deps/http_parser/http_parser.c b/deps/http_parser/http_parser.c index 7556d92099..a2a07f8d30 100644 --- a/deps/http_parser/http_parser.c +++ b/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; } - diff --git a/deps/http_parser/http_parser.h b/deps/http_parser/http_parser.h index a4abf3208f..1895d7979e 100644 --- a/deps/http_parser/http_parser.h +++ b/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 } diff --git a/deps/http_parser/test.c b/deps/http_parser/test.c index a9746adb54..7d6a13c39b 100644 --- a/deps/http_parser/test.c +++ b/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 */