diff options
Diffstat (limited to 'deps')
-rw-r--r-- | deps/http_parser/Makefile | 38 | ||||
-rw-r--r-- | deps/http_parser/README.md | 4 | ||||
-rw-r--r-- | deps/http_parser/http_parser.c | 334 | ||||
-rw-r--r-- | deps/http_parser/http_parser.gyp | 50 | ||||
-rw-r--r-- | deps/http_parser/http_parser.h | 94 | ||||
-rw-r--r-- | deps/http_parser/test.c | 376 |
6 files changed, 608 insertions, 288 deletions
diff --git a/deps/http_parser/Makefile b/deps/http_parser/Makefile index 4eceeaae757..efac2167f05 100644 --- a/deps/http_parser/Makefile +++ b/deps/http_parser/Makefile @@ -1,39 +1,45 @@ -CPPFLAGS?=-Wall -Wextra -Werror -I. -OPT_DEBUG=$(CPPFLAGS) -O0 -g -DHTTP_PARSER_STRICT=1 -OPT_FAST=$(CPPFLAGS) -O3 -DHTTP_PARSER_STRICT=0 - CC?=gcc AR?=ar +CPPFLAGS += -I. +CPPFLAGS_DEBUG = $(CPPFLAGS) -DHTTP_PARSER_STRICT=1 -DHTTP_PARSER_DEBUG=1 +CPPFLAGS_DEBUG += $(CPPFLAGS_DEBUG_EXTRA) +CPPFLAGS_FAST = $(CPPFLAGS) -DHTTP_PARSER_STRICT=0 -DHTTP_PARSER_DEBUG=0 +CPPFLAGS_FAST += $(CPPFLAGS_FAST_EXTRA) + +CFLAGS += -Wall -Wextra -Werror +CFLAGS_DEBUG = $(CFLAGS) -O0 -g $(CFLAGS_DEBUG_EXTRA) +CFLAGS_FAST = $(CFLAGS) -O3 $(CFLAGS_FAST_EXTRA) + test: test_g test_fast ./test_g ./test_fast test_g: http_parser_g.o test_g.o - $(CC) $(OPT_DEBUG) http_parser_g.o test_g.o -o $@ + $(CC) $(CFLAGS_DEBUG) $(LDFLAGS) http_parser_g.o test_g.o -o $@ test_g.o: test.c http_parser.h Makefile - $(CC) $(OPT_DEBUG) -c test.c -o $@ - -test.o: test.c http_parser.h Makefile - $(CC) $(OPT_FAST) -c test.c -o $@ + $(CC) $(CPPFLAGS_DEBUG) $(CFLAGS_DEBUG) -c test.c -o $@ http_parser_g.o: http_parser.c http_parser.h Makefile - $(CC) $(OPT_DEBUG) -c http_parser.c -o $@ + $(CC) $(CPPFLAGS_DEBUG) $(CFLAGS_DEBUG) -c http_parser.c -o $@ -test-valgrind: test_g - valgrind ./test_g +test_fast: http_parser.o test.o http_parser.h + $(CC) $(CFLAGS_FAST) $(LDFLAGS) http_parser.o test.o -o $@ -http_parser.o: http_parser.c http_parser.h Makefile - $(CC) $(OPT_FAST) -c http_parser.c +test.o: test.c http_parser.h Makefile + $(CC) $(CPPFLAGS_FAST) $(CFLAGS_FAST) -c test.c -o $@ -test_fast: http_parser.o test.c http_parser.h - $(CC) $(OPT_FAST) http_parser.o test.c -o $@ +http_parser.o: http_parser.c http_parser.h Makefile + $(CC) $(CPPFLAGS_FAST) $(CFLAGS_FAST) -c http_parser.c test-run-timed: test_fast while(true) do time ./test_fast > /dev/null; done +test-valgrind: test_g + valgrind ./test_g + package: http_parser.o $(AR) rcs libhttp_parser.a http_parser.o diff --git a/deps/http_parser/README.md b/deps/http_parser/README.md index 72332fb35bd..405dd5f342c 100644 --- a/deps/http_parser/README.md +++ b/deps/http_parser/README.md @@ -24,7 +24,7 @@ The parser extracts the following information from HTTP messages: * Response status code * Transfer-Encoding * HTTP version - * Request path, query string, fragment + * Request URL * Message body @@ -126,7 +126,7 @@ There are two types of callbacks: * notification `typedef int (*http_cb) (http_parser*);` Callbacks: on_message_begin, on_headers_complete, on_message_complete. * data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);` - Callbacks: (requests only) on_path, on_query_string, on_uri, on_fragment, + Callbacks: (requests only) on_uri, (common) on_header_field, on_header_value, on_body; Callbacks must return 0 on success. Returning a non-zero value indicates diff --git a/deps/http_parser/http_parser.c b/deps/http_parser/http_parser.c index 1453d411b0c..e905747a320 100644 --- a/deps/http_parser/http_parser.c +++ b/deps/http_parser/http_parser.c @@ -31,10 +31,27 @@ #endif +#if HTTP_PARSER_DEBUG +#define SET_ERRNO(e) \ +do { \ + parser->http_errno = (e); \ + parser->error_lineno = __LINE__; \ +} while (0) +#else +#define SET_ERRNO(e) \ +do { \ + parser->http_errno = (e); \ +} while(0) +#endif + + #define CALLBACK2(FOR) \ do { \ if (settings->on_##FOR) { \ - if (0 != settings->on_##FOR(parser)) return (p - data); \ + if (0 != settings->on_##FOR(parser)) { \ + SET_ERRNO(HPE_CB_##FOR); \ + return (p - data); \ + } \ } \ } while (0) @@ -44,7 +61,7 @@ do { \ FOR##_mark = p; \ } while (0) -#define CALLBACK_NOCLEAR(FOR) \ +#define CALLBACK(FOR) \ do { \ if (FOR##_mark) { \ if (settings->on_##FOR) { \ @@ -52,20 +69,15 @@ do { \ FOR##_mark, \ p - FOR##_mark)) \ { \ + SET_ERRNO(HPE_CB_##FOR); \ return (p - data); \ } \ } \ + FOR##_mark = NULL; \ } \ } while (0) -#define CALLBACK(FOR) \ -do { \ - CALLBACK_NOCLEAR(FOR); \ - FOR##_mark = NULL; \ -} while (0) - - #define PROXY_CONNECTION "proxy-connection" #define CONNECTION "connection" #define CONTENT_LENGTH "content-length" @@ -241,6 +253,7 @@ enum state , s_header_field , s_header_value_start , s_header_value + , s_header_value_lws , s_header_almost_done @@ -299,7 +312,7 @@ enum header_states #define LF '\n' #define LOWER(c) (unsigned char)(c | 0x20) #define TOKEN(c) (tokens[(unsigned char)c]) -#define IS_ALPHA(c) ((c) >= 'a' && (c) <= 'z') +#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') #define IS_NUM(c) ((c) >= '0' && (c) <= '9') #define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) @@ -318,7 +331,13 @@ enum header_states #if HTTP_PARSER_STRICT -# define STRICT_CHECK(cond) if (cond) goto error +# define STRICT_CHECK(cond) \ +do { \ + if (cond) { \ + SET_ERRNO(HPE_STRICT); \ + goto error; \ + } \ +} while (0) # define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) #else # define STRICT_CHECK(cond) @@ -326,20 +345,39 @@ enum header_states #endif +/* Map errno values to strings for human-readable output */ +#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, +static struct { + const char *name; + const char *description; +} http_strerror_tab[] = { + HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) +}; +#undef HTTP_STRERROR_GEN + + size_t http_parser_execute (http_parser *parser, const http_parser_settings *settings, const char *data, size_t len) { char c, ch; + int8_t unhex_val; const char *p = data, *pe; int64_t to_read; - - enum state state = (enum state) parser->state; - enum header_states header_state = (enum header_states) parser->header_state; + enum state state; + enum header_states header_state; uint64_t index = parser->index; uint64_t nread = parser->nread; + /* We're in an error state. Don't bother doing anything. */ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + return 0; + } + + state = (enum state) parser->state; + header_state = (enum header_states) parser->header_state; + if (len == 0) { switch (state) { case s_body_identity_eof: @@ -353,7 +391,8 @@ size_t http_parser_execute (http_parser *parser, return 0; default: - return 1; // error + SET_ERRNO(HPE_INVALID_EOF_STATE); + return 1; } } @@ -362,21 +401,12 @@ size_t http_parser_execute (http_parser *parser, separated. */ const char *header_field_mark = 0; const char *header_value_mark = 0; - const char *fragment_mark = 0; - const char *query_string_mark = 0; - const char *path_mark = 0; const char *url_mark = 0; if (state == s_header_field) header_field_mark = data; if (state == s_header_value) header_value_mark = data; - if (state == s_req_fragment) - fragment_mark = data; - if (state == s_req_query_string) - query_string_mark = data; - if (state == s_req_path) - path_mark = data; if (state == s_req_path || state == s_req_schema || state == s_req_schema_slash || state == s_req_schema_slash_slash || state == s_req_port || state == s_req_query_string_start || state == s_req_query_string @@ -390,7 +420,10 @@ size_t http_parser_execute (http_parser *parser, if (PARSING_HEADER(state)) { ++nread; /* Buffer overflow attack */ - if (nread > HTTP_MAX_HEADER_SIZE) goto error; + if (nread > HTTP_MAX_HEADER_SIZE) { + SET_ERRNO(HPE_HEADER_OVERFLOW); + goto error; + } } switch (state) { @@ -399,6 +432,7 @@ size_t http_parser_execute (http_parser *parser, /* this state is used after a 'Connection: close' message * the parser will error out if it reads another message */ + SET_ERRNO(HPE_CLOSED_CONNECTION); goto error; case s_start_req_or_res: @@ -424,7 +458,11 @@ size_t http_parser_execute (http_parser *parser, parser->type = HTTP_RESPONSE; state = s_res_HT; } else { - if (ch != 'E') goto error; + if (ch != 'E') { + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + parser->type = HTTP_REQUEST; parser->method = HTTP_HEAD; index = 2; @@ -449,6 +487,7 @@ size_t http_parser_execute (http_parser *parser, break; default: + SET_ERRNO(HPE_INVALID_CONSTANT); goto error; } break; @@ -475,7 +514,11 @@ size_t http_parser_execute (http_parser *parser, break; case s_res_first_http_major: - if (ch < '1' || ch > '9') goto error; + if (ch < '1' || ch > '9') { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + parser->http_major = ch - '0'; state = s_res_http_major; break; @@ -488,18 +531,29 @@ size_t http_parser_execute (http_parser *parser, break; } - if (!IS_NUM(ch)) goto error; + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } parser->http_major *= 10; parser->http_major += ch - '0'; - if (parser->http_major > 999) goto error; + if (parser->http_major > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + break; } /* first digit of minor HTTP version */ case s_res_first_http_minor: - if (!IS_NUM(ch)) goto error; + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + parser->http_minor = ch - '0'; state = s_res_http_minor; break; @@ -512,12 +566,19 @@ size_t http_parser_execute (http_parser *parser, break; } - if (!IS_NUM(ch)) goto error; + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } parser->http_minor *= 10; parser->http_minor += ch - '0'; - if (parser->http_minor > 999) goto error; + if (parser->http_minor > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + break; } @@ -527,6 +588,8 @@ size_t http_parser_execute (http_parser *parser, if (ch == ' ') { break; } + + SET_ERRNO(HPE_INVALID_STATUS); goto error; } parser->status_code = ch - '0'; @@ -548,6 +611,7 @@ size_t http_parser_execute (http_parser *parser, state = s_header_field_start; break; default: + SET_ERRNO(HPE_INVALID_STATUS); goto error; } break; @@ -556,7 +620,11 @@ size_t http_parser_execute (http_parser *parser, parser->status_code *= 10; parser->status_code += ch - '0'; - if (parser->status_code > 999) goto error; + if (parser->status_code > 999) { + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + break; } @@ -588,7 +656,10 @@ size_t http_parser_execute (http_parser *parser, CALLBACK2(message_begin); - if (!IS_ALPHA(LOWER(ch))) goto error; + if (!IS_ALPHA(ch)) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } start_req_method_assign: parser->method = (enum http_method) 0; @@ -609,7 +680,9 @@ size_t http_parser_execute (http_parser *parser, case 'S': parser->method = HTTP_SUBSCRIBE; break; case 'T': parser->method = HTTP_TRACE; break; case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break; - default: goto error; + default: + SET_ERRNO(HPE_INVALID_METHOD); + goto error; } state = s_req_method; break; @@ -617,8 +690,10 @@ size_t http_parser_execute (http_parser *parser, case s_req_method: { - if (ch == '\0') + if (ch == '\0') { + SET_ERRNO(HPE_INVALID_METHOD); goto error; + } const char *matcher = method_strings[parser->method]; if (ch == ' ' && matcher[index] == '\0') { @@ -630,6 +705,8 @@ size_t http_parser_execute (http_parser *parser, parser->method = HTTP_CHECKOUT; } else if (index == 2 && ch == 'P') { parser->method = HTTP_COPY; + } else { + goto error; } } else if (parser->method == HTTP_MKCOL) { if (index == 1 && ch == 'O') { @@ -640,18 +717,25 @@ size_t http_parser_execute (http_parser *parser, parser->method = HTTP_MSEARCH; } else if (index == 2 && ch == 'A') { parser->method = HTTP_MKACTIVITY; + } else { + goto error; + } + } else if (index == 1 && parser->method == HTTP_POST) { + if (ch == 'R') { + parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */ + } else if (ch == 'U') { + parser->method = HTTP_PUT; + } else if (ch == 'A') { + parser->method = HTTP_PATCH; + } else { + goto error; } - } 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 == 1 && parser->method == HTTP_POST && ch == 'A') { - parser->method = HTTP_PATCH; } else if (index == 2 && parser->method == HTTP_UNLOCK && ch == 'S') { parser->method = HTTP_UNSUBSCRIBE; } else if (index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') { parser->method = HTTP_PROPPATCH; } else { + SET_ERRNO(HPE_INVALID_METHOD); goto error; } @@ -664,13 +748,10 @@ size_t http_parser_execute (http_parser *parser, if (ch == '/' || ch == '*') { MARK(url); - MARK(path); state = s_req_path; break; } - c = LOWER(ch); - /* Proxied requests are followed by scheme of an absolute URI (alpha). * CONNECT is followed by a hostname, which begins with alphanum. * All other methods are followed by '/' or '*' (handled above). @@ -681,20 +762,20 @@ size_t http_parser_execute (http_parser *parser, break; } + SET_ERRNO(HPE_INVALID_URL); goto error; } case s_req_schema: { - c = LOWER(ch); - - if (IS_ALPHA(c)) break; + if (IS_ALPHA(ch)) break; if (ch == ':') { state = s_req_schema_slash; break; } + SET_ERRNO(HPE_INVALID_URL); goto error; } @@ -710,14 +791,12 @@ size_t http_parser_execute (http_parser *parser, case s_req_host: { - c = LOWER(ch); if (IS_HOST_CHAR(ch)) break; switch (ch) { case ':': state = s_req_port; break; case '/': - MARK(path); state = s_req_path; break; case ' ': @@ -732,6 +811,7 @@ size_t http_parser_execute (http_parser *parser, state = s_req_query_string_start; break; default: + SET_ERRNO(HPE_INVALID_HOST); goto error; } break; @@ -742,7 +822,6 @@ size_t http_parser_execute (http_parser *parser, if (IS_NUM(ch)) break; switch (ch) { case '/': - MARK(path); state = s_req_path; break; case ' ': @@ -757,6 +836,7 @@ size_t http_parser_execute (http_parser *parser, state = s_req_query_string_start; break; default: + SET_ERRNO(HPE_INVALID_PORT); goto error; } break; @@ -769,32 +849,28 @@ size_t http_parser_execute (http_parser *parser, switch (ch) { case ' ': CALLBACK(url); - CALLBACK(path); state = s_req_http_start; break; case CR: CALLBACK(url); - CALLBACK(path); parser->http_major = 0; parser->http_minor = 9; state = s_req_line_almost_done; break; case LF: CALLBACK(url); - CALLBACK(path); parser->http_major = 0; parser->http_minor = 9; state = s_header_field_start; break; case '?': - CALLBACK(path); state = s_req_query_string_start; break; case '#': - CALLBACK(path); state = s_req_fragment_start; break; default: + SET_ERRNO(HPE_INVALID_PATH); goto error; } break; @@ -803,7 +879,6 @@ size_t http_parser_execute (http_parser *parser, case s_req_query_string_start: { if (IS_URL_CHAR(ch)) { - MARK(query_string); state = s_req_query_string; break; } @@ -831,6 +906,7 @@ size_t http_parser_execute (http_parser *parser, state = s_req_fragment_start; break; default: + SET_ERRNO(HPE_INVALID_QUERY_STRING); goto error; } break; @@ -846,28 +922,25 @@ size_t http_parser_execute (http_parser *parser, break; case ' ': CALLBACK(url); - CALLBACK(query_string); state = s_req_http_start; break; case CR: CALLBACK(url); - CALLBACK(query_string); parser->http_major = 0; parser->http_minor = 9; state = s_req_line_almost_done; break; case LF: CALLBACK(url); - CALLBACK(query_string); parser->http_major = 0; parser->http_minor = 9; state = s_header_field_start; break; case '#': - CALLBACK(query_string); state = s_req_fragment_start; break; default: + SET_ERRNO(HPE_INVALID_QUERY_STRING); goto error; } break; @@ -876,7 +949,6 @@ size_t http_parser_execute (http_parser *parser, case s_req_fragment_start: { if (IS_URL_CHAR(ch)) { - MARK(fragment); state = s_req_fragment; break; } @@ -899,12 +971,12 @@ size_t http_parser_execute (http_parser *parser, state = s_header_field_start; break; case '?': - MARK(fragment); state = s_req_fragment; break; case '#': break; default: + SET_ERRNO(HPE_INVALID_FRAGMENT); goto error; } break; @@ -917,19 +989,16 @@ size_t http_parser_execute (http_parser *parser, switch (ch) { case ' ': CALLBACK(url); - CALLBACK(fragment); state = s_req_http_start; break; case CR: CALLBACK(url); - CALLBACK(fragment); parser->http_major = 0; parser->http_minor = 9; state = s_req_line_almost_done; break; case LF: CALLBACK(url); - CALLBACK(fragment); parser->http_major = 0; parser->http_minor = 9; state = s_header_field_start; @@ -938,6 +1007,7 @@ size_t http_parser_execute (http_parser *parser, case '#': break; default: + SET_ERRNO(HPE_INVALID_FRAGMENT); goto error; } break; @@ -951,6 +1021,7 @@ size_t http_parser_execute (http_parser *parser, case ' ': break; default: + SET_ERRNO(HPE_INVALID_CONSTANT); goto error; } break; @@ -977,7 +1048,11 @@ size_t http_parser_execute (http_parser *parser, /* first digit of major HTTP version */ case s_req_first_http_major: - if (ch < '1' || ch > '9') goto error; + if (ch < '1' || ch > '9') { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + parser->http_major = ch - '0'; state = s_req_http_major; break; @@ -990,18 +1065,29 @@ size_t http_parser_execute (http_parser *parser, break; } - if (!IS_NUM(ch)) goto error; + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } parser->http_major *= 10; parser->http_major += ch - '0'; - if (parser->http_major > 999) goto error; + if (parser->http_major > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + break; } /* first digit of minor HTTP version */ case s_req_first_http_minor: - if (!IS_NUM(ch)) goto error; + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + parser->http_minor = ch - '0'; state = s_req_http_minor; break; @@ -1021,24 +1107,36 @@ size_t http_parser_execute (http_parser *parser, /* XXX allow spaces after digit? */ - if (!IS_NUM(ch)) goto error; + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } parser->http_minor *= 10; parser->http_minor += ch - '0'; - if (parser->http_minor > 999) goto error; + if (parser->http_minor > 999) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + break; } /* end of request line */ case s_req_line_almost_done: { - if (ch != LF) goto error; + if (ch != LF) { + SET_ERRNO(HPE_LF_EXPECTED); + goto error; + } + state = s_header_field_start; break; } case s_header_field_start: + header_field_start: { if (ch == CR) { state = s_headers_almost_done; @@ -1054,7 +1152,10 @@ size_t http_parser_execute (http_parser *parser, c = TOKEN(ch); - if (!c) goto error; + if (!c) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } MARK(header_field); @@ -1211,20 +1312,19 @@ size_t http_parser_execute (http_parser *parser, break; } + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); goto error; } case s_header_value_start: { - if (ch == ' ') break; + if (ch == ' ' || ch == '\t') break; MARK(header_value); state = s_header_value; index = 0; - c = LOWER(ch); - if (ch == CR) { CALLBACK(header_value); header_state = h_general; @@ -1238,6 +1338,8 @@ size_t http_parser_execute (http_parser *parser, break; } + c = LOWER(ch); + switch (header_state) { case h_upgrade: parser->flags |= F_UPGRADE; @@ -1254,7 +1356,11 @@ size_t http_parser_execute (http_parser *parser, break; case h_content_length: - if (!IS_NUM(ch)) goto error; + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + parser->content_length = ch - '0'; break; @@ -1279,7 +1385,6 @@ size_t http_parser_execute (http_parser *parser, case s_header_value: { - c = LOWER(ch); if (ch == CR) { CALLBACK(header_value); @@ -1292,6 +1397,8 @@ size_t http_parser_execute (http_parser *parser, goto header_almost_done; } + c = LOWER(ch); + switch (header_state) { case h_general: break; @@ -1303,7 +1410,11 @@ size_t http_parser_execute (http_parser *parser, case h_content_length: if (ch == ' ') break; - if (!IS_NUM(ch)) goto error; + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + parser->content_length *= 10; parser->content_length += ch - '0'; break; @@ -1359,7 +1470,7 @@ size_t http_parser_execute (http_parser *parser, { STRICT_CHECK(ch != LF); - state = s_header_field_start; + state = s_header_value_lws; switch (header_state) { case h_connection_keep_alive: @@ -1377,6 +1488,18 @@ size_t http_parser_execute (http_parser *parser, break; } + case s_header_value_lws: + { + if (ch == ' ' || ch == '\t') + state = s_header_value_start; + else + { + state = s_header_field_start; + goto header_field_start; + } + break; + } + case s_headers_almost_done: headers_almost_done: { @@ -1412,6 +1535,7 @@ size_t http_parser_execute (http_parser *parser, default: parser->state = state; + SET_ERRNO(HPE_CB_headers_complete); return p - data; /* Error */ } } @@ -1419,7 +1543,7 @@ size_t http_parser_execute (http_parser *parser, /* Exit, the rest of the connect is in a different protocol. */ if (parser->upgrade) { CALLBACK2(message_complete); - return (p - data); + return (p - data) + 1; } if (parser->flags & F_SKIPBODY) { @@ -1478,9 +1602,13 @@ size_t http_parser_execute (http_parser *parser, assert(nread == 1); assert(parser->flags & F_CHUNKED); - c = unhex[(unsigned char)ch]; - if (c == -1) goto error; - parser->content_length = c; + unhex_val = unhex[(unsigned char)ch]; + if (unhex_val == -1) { + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + parser->content_length = unhex_val; state = s_chunk_size; break; } @@ -1494,18 +1622,20 @@ size_t http_parser_execute (http_parser *parser, break; } - c = unhex[(unsigned char)ch]; + unhex_val = unhex[(unsigned char)ch]; - if (c == -1) { + if (unhex_val == -1) { if (ch == ';' || ch == ' ') { state = s_chunk_parameters; break; } + + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); goto error; } parser->content_length *= 16; - parser->content_length += c; + parser->content_length += unhex_val; break; } @@ -1569,16 +1699,14 @@ size_t http_parser_execute (http_parser *parser, default: assert(0 && "unhandled state"); + SET_ERRNO(HPE_INVALID_INTERNAL_STATE); goto error; } } - CALLBACK_NOCLEAR(header_field); - CALLBACK_NOCLEAR(header_value); - CALLBACK_NOCLEAR(fragment); - CALLBACK_NOCLEAR(query_string); - CALLBACK_NOCLEAR(path); - CALLBACK_NOCLEAR(url); + CALLBACK(header_field); + CALLBACK(header_value); + CALLBACK(url); parser->state = state; parser->header_state = header_state; @@ -1588,7 +1716,10 @@ size_t http_parser_execute (http_parser *parser, return len; error: - parser->state = s_dead; + if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { + SET_ERRNO(HPE_UNKNOWN); + } + return (p - data); } @@ -1629,4 +1760,17 @@ http_parser_init (http_parser *parser, enum http_parser_type t) parser->upgrade = 0; parser->flags = 0; parser->method = 0; + parser->http_errno = 0; +} + +const char * +http_errno_name(enum http_errno err) { + assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); + return http_strerror_tab[err].name; +} + +const char * +http_errno_description(enum http_errno err) { + assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); + return http_strerror_tab[err].description; } diff --git a/deps/http_parser/http_parser.gyp b/deps/http_parser/http_parser.gyp new file mode 100644 index 00000000000..e9183b8d55d --- /dev/null +++ b/deps/http_parser/http_parser.gyp @@ -0,0 +1,50 @@ +# This file is used with the GYP meta build system. +# http://code.google.com/p/gyp/ +# To build try this: +# svn co http://gyp.googlecode.com/svn/trunk gyp +# ./gyp/gyp -f make --depth=`pwd` http_parser.gyp +# ./out/Debug/test +{ + 'target_defaults': { + 'configurations': { + 'Debug': { + 'defines': [ 'DEBUG', '_DEBUG' ] + }, + 'Release': { + 'defines': [ 'NDEBUG' ] + } + } + }, + + 'targets': [ + { + 'target_name': 'http_parser', + 'type': 'static_library', + 'include_dirs': [ '.' ], + 'direct_dependent_settings': { + 'include_dirs': [ '.' ], + }, + 'defines': [ 'HTTP_PARSER_STRICT=0' ], + 'sources': [ './http_parser.c', ], + 'conditions': [ + ['OS=="win"', { + 'msvs_settings': { + 'VCCLCompilerTool': { + # Compile as C++. http_parser.c is actually C99, but C++ is + # close enough in this case. + 'CompileAs': 2, + }, + }, + }] + ], + }, + + { + 'target_name': 'test', + 'type': 'executable', + 'dependencies': [ 'http_parser' ], + 'sources': [ 'test.c' ] + } + ] +} + diff --git a/deps/http_parser/http_parser.h b/deps/http_parser/http_parser.h index 6a54a2d6357..7d395208e9f 100644 --- a/deps/http_parser/http_parser.h +++ b/deps/http_parser/http_parser.h @@ -28,7 +28,7 @@ extern "C" { #define HTTP_PARSER_VERSION_MINOR 0 #include <sys/types.h> -#if defined(_WIN32) && !defined(__MINGW32__) +#if defined(_WIN32) && !defined(__MINGW32__) && !defined(_MSC_VER) typedef __int8 int8_t; typedef unsigned __int8 uint8_t; typedef __int16 int16_t; @@ -51,6 +51,13 @@ typedef int ssize_t; # define HTTP_PARSER_STRICT 1 #endif +/* Compile with -DHTTP_PARSER_DEBUG=1 to add extra debugging information to + * the error reporting facility. + */ +#ifndef HTTP_PARSER_DEBUG +# define HTTP_PARSER_DEBUG 0 +#endif + /* Maximium header size allowed */ #define HTTP_MAX_HEADER_SIZE (80*1024) @@ -58,6 +65,7 @@ typedef int ssize_t; typedef struct http_parser http_parser; typedef struct http_parser_settings http_parser_settings; +typedef struct http_parser_result http_parser_result; /* Callbacks should return non-zero to indicate an error. The parser will @@ -125,6 +133,72 @@ enum flags }; +/* Map for errno-related constants + * + * The provided argument should be a macro that takes 2 arguments. + */ +#define HTTP_ERRNO_MAP(XX) \ + /* No error */ \ + XX(OK, "success") \ + \ + /* Callback-related errors */ \ + XX(CB_message_begin, "the on_message_begin callback failed") \ + XX(CB_path, "the on_path callback failed") \ + XX(CB_query_string, "the on_query_string callback failed") \ + XX(CB_url, "the on_url callback failed") \ + XX(CB_fragment, "the on_fragment callback failed") \ + XX(CB_header_field, "the on_header_field callback failed") \ + XX(CB_header_value, "the on_header_value callback failed") \ + XX(CB_headers_complete, "the on_headers_complete callback failed") \ + XX(CB_body, "the on_body callback failed") \ + XX(CB_message_complete, "the on_message_complete callback failed") \ + \ + /* Parsing-related errors */ \ + XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ + XX(HEADER_OVERFLOW, \ + "too many header bytes seen; overflow detected") \ + XX(CLOSED_CONNECTION, \ + "data received after completed connection: close message") \ + XX(INVALID_VERSION, "invalid HTTP version") \ + XX(INVALID_STATUS, "invalid HTTP status code") \ + XX(INVALID_METHOD, "invalid HTTP method") \ + XX(INVALID_URL, "invalid URL") \ + XX(INVALID_HOST, "invalid host") \ + XX(INVALID_PORT, "invalid port") \ + XX(INVALID_PATH, "invalid path") \ + XX(INVALID_QUERY_STRING, "invalid query string") \ + XX(INVALID_FRAGMENT, "invalid fragment") \ + XX(LF_EXPECTED, "LF character expected") \ + XX(INVALID_HEADER_TOKEN, "invalid character in header") \ + XX(INVALID_CONTENT_LENGTH, \ + "invalid character in content-length header") \ + XX(INVALID_CHUNK_SIZE, \ + "invalid character in chunk size header") \ + XX(INVALID_CONSTANT, "invalid constant string") \ + XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ + XX(STRICT, "strict mode assertion failed") \ + XX(UNKNOWN, "an unknown error occurred") + + +/* Define HPE_* values for each errno value above */ +#define HTTP_ERRNO_GEN(n, s) HPE_##n, +enum http_errno { + HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) +}; +#undef HTTP_ERRNO_GEN + + +/* Get an http_errno value from an http_parser */ +#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) + +/* Get the line number that generated the current error */ +#if HTTP_PARSER_DEBUG +#define HTTP_PARSER_ERRNO_LINE(p) ((p)->error_lineno) +#else +#define HTTP_PARSER_ERRNO_LINE(p) 0 +#endif + + struct http_parser { /** PRIVATE **/ unsigned char type : 2; @@ -141,13 +215,18 @@ struct http_parser { unsigned short http_minor; unsigned short status_code; /* responses only */ unsigned char method; /* requests only */ + unsigned char http_errno : 7; /* 1 = Upgrade header was present and the parser has exited because of that. * 0 = No upgrade header present. * Should be checked when http_parser_execute() returns in addition to * error checking. */ - char upgrade; + char upgrade : 1; + +#if HTTP_PARSER_DEBUG + uint32_t error_lineno; +#endif /** PUBLIC **/ void *data; /* A pointer to get hook to the "connection" or "socket" object */ @@ -156,10 +235,7 @@ struct http_parser { struct http_parser_settings { http_cb on_message_begin; - http_data_cb on_path; - http_data_cb on_query_string; http_data_cb on_url; - http_data_cb on_fragment; http_data_cb on_header_field; http_data_cb on_header_value; http_cb on_headers_complete; @@ -186,7 +262,13 @@ 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); +const char *http_method_str(enum http_method m); + +/* Return a string name of the given error */ +const char *http_errno_name(enum http_errno err); + +/* Return a string description of the given error */ +const char *http_errno_description(enum http_errno err); #ifdef __cplusplus } diff --git a/deps/http_parser/test.c b/deps/http_parser/test.c index a4b80a2e606..7d95b0eb9c5 100644 --- a/deps/http_parser/test.c +++ b/deps/http_parser/test.c @@ -44,10 +44,7 @@ struct message { enum http_parser_type type; enum http_method method; int status_code; - char request_path[MAX_ELEMENT_SIZE]; char request_url[MAX_ELEMENT_SIZE]; - char fragment[MAX_ELEMENT_SIZE]; - char query_string[MAX_ELEMENT_SIZE]; char body[MAX_ELEMENT_SIZE]; size_t body_size; int num_headers; @@ -55,7 +52,7 @@ struct message { char headers [MAX_HEADERS][2][MAX_ELEMENT_SIZE]; int should_keep_alive; - int upgrade; + const char *upgrade; // upgraded body unsigned short http_major; unsigned short http_minor; @@ -86,9 +83,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/test" ,.request_url= "/test" ,.num_headers= 3 ,.headers= @@ -117,9 +111,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/favicon.ico" ,.request_url= "/favicon.ico" ,.num_headers= 8 ,.headers= @@ -146,9 +137,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/dumbfuck" ,.request_url= "/dumbfuck" ,.num_headers= 1 ,.headers= @@ -167,9 +155,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET - ,.query_string= "page=1" - ,.fragment= "posts-17408" - ,.request_path= "/forums/1/topics/2375" /* XXX request url does include fragment? */ ,.request_url= "/forums/1/topics/2375?page=1#posts-17408" ,.num_headers= 0 @@ -186,9 +171,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/get_no_headers_no_body/world" ,.request_url= "/get_no_headers_no_body/world" ,.num_headers= 0 ,.body= "" @@ -205,9 +187,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/get_one_header_no_body" ,.request_url= "/get_one_header_no_body" ,.num_headers= 1 ,.headers= @@ -228,9 +207,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 0 ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/get_funky_content_length_body_hello" ,.request_url= "/get_funky_content_length_body_hello" ,.num_headers= 1 ,.headers= @@ -253,9 +229,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST - ,.query_string= "q=search" - ,.fragment= "hey" - ,.request_path= "/post_identity_body_world" ,.request_url= "/post_identity_body_world?q=search#hey" ,.num_headers= 3 ,.headers= @@ -280,9 +253,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/post_chunked_all_your_base" ,.request_url= "/post_chunked_all_your_base" ,.num_headers= 1 ,.headers= @@ -306,9 +276,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/two_chunks_mult_zero_end" ,.request_url= "/two_chunks_mult_zero_end" ,.num_headers= 1 ,.headers= @@ -334,9 +301,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/chunked_w_trailing_headers" ,.request_url= "/chunked_w_trailing_headers" ,.num_headers= 3 ,.headers= @@ -362,9 +326,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_POST - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/chunked_w_bullshit_after_length" ,.request_url= "/chunked_w_bullshit_after_length" ,.num_headers= 1 ,.headers= @@ -382,9 +343,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET - ,.query_string= "foo=\"bar\"" - ,.fragment= "" - ,.request_path= "/with_\"stupid\"_quotes" ,.request_url= "/with_\"stupid\"_quotes?foo=\"bar\"" ,.num_headers= 0 ,.headers= { } @@ -408,9 +366,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 0 ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/test" ,.request_url= "/test" ,.num_headers= 3 ,.headers= { { "Host", "0.0.0.0:5000" } @@ -431,9 +386,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET - ,.query_string= "foo=bar?baz" - ,.fragment= "" - ,.request_path= "/test.cgi" ,.request_url= "/test.cgi?foo=bar?baz" ,.num_headers= 0 ,.headers= {} @@ -452,9 +404,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/test" ,.request_url= "/test" ,.num_headers= 0 ,.headers= { } @@ -473,17 +422,15 @@ const struct message requests[] = "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" "Origin: http://example.com\r\n" "\r\n" + "Hot diggity dogg" ,.should_keep_alive= TRUE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/demo" ,.request_url= "/demo" ,.num_headers= 7 - ,.upgrade=1 + ,.upgrade="Hot diggity dogg" ,.headers= { { "Host", "example.com" } , { "Connection", "Upgrade" } , { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" } @@ -502,17 +449,16 @@ const struct message requests[] = "User-agent: Mozilla/1.1N\r\n" "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" "\r\n" + "some data\r\n" + "and yet even more data" ,.should_keep_alive= FALSE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 0 ,.method= HTTP_CONNECT - ,.query_string= "" - ,.fragment= "" - ,.request_path= "" ,.request_url= "0-home0.netscape.com:443" ,.num_headers= 2 - ,.upgrade=1 + ,.upgrade="some data\r\nand yet even more data" ,.headers= { { "User-agent", "Mozilla/1.1N" } , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } } @@ -529,9 +475,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_REPORT - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/test" ,.request_url= "/test" ,.num_headers= 0 ,.headers= {} @@ -548,9 +491,6 @@ const struct message requests[] = ,.http_major= 0 ,.http_minor= 9 ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/" ,.request_url= "/" ,.num_headers= 0 ,.headers= {} @@ -570,9 +510,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_MSEARCH - ,.query_string= "" - ,.fragment= "" - ,.request_path= "*" ,.request_url= "*" ,.num_headers= 3 ,.headers= { { "HOST", "239.255.255.250:1900" } @@ -582,6 +519,32 @@ const struct message requests[] = ,.body= "" } +#define LINE_FOLDING_IN_HEADER 20 +, {.name= "line folding in header value" + ,.type= HTTP_REQUEST + ,.raw= "GET / HTTP/1.1\r\n" + "Line1: abc\r\n" + "\tdef\r\n" + " ghi\r\n" + "\t\tjkl\r\n" + " mno \r\n" + "\t \tqrs\r\n" + "Line2: \t line2\t\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.request_url= "/" + ,.num_headers= 2 + ,.headers= { { "Line1", "abcdefghijklmno qrs" } + , { "Line2", "line2\t" } + } + ,.body= "" + } + + #define QUERY_TERMINATED_HOST 21 , {.name= "host terminated by a query string" ,.type= HTTP_REQUEST @@ -592,9 +555,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET - ,.query_string= "hail=all" - ,.fragment= "" - ,.request_path= "" ,.request_url= "http://hypnotoad.org?hail=all" ,.num_headers= 0 ,.headers= { } @@ -611,9 +571,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET - ,.query_string= "hail=all" - ,.fragment= "" - ,.request_path= "" ,.request_url= "http://hypnotoad.org:1234?hail=all" ,.num_headers= 0 ,.headers= { } @@ -630,9 +587,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET - ,.query_string= "" - ,.fragment= "" - ,.request_path= "" ,.request_url= "http://hypnotoad.org:1234" ,.num_headers= 0 ,.headers= { } @@ -651,9 +605,6 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_GET - ,.query_string= "q=1" - ,.fragment= "narf" - ,.request_path= "/δ¶/δt/pope" ,.request_url= "/δ¶/δt/pope?q=1#narf" ,.num_headers= 1 ,.headers= { {"Host", "github.com" } @@ -673,12 +624,9 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 0 ,.method= HTTP_CONNECT - ,.query_string= "" - ,.fragment= "" - ,.request_path= "" ,.request_url= "home_0.netscape.com:443" ,.num_headers= 2 - ,.upgrade=1 + ,.upgrade="" ,.headers= { { "User-agent", "Mozilla/1.1N" } , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } } @@ -701,12 +649,8 @@ const struct message requests[] = ,.http_major= 1 ,.http_minor= 1 ,.method= HTTP_PATCH - ,.query_string= "" - ,.fragment= "" - ,.request_path= "/file.txt" ,.request_url= "/file.txt" ,.num_headers= 4 - ,.upgrade=0 ,.headers= { { "Host", "www.example.com" } , { "Content-Type", "application/example" } , { "If-Match", "\"e0023aa4e\"" } @@ -715,6 +659,27 @@ const struct message requests[] = ,.body= "cccccccccc" } +#define CONNECT_CAPS_REQUEST 27 +, {.name = "connect caps request" + ,.type= HTTP_REQUEST + ,.raw= "CONNECT HOME0.NETSCAPE.COM:443 HTTP/1.0\r\n" + "User-agent: Mozilla/1.1N\r\n" + "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n" + "\r\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 0 + ,.method= HTTP_CONNECT + ,.request_url= "HOME0.NETSCAPE.COM:443" + ,.num_headers= 2 + ,.upgrade="" + ,.headers= { { "User-agent", "Mozilla/1.1N" } + , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" } + } + ,.body= "" + } + , {.name= NULL } /* sentinel */ }; @@ -1081,14 +1046,6 @@ const struct message responses[] = }; int -request_path_cb (http_parser *p, const char *buf, size_t len) -{ - assert(p == parser); - strncat(messages[num_messages].request_path, buf, len); - return 0; -} - -int request_url_cb (http_parser *p, const char *buf, size_t len) { assert(p == parser); @@ -1097,22 +1054,6 @@ request_url_cb (http_parser *p, const char *buf, size_t len) } int -query_string_cb (http_parser *p, const char *buf, size_t len) -{ - assert(p == parser); - strncat(messages[num_messages].query_string, buf, len); - return 0; -} - -int -fragment_cb (http_parser *p, const char *buf, size_t len) -{ - assert(p == parser); - strncat(messages[num_messages].fragment, buf, len); - return 0; -} - -int header_field_cb (http_parser *p, const char *buf, size_t len) { assert(p == parser); @@ -1205,10 +1146,7 @@ static http_parser_settings settings = {.on_message_begin = message_begin_cb ,.on_header_field = header_field_cb ,.on_header_value = header_value_cb - ,.on_path = request_path_cb ,.on_url = request_url_cb - ,.on_fragment = fragment_cb - ,.on_query_string = query_string_cb ,.on_body = body_cb ,.on_headers_complete = headers_complete_cb ,.on_message_complete = message_complete_cb @@ -1218,10 +1156,7 @@ static http_parser_settings settings_count_body = {.on_message_begin = message_begin_cb ,.on_header_field = header_field_cb ,.on_header_value = header_value_cb - ,.on_path = request_path_cb ,.on_url = request_url_cb - ,.on_fragment = fragment_cb - ,.on_query_string = query_string_cb ,.on_body = count_body_cb ,.on_headers_complete = headers_complete_cb ,.on_message_complete = message_complete_cb @@ -1231,10 +1166,7 @@ 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 @@ -1284,7 +1216,13 @@ check_str_eq (const struct message *m, const char *prop, const char *expected, const char *found) { - if (0 != strcmp(expected, found)) { + if ((expected == NULL) != (found == NULL)) { + printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); + printf("expected %s\n", (expected == NULL) ? "NULL" : expected); + printf(" found %s\n", (found == NULL) ? "NULL" : found); + return 0; + } + if (expected != NULL && 0 != strcmp(expected, found)) { printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name); printf("expected '%s'\n", expected); printf(" found '%s'\n", found); @@ -1337,9 +1275,6 @@ message_eq (int index, const struct message *expected) assert(m->message_complete_cb_called); - MESSAGE_CHECK_STR_EQ(expected, m, request_path); - MESSAGE_CHECK_STR_EQ(expected, m, query_string); - MESSAGE_CHECK_STR_EQ(expected, m, fragment); MESSAGE_CHECK_STR_EQ(expected, m, request_url); if (expected->body_size) { MESSAGE_CHECK_NUM_EQ(expected, m, body_size); @@ -1357,13 +1292,81 @@ message_eq (int index, const struct message *expected) if (!r) return 0; } + MESSAGE_CHECK_STR_EQ(expected, m, upgrade); + return 1; } +/* Given a sequence of varargs messages, return the number of them that the + * parser should successfully parse, taking into account that upgraded + * messages prevent all subsequent messages from being parsed. + */ +size_t +count_parsed_messages(const size_t nmsgs, ...) { + size_t i; + va_list ap; + + va_start(ap, nmsgs); + + for (i = 0; i < nmsgs; i++) { + struct message *m = va_arg(ap, struct message *); + + if (m->upgrade) { + va_end(ap); + return i + 1; + } + } + + va_end(ap); + return nmsgs; +} + +/* Given a sequence of bytes and the number of these that we were able to + * parse, verify that upgrade bodies are correct. + */ +void +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++) { + struct message *m = va_arg(ap, struct message *); + + off += strlen(m->raw); + + if (m->upgrade) { + off -= strlen(m->upgrade); + + /* Check the portion of the response after its specified upgrade */ + if (!check_str_eq(m, "upgrade", body + off, body + nread)) { + exit(1); + } + + /* Fix up the response so that message_eq() will verify the beginning + * of the upgrade */ + *(body + nread + strlen(m->upgrade)) = '\0'; + messages[num_messages -1 ].upgrade = body + nread; + + va_end(ap); + return; + } + } + + va_end(ap); + printf("\n\n*** Error: expected a message with upgrade ***\n"); + + exit(1); +} + static void print_error (const char *raw, size_t error_location) { - fprintf(stderr, "\n*** parse error ***\n\n"); + fprintf(stderr, "\n*** %s:%d -- %s ***\n\n", + "http_parser.c", HTTP_PARSER_ERRNO_LINE(parser), + http_errno_description(HTTP_PARSER_ERRNO(parser))); int this_line = 0, char_len = 0; size_t i, j, len = strlen(raw), error_location_line = 0; @@ -1418,7 +1421,10 @@ test_message (const struct message *message) if (msg1len) { read = parse(msg1, msg1len); - if (message->upgrade && parser->upgrade) goto test; + if (message->upgrade && parser->upgrade) { + messages[num_messages - 1].upgrade = msg1 + read; + goto test; + } if (read != msg1len) { print_error(msg1, read); @@ -1429,7 +1435,10 @@ test_message (const struct message *message) read = parse(msg2, msg2len); - if (message->upgrade && parser->upgrade) goto test; + if (message->upgrade && parser->upgrade) { + messages[num_messages - 1].upgrade = msg2 + read; + goto test; + } if (read != msg2len) { print_error(msg2, read); @@ -1438,8 +1447,6 @@ test_message (const struct message *message) read = parse(NULL, 0); - if (message->upgrade && parser->upgrade) goto test; - if (read != 0) { print_error(message->raw, read); exit(1); @@ -1495,21 +1502,32 @@ test_message_count_body (const struct message *message) } void -test_simple (const char *buf, int should_pass) +test_simple (const char *buf, enum http_errno err_expected) { parser_init(HTTP_REQUEST); size_t parsed; int pass; + enum http_errno err; + parsed = parse(buf, strlen(buf)); pass = (parsed == strlen(buf)); + err = HTTP_PARSER_ERRNO(parser); parsed = parse(NULL, 0); pass &= (parsed == 0); parser_free(); - if (pass != should_pass) { - fprintf(stderr, "\n*** test_simple expected %s ***\n\n%s", should_pass ? "success" : "error", buf); + /* In strict mode, allow us to pass with an unexpected HPE_STRICT as + * long as the caller isn't expecting success. + */ +#if HTTP_PARSER_STRICT + if (err_expected != err && err_expected != HPE_OK && err != HPE_STRICT) { +#else + if (err_expected != err) { +#endif + fprintf(stderr, "\n*** test_simple expected %s, but saw %s ***\n\n%s\n", + http_errno_name(err_expected), http_errno_name(err), buf); exit(1); } } @@ -1526,10 +1544,14 @@ test_header_overflow_error (int req) assert(parsed == strlen(buf)); buf = "header-key: header-value\r\n"; + size_t buflen = strlen(buf); + int i; for (i = 0; i < 10000; i++) { - if (http_parser_execute(&parser, &settings_null, buf, strlen(buf)) != strlen(buf)) { + parsed = http_parser_execute(&parser, &settings_null, buf, buflen); + if (parsed != buflen) { //fprintf(stderr, "error found on iter %d\n", i); + assert(HTTP_PARSER_ERRNO(&parser) == HPE_HEADER_OVERFLOW); return; } } @@ -1574,12 +1596,7 @@ test_no_overflow_long_body (int req, size_t length) void test_multiple3 (const struct message *r1, const struct message *r2, const struct message *r3) { - int message_count = 1; - if (!r1->upgrade) { - message_count++; - if (!r2->upgrade) message_count++; - } - int has_upgrade = (message_count < 3 || r3->upgrade); + int message_count = count_parsed_messages(3, r1, r2, r3); char total[ strlen(r1->raw) + strlen(r2->raw) @@ -1598,7 +1615,10 @@ test_multiple3 (const struct message *r1, const struct message *r2, const struct read = parse(total, strlen(total)); - if (has_upgrade && parser->upgrade) goto test; + if (parser->upgrade) { + upgrade_message_fix(total, read, 3, r1, r2, r3); + goto test; + } if (read != strlen(total)) { print_error(total, read); @@ -1607,8 +1627,6 @@ test_multiple3 (const struct message *r1, const struct message *r2, const struct read = parse(NULL, 0); - if (has_upgrade && parser->upgrade) goto test; - if (read != 0) { print_error(total, read); exit(1); @@ -1622,12 +1640,8 @@ test: } if (!message_eq(0, r1)) exit(1); - if (message_count > 1) { - if (!message_eq(1, r2)) exit(1); - if (message_count > 2) { - if (!message_eq(2, r3)) exit(1); - } - } + if (message_count > 1 && !message_eq(1, r2)) exit(1); + if (message_count > 2 && !message_eq(2, r3)) exit(1); parser_free(); } @@ -1656,6 +1670,7 @@ test_scan (const struct message *r1, const struct message *r2, const struct mess int ops = 0 ; size_t buf1_len, buf2_len, buf3_len; + int message_count = count_parsed_messages(3, r1, r2, r3); int i,j,type_both; for (type_both = 0; type_both < 2; type_both ++ ) { @@ -1684,27 +1699,27 @@ test_scan (const struct message *r1, const struct message *r2, const struct mess read = parse(buf1, buf1_len); - if (r3->upgrade && parser->upgrade) goto test; + if (parser->upgrade) goto test; if (read != buf1_len) { print_error(buf1, read); goto error; } - read = parse(buf2, buf2_len); + read += parse(buf2, buf2_len); - if (r3->upgrade && parser->upgrade) goto test; + if (parser->upgrade) goto test; - if (read != buf2_len) { + if (read != buf1_len + buf2_len) { print_error(buf2, read); goto error; } - read = parse(buf3, buf3_len); + read += parse(buf3, buf3_len); - if (r3->upgrade && parser->upgrade) goto test; + if (parser->upgrade) goto test; - if (read != buf3_len) { + if (read != buf1_len + buf2_len + buf3_len) { print_error(buf3, read); goto error; } @@ -1712,9 +1727,13 @@ test_scan (const struct message *r1, const struct message *r2, const struct mess parse(NULL, 0); test: + if (parser->upgrade) { + upgrade_message_fix(total, read, 3, r1, r2, r3); + } - if (3 != num_messages) { - fprintf(stderr, "\n\nParser didn't see 3 messages only %d\n", num_messages); + if (message_count != num_messages) { + fprintf(stderr, "\n\nParser didn't see %d messages only %d\n", + message_count, num_messages); goto error; } @@ -1723,12 +1742,12 @@ test: goto error; } - if (!message_eq(1, r2)) { + if (message_count > 1 && !message_eq(1, r2)) { fprintf(stderr, "\n\nError matching messages[1] in test_scan.\n"); goto error; } - if (!message_eq(2, r3)) { + if (message_count > 2 && !message_eq(2, r3)) { fprintf(stderr, "\n\nError matching messages[2] in test_scan.\n"); goto error; } @@ -1868,13 +1887,13 @@ main (void) /// REQUESTS - test_simple("hello world", 0); - test_simple("GET / HTP/1.1\r\n\r\n", 0); + test_simple("hello world", HPE_INVALID_METHOD); + test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION); - 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); + test_simple("ASDF / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD); + test_simple("PROPPATCHA / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD); + test_simple("GETA / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD); // Well-formed but incomplete test_simple("GET / HTTP/1.1\r\n" @@ -1882,7 +1901,7 @@ main (void) "Content-Length: 6\r\n" "\r\n" "fooba", - 0); + HPE_OK); static const char *all_methods[] = { "DELETE", @@ -1900,12 +1919,31 @@ main (void) "PROPFIND", "PROPPATCH", "UNLOCK", + "REPORT", + "MKACTIVITY", + "CHECKOUT", + "MERGE", + "M-SEARCH", + "NOTIFY", + "SUBSCRIBE", + "UNSUBSCRIBE", + "PATCH", 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); + test_simple(buf, HPE_OK); + } + + static const char *bad_methods[] = { + "C******", + "M****", + 0 }; + for (this_method = bad_methods; *this_method; this_method++) { + char buf[200]; + sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method); + test_simple(buf, HPE_UNKNOWN); } const char *dumbfuck2 = @@ -1943,7 +1981,7 @@ main (void) "\tRA==\r\n" "\t-----END CERTIFICATE-----\r\n" "\r\n"; - test_simple(dumbfuck2, 0); + test_simple(dumbfuck2, HPE_OK); #if 0 // NOTE(Wed Nov 18 11:57:27 CET 2009) this seems okay. we just read body |