diff --git a/third-party/http-parser/README.md b/third-party/http-parser/README.md index f9972ae5..7c54dd42 100644 --- a/third-party/http-parser/README.md +++ b/third-party/http-parser/README.md @@ -131,7 +131,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_uri, + Callbacks: (requests only) on_url, (common) on_header_field, on_header_value, on_body; Callbacks must return 0 on success. Returning a non-zero value indicates diff --git a/third-party/http-parser/bench.c b/third-party/http-parser/bench.c new file mode 100644 index 00000000..5b452fa1 --- /dev/null +++ b/third-party/http-parser/bench.c @@ -0,0 +1,111 @@ +/* Copyright Fedor Indutny. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include "http_parser.h" +#include +#include +#include +#include + +static const char data[] = + "POST /joyent/http-parser HTTP/1.1\r\n" + "Host: github.com\r\n" + "DNT: 1\r\n" + "Accept-Encoding: gzip, deflate, sdch\r\n" + "Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.6,en;q=0.4\r\n" + "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) " + "AppleWebKit/537.36 (KHTML, like Gecko) " + "Chrome/39.0.2171.65 Safari/537.36\r\n" + "Accept: text/html,application/xhtml+xml,application/xml;q=0.9," + "image/webp,*/*;q=0.8\r\n" + "Referer: https://github.com/joyent/http-parser\r\n" + "Connection: keep-alive\r\n" + "Transfer-Encoding: chunked\r\n" + "Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n\r\n"; +static const size_t data_len = sizeof(data) - 1; + +static int on_info(http_parser* p) { + return 0; +} + + +static int on_data(http_parser* p, const char *at, size_t length) { + return 0; +} + +static http_parser_settings settings = { + .on_message_begin = on_info, + .on_headers_complete = on_info, + .on_message_complete = on_info, + .on_header_field = on_data, + .on_header_value = on_data, + .on_url = on_data, + .on_status = on_data, + .on_body = on_data +}; + +int bench(int iter_count, int silent) { + struct http_parser parser; + int i; + int err; + struct timeval start; + struct timeval end; + float rps; + + if (!silent) { + err = gettimeofday(&start, NULL); + assert(err == 0); + } + + for (i = 0; i < iter_count; i++) { + size_t parsed; + http_parser_init(&parser, HTTP_REQUEST); + + parsed = http_parser_execute(&parser, &settings, data, data_len); + assert(parsed == data_len); + } + + if (!silent) { + err = gettimeofday(&end, NULL); + assert(err == 0); + + fprintf(stdout, "Benchmark result:\n"); + + rps = (float) (end.tv_sec - start.tv_sec) + + (end.tv_usec - start.tv_usec) * 1e-6f; + fprintf(stdout, "Took %f seconds to run\n", rps); + + rps = (float) iter_count / rps; + fprintf(stdout, "%f req/sec\n", rps); + fflush(stdout); + } + + return 0; +} + +int main(int argc, char** argv) { + if (argc == 2 && strcmp(argv[1], "infinite") == 0) { + for (;;) + bench(5000000, 1); + return 0; + } else { + return bench(5000000, 0); + } +} diff --git a/third-party/http-parser/http_parser.c b/third-party/http-parser/http_parser.c index 749d1bb6..23077d1d 100644 --- a/third-party/http-parser/http_parser.c +++ b/third-party/http-parser/http_parser.c @@ -56,19 +56,41 @@ do { \ parser->http_errno = (e); \ } while(0) +#define CURRENT_STATE() p_state +#define UPDATE_STATE(V) p_state = (V); +#define RETURN(V) \ +do { \ + parser->state = CURRENT_STATE(); \ + return (V); \ +} while (0); +#define REEXECUTE() \ + --p; \ + break; + + +#ifdef __GNUC__ +# define LIKELY(X) __builtin_expect(!!(X), 1) +# define UNLIKELY(X) __builtin_expect(!!(X), 0) +#else +# define LIKELY(X) (X) +# define UNLIKELY(X) (X) +#endif + /* Run the notify callback FOR, returning ER if it fails */ #define CALLBACK_NOTIFY_(FOR, ER) \ do { \ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ \ - if (settings->on_##FOR) { \ - if (0 != settings->on_##FOR(parser)) { \ + if (LIKELY(settings->on_##FOR)) { \ + parser->state = CURRENT_STATE(); \ + if (UNLIKELY(0 != settings->on_##FOR(parser))) { \ SET_ERRNO(HPE_CB_##FOR); \ } \ + UPDATE_STATE(parser->state); \ \ /* We either errored above or got paused; get out */ \ - if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \ + if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ return (ER); \ } \ } \ @@ -86,13 +108,16 @@ do { \ assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ \ if (FOR##_mark) { \ - if (settings->on_##FOR) { \ - if (0 != settings->on_##FOR(parser, FOR##_mark, (LEN))) { \ + if (LIKELY(settings->on_##FOR)) { \ + parser->state = CURRENT_STATE(); \ + if (UNLIKELY(0 != \ + settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \ SET_ERRNO(HPE_CB_##FOR); \ } \ + UPDATE_STATE(parser->state); \ \ /* We either errored above or got paused; get out */ \ - if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { \ + if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ return (ER); \ } \ } \ @@ -116,6 +141,26 @@ do { \ } \ } while (0) +/* Don't allow the total size of the HTTP headers (including the status + * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect + * embedders against denial-of-service attacks where the attacker feeds + * us a never-ending header that the embedder keeps buffering. + * + * This check is arguably the responsibility of embedders but we're doing + * it on the embedder's behalf because most won't bother and this way we + * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger + * than any reasonable request or response so this should never affect + * day-to-day operation. + */ +#define COUNT_HEADER_SIZE(V) \ +do { \ + parser->nread += (V); \ + if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) { \ + SET_ERRNO(HPE_HEADER_OVERFLOW); \ + goto error; \ + } \ +} while (0) + #define PROXY_CONNECTION "proxy-connection" #define CONNECTION "connection" @@ -334,12 +379,16 @@ enum header_states , h_upgrade , h_matching_transfer_encoding_chunked + , h_matching_connection_token_start , h_matching_connection_keep_alive , h_matching_connection_close + , h_matching_connection_upgrade + , h_matching_connection_token , h_transfer_encoding_chunked , h_connection_keep_alive , h_connection_close + , h_connection_upgrade }; enum http_host_state @@ -371,6 +420,8 @@ enum http_host_state (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ (c) == '$' || (c) == ',') +#define STRICT_TOKEN(c) (tokens[(unsigned char)c]) + #if HTTP_PARSER_STRICT #define TOKEN(c) (tokens[(unsigned char)c]) #define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) @@ -586,6 +637,7 @@ size_t http_parser_execute (http_parser *parser, const char *url_mark = 0; const char *body_mark = 0; const char *status_mark = 0; + enum state p_state = parser->state; /* We're in an error state. Don't bother doing anything. */ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { @@ -593,7 +645,7 @@ size_t http_parser_execute (http_parser *parser, } if (len == 0) { - switch (parser->state) { + switch (CURRENT_STATE()) { case s_body_identity_eof: /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if * we got paused. @@ -614,11 +666,11 @@ size_t http_parser_execute (http_parser *parser, } - if (parser->state == s_header_field) + if (CURRENT_STATE() == s_header_field) header_field_mark = data; - if (parser->state == s_header_value) + if (CURRENT_STATE() == s_header_value) header_value_mark = data; - switch (parser->state) { + switch (CURRENT_STATE()) { case s_req_path: case s_req_schema: case s_req_schema_slash: @@ -635,38 +687,23 @@ size_t http_parser_execute (http_parser *parser, case s_res_status: status_mark = data; break; + default: + break; } for (p=data; p != data + len; p++) { ch = *p; - if (PARSING_HEADER(parser->state)) { - ++parser->nread; - /* Don't allow the total size of the HTTP headers (including the status - * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect - * embedders against denial-of-service attacks where the attacker feeds - * us a never-ending header that the embedder keeps buffering. - * - * This check is arguably the responsibility of embedders but we're doing - * it on the embedder's behalf because most won't bother and this way we - * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger - * than any reasonable request or response so this should never affect - * day-to-day operation. - */ - if (parser->nread > (HTTP_MAX_HEADER_SIZE)) { - SET_ERRNO(HPE_HEADER_OVERFLOW); - goto error; - } - } + if (PARSING_HEADER(CURRENT_STATE())) + COUNT_HEADER_SIZE(1); - reexecute_byte: - switch (parser->state) { + switch (CURRENT_STATE()) { case s_dead: /* this state is used after a 'Connection: close' message * the parser will error out if it reads another message */ - if (ch == CR || ch == LF) + if (LIKELY(ch == CR || ch == LF)) break; SET_ERRNO(HPE_CLOSED_CONNECTION); @@ -680,13 +717,13 @@ size_t http_parser_execute (http_parser *parser, parser->content_length = ULLONG_MAX; if (ch == 'H') { - parser->state = s_res_or_resp_H; + UPDATE_STATE(s_res_or_resp_H); CALLBACK_NOTIFY(message_begin); } else { parser->type = HTTP_REQUEST; - parser->state = s_start_req; - goto reexecute_byte; + UPDATE_STATE(s_start_req); + REEXECUTE(); } break; @@ -695,9 +732,9 @@ size_t http_parser_execute (http_parser *parser, case s_res_or_resp_H: if (ch == 'T') { parser->type = HTTP_RESPONSE; - parser->state = s_res_HT; + UPDATE_STATE(s_res_HT); } else { - if (ch != 'E') { + if (UNLIKELY(ch != 'E')) { SET_ERRNO(HPE_INVALID_CONSTANT); goto error; } @@ -705,7 +742,7 @@ size_t http_parser_execute (http_parser *parser, parser->type = HTTP_REQUEST; parser->method = HTTP_HEAD; parser->index = 2; - parser->state = s_req_method; + UPDATE_STATE(s_req_method); } break; @@ -716,7 +753,7 @@ size_t http_parser_execute (http_parser *parser, switch (ch) { case 'H': - parser->state = s_res_H; + UPDATE_STATE(s_res_H); break; case CR: @@ -734,39 +771,39 @@ size_t http_parser_execute (http_parser *parser, case s_res_H: STRICT_CHECK(ch != 'T'); - parser->state = s_res_HT; + UPDATE_STATE(s_res_HT); break; case s_res_HT: STRICT_CHECK(ch != 'T'); - parser->state = s_res_HTT; + UPDATE_STATE(s_res_HTT); break; case s_res_HTT: STRICT_CHECK(ch != 'P'); - parser->state = s_res_HTTP; + UPDATE_STATE(s_res_HTTP); break; case s_res_HTTP: STRICT_CHECK(ch != '/'); - parser->state = s_res_first_http_major; + UPDATE_STATE(s_res_first_http_major); break; case s_res_first_http_major: - if (ch < '0' || ch > '9') { + if (UNLIKELY(ch < '0' || ch > '9')) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_major = ch - '0'; - parser->state = s_res_http_major; + UPDATE_STATE(s_res_http_major); break; /* major HTTP version or dot */ case s_res_http_major: { if (ch == '.') { - parser->state = s_res_first_http_minor; + UPDATE_STATE(s_res_first_http_minor); break; } @@ -778,7 +815,7 @@ size_t http_parser_execute (http_parser *parser, parser->http_major *= 10; parser->http_major += ch - '0'; - if (parser->http_major > 999) { + if (UNLIKELY(parser->http_major > 999)) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } @@ -788,24 +825,24 @@ size_t http_parser_execute (http_parser *parser, /* first digit of minor HTTP version */ case s_res_first_http_minor: - if (!IS_NUM(ch)) { + if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_minor = ch - '0'; - parser->state = s_res_http_minor; + UPDATE_STATE(s_res_http_minor); break; /* minor HTTP version or end of request line */ case s_res_http_minor: { if (ch == ' ') { - parser->state = s_res_first_status_code; + UPDATE_STATE(s_res_first_status_code); break; } - if (!IS_NUM(ch)) { + if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } @@ -813,7 +850,7 @@ size_t http_parser_execute (http_parser *parser, parser->http_minor *= 10; parser->http_minor += ch - '0'; - if (parser->http_minor > 999) { + if (UNLIKELY(parser->http_minor > 999)) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } @@ -832,7 +869,7 @@ size_t http_parser_execute (http_parser *parser, goto error; } parser->status_code = ch - '0'; - parser->state = s_res_status_code; + UPDATE_STATE(s_res_status_code); break; } @@ -841,13 +878,13 @@ size_t http_parser_execute (http_parser *parser, if (!IS_NUM(ch)) { switch (ch) { case ' ': - parser->state = s_res_status_start; + UPDATE_STATE(s_res_status_start); break; case CR: - parser->state = s_res_line_almost_done; + UPDATE_STATE(s_res_line_almost_done); break; case LF: - parser->state = s_header_field_start; + UPDATE_STATE(s_header_field_start); break; default: SET_ERRNO(HPE_INVALID_STATUS); @@ -859,7 +896,7 @@ size_t http_parser_execute (http_parser *parser, parser->status_code *= 10; parser->status_code += ch - '0'; - if (parser->status_code > 999) { + if (UNLIKELY(parser->status_code > 999)) { SET_ERRNO(HPE_INVALID_STATUS); goto error; } @@ -870,30 +907,30 @@ size_t http_parser_execute (http_parser *parser, case s_res_status_start: { if (ch == CR) { - parser->state = s_res_line_almost_done; + UPDATE_STATE(s_res_line_almost_done); break; } if (ch == LF) { - parser->state = s_header_field_start; + UPDATE_STATE(s_header_field_start); break; } MARK(status); - parser->state = s_res_status; + UPDATE_STATE(s_res_status); parser->index = 0; break; } case s_res_status: if (ch == CR) { - parser->state = s_res_line_almost_done; + UPDATE_STATE(s_res_line_almost_done); CALLBACK_DATA(status); break; } if (ch == LF) { - parser->state = s_header_field_start; + UPDATE_STATE(s_header_field_start); CALLBACK_DATA(status); break; } @@ -902,7 +939,7 @@ size_t http_parser_execute (http_parser *parser, case s_res_line_almost_done: STRICT_CHECK(ch != LF); - parser->state = s_header_field_start; + UPDATE_STATE(s_header_field_start); break; case s_start_req: @@ -912,7 +949,7 @@ size_t http_parser_execute (http_parser *parser, parser->flags = 0; parser->content_length = ULLONG_MAX; - if (!IS_ALPHA(ch)) { + if (UNLIKELY(!IS_ALPHA(ch))) { SET_ERRNO(HPE_INVALID_METHOD); goto error; } @@ -939,7 +976,7 @@ size_t http_parser_execute (http_parser *parser, SET_ERRNO(HPE_INVALID_METHOD); goto error; } - parser->state = s_req_method; + UPDATE_STATE(s_req_method); CALLBACK_NOTIFY(message_begin); @@ -949,14 +986,14 @@ size_t http_parser_execute (http_parser *parser, case s_req_method: { const char *matcher; - if (ch == '\0') { + if (UNLIKELY(ch == '\0')) { SET_ERRNO(HPE_INVALID_METHOD); goto error; } matcher = method_strings[parser->method]; if (ch == ' ' && matcher[parser->index] == '\0') { - parser->state = s_req_spaces_before_url; + UPDATE_STATE(s_req_spaces_before_url); } else if (ch == matcher[parser->index]) { ; /* nada */ } else if (parser->method == HTTP_CONNECT) { @@ -1037,11 +1074,11 @@ size_t http_parser_execute (http_parser *parser, MARK(url); if (parser->method == HTTP_CONNECT) { - parser->state = s_req_server_start; + UPDATE_STATE(s_req_server_start); } - parser->state = parse_url_char((enum state)parser->state, ch); - if (parser->state == s_dead) { + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { SET_ERRNO(HPE_INVALID_URL); goto error; } @@ -1062,8 +1099,8 @@ size_t http_parser_execute (http_parser *parser, SET_ERRNO(HPE_INVALID_URL); goto error; default: - parser->state = parse_url_char((enum state)parser->state, ch); - if (parser->state == s_dead) { + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { SET_ERRNO(HPE_INVALID_URL); goto error; } @@ -1082,21 +1119,21 @@ size_t http_parser_execute (http_parser *parser, { switch (ch) { case ' ': - parser->state = s_req_http_start; + UPDATE_STATE(s_req_http_start); CALLBACK_DATA(url); break; case CR: case LF: parser->http_major = 0; parser->http_minor = 9; - parser->state = (ch == CR) ? + UPDATE_STATE((ch == CR) ? s_req_line_almost_done : - s_header_field_start; + s_header_field_start); CALLBACK_DATA(url); break; default: - parser->state = parse_url_char((enum state)parser->state, ch); - if (parser->state == s_dead) { + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { SET_ERRNO(HPE_INVALID_URL); goto error; } @@ -1107,7 +1144,7 @@ size_t http_parser_execute (http_parser *parser, case s_req_http_start: switch (ch) { case 'H': - parser->state = s_req_http_H; + UPDATE_STATE(s_req_http_H); break; case ' ': break; @@ -1119,44 +1156,44 @@ size_t http_parser_execute (http_parser *parser, case s_req_http_H: STRICT_CHECK(ch != 'T'); - parser->state = s_req_http_HT; + UPDATE_STATE(s_req_http_HT); break; case s_req_http_HT: STRICT_CHECK(ch != 'T'); - parser->state = s_req_http_HTT; + UPDATE_STATE(s_req_http_HTT); break; case s_req_http_HTT: STRICT_CHECK(ch != 'P'); - parser->state = s_req_http_HTTP; + UPDATE_STATE(s_req_http_HTTP); break; case s_req_http_HTTP: STRICT_CHECK(ch != '/'); - parser->state = s_req_first_http_major; + UPDATE_STATE(s_req_first_http_major); break; /* first digit of major HTTP version */ case s_req_first_http_major: - if (ch < '1' || ch > '9') { + if (UNLIKELY(ch < '1' || ch > '9')) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_major = ch - '0'; - parser->state = s_req_http_major; + UPDATE_STATE(s_req_http_major); break; /* major HTTP version or dot */ case s_req_http_major: { if (ch == '.') { - parser->state = s_req_first_http_minor; + UPDATE_STATE(s_req_first_http_minor); break; } - if (!IS_NUM(ch)) { + if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } @@ -1164,7 +1201,7 @@ size_t http_parser_execute (http_parser *parser, parser->http_major *= 10; parser->http_major += ch - '0'; - if (parser->http_major > 999) { + if (UNLIKELY(parser->http_major > 999)) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } @@ -1174,31 +1211,31 @@ size_t http_parser_execute (http_parser *parser, /* first digit of minor HTTP version */ case s_req_first_http_minor: - if (!IS_NUM(ch)) { + if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_minor = ch - '0'; - parser->state = s_req_http_minor; + UPDATE_STATE(s_req_http_minor); break; /* minor HTTP version or end of request line */ case s_req_http_minor: { if (ch == CR) { - parser->state = s_req_line_almost_done; + UPDATE_STATE(s_req_line_almost_done); break; } if (ch == LF) { - parser->state = s_header_field_start; + UPDATE_STATE(s_header_field_start); break; } /* XXX allow spaces after digit? */ - if (!IS_NUM(ch)) { + if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } @@ -1206,7 +1243,7 @@ size_t http_parser_execute (http_parser *parser, parser->http_minor *= 10; parser->http_minor += ch - '0'; - if (parser->http_minor > 999) { + if (UNLIKELY(parser->http_minor > 999)) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } @@ -1217,32 +1254,32 @@ size_t http_parser_execute (http_parser *parser, /* end of request line */ case s_req_line_almost_done: { - if (ch != LF) { + if (UNLIKELY(ch != LF)) { SET_ERRNO(HPE_LF_EXPECTED); goto error; } - parser->state = s_header_field_start; + UPDATE_STATE(s_header_field_start); break; } case s_header_field_start: { if (ch == CR) { - parser->state = s_headers_almost_done; + UPDATE_STATE(s_headers_almost_done); break; } if (ch == LF) { /* they might be just sending \n instead of \r\n so this would be * the second \n to denote the end of headers*/ - parser->state = s_headers_almost_done; - goto reexecute_byte; + UPDATE_STATE(s_headers_almost_done); + REEXECUTE(); } c = TOKEN(ch); - if (!c) { + if (UNLIKELY(!c)) { SET_ERRNO(HPE_INVALID_HEADER_TOKEN); goto error; } @@ -1250,7 +1287,7 @@ size_t http_parser_execute (http_parser *parser, MARK(header_field); parser->index = 0; - parser->state = s_header_field; + UPDATE_STATE(s_header_field); switch (c) { case 'c': @@ -1278,9 +1315,14 @@ size_t http_parser_execute (http_parser *parser, case s_header_field: { - c = TOKEN(ch); + const char* start = p; + for (; p != data + len; p++) { + ch = *p; + c = TOKEN(ch); + + if (!c) + break; - if (c) { switch (parser->header_state) { case h_general: break; @@ -1381,11 +1423,17 @@ size_t http_parser_execute (http_parser *parser, assert(0 && "Unknown header_state"); break; } + } + + COUNT_HEADER_SIZE(p - start); + + if (p == data + len) { + --p; break; } if (ch == ':') { - parser->state = s_header_value_discard_ws; + UPDATE_STATE(s_header_value_discard_ws); CALLBACK_DATA(header_field); break; } @@ -1398,12 +1446,12 @@ size_t http_parser_execute (http_parser *parser, if (ch == ' ' || ch == '\t') break; if (ch == CR) { - parser->state = s_header_value_discard_ws_almost_done; + UPDATE_STATE(s_header_value_discard_ws_almost_done); break; } if (ch == LF) { - parser->state = s_header_value_discard_lws; + UPDATE_STATE(s_header_value_discard_lws); break; } @@ -1413,7 +1461,7 @@ size_t http_parser_execute (http_parser *parser, { MARK(header_value); - parser->state = s_header_value; + UPDATE_STATE(s_header_value); parser->index = 0; c = LOWER(ch); @@ -1434,7 +1482,7 @@ size_t http_parser_execute (http_parser *parser, break; case h_content_length: - if (!IS_NUM(ch)) { + if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); goto error; } @@ -1449,11 +1497,17 @@ size_t http_parser_execute (http_parser *parser, /* looking for 'Connection: close' */ } else if (c == 'c') { parser->header_state = h_matching_connection_close; + } else if (c == 'u') { + parser->header_state = h_matching_connection_upgrade; } else { - parser->header_state = h_general; + parser->header_state = h_matching_connection_token; } break; + /* Multi-value `Connection` header */ + case h_matching_connection_token_start: + break; + default: parser->header_state = h_general; break; @@ -1463,98 +1517,185 @@ size_t http_parser_execute (http_parser *parser, case s_header_value: { - - if (ch == CR) { - parser->state = s_header_almost_done; - CALLBACK_DATA(header_value); - break; - } - - if (ch == LF) { - parser->state = s_header_almost_done; - CALLBACK_DATA_NOADVANCE(header_value); - goto reexecute_byte; - } - - c = LOWER(ch); - - switch (parser->header_state) { - case h_general: - break; - - case h_connection: - case h_transfer_encoding: - assert(0 && "Shouldn't get here."); - break; - - case h_content_length: - { - uint64_t t; - - if (ch == ' ') break; - - if (!IS_NUM(ch)) { - SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); - goto error; - } - - t = parser->content_length; - t *= 10; - t += ch - '0'; - - /* Overflow? Test against a conservative limit for simplicity. */ - if ((ULLONG_MAX - 10) / 10 < parser->content_length) { - SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); - goto error; - } - - parser->content_length = t; + const char* start = p; + enum header_states h_state = parser->header_state; + for (; p != data + len; p++) { + ch = *p; + if (ch == CR) { + UPDATE_STATE(s_header_almost_done); + parser->header_state = h_state; + CALLBACK_DATA(header_value); break; } - /* Transfer-Encoding: chunked */ - case h_matching_transfer_encoding_chunked: - parser->index++; - if (parser->index > sizeof(CHUNKED)-1 - || c != CHUNKED[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(CHUNKED)-2) { - parser->header_state = h_transfer_encoding_chunked; + if (ch == LF) { + UPDATE_STATE(s_header_almost_done); + COUNT_HEADER_SIZE(p - start); + parser->header_state = h_state; + CALLBACK_DATA_NOADVANCE(header_value); + REEXECUTE(); + } + + c = LOWER(ch); + + switch (h_state) { + case h_general: + { + const char* p_cr; + const char* p_lf; + size_t limit = data + len - p; + + limit = MIN(limit, HTTP_MAX_HEADER_SIZE); + + p_cr = memchr(p, CR, limit); + p_lf = memchr(p, LF, limit); + if (p_cr != NULL) { + if (p_lf != NULL && p_cr >= p_lf) + p = p_lf; + else + p = p_cr; + } else if (UNLIKELY(p_lf != NULL)) { + p = p_lf; + } else { + p = data + len; + } + --p; + + break; } - break; - /* looking for 'Connection: keep-alive' */ - case h_matching_connection_keep_alive: - parser->index++; - if (parser->index > sizeof(KEEP_ALIVE)-1 - || c != KEEP_ALIVE[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(KEEP_ALIVE)-2) { - parser->header_state = h_connection_keep_alive; + case h_connection: + case h_transfer_encoding: + assert(0 && "Shouldn't get here."); + break; + + case h_content_length: + { + uint64_t t; + + if (ch == ' ') break; + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + parser->header_state = h_state; + goto error; + } + + t = parser->content_length; + t *= 10; + t += ch - '0'; + + /* Overflow? Test against a conservative limit for simplicity. */ + if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + parser->header_state = h_state; + goto error; + } + + parser->content_length = t; + break; } - break; - /* looking for 'Connection: close' */ - case h_matching_connection_close: - parser->index++; - if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { - parser->header_state = h_general; - } else if (parser->index == sizeof(CLOSE)-2) { - parser->header_state = h_connection_close; - } - break; + /* Transfer-Encoding: chunked */ + case h_matching_transfer_encoding_chunked: + parser->index++; + if (parser->index > sizeof(CHUNKED)-1 + || c != CHUNKED[parser->index]) { + h_state = h_general; + } else if (parser->index == sizeof(CHUNKED)-2) { + h_state = h_transfer_encoding_chunked; + } + break; - case h_transfer_encoding_chunked: - case h_connection_keep_alive: - case h_connection_close: - if (ch != ' ') parser->header_state = h_general; - break; + case h_matching_connection_token_start: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + h_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + h_state = h_matching_connection_close; + } else if (c == 'u') { + h_state = h_matching_connection_upgrade; + } else if (STRICT_TOKEN(c)) { + h_state = h_matching_connection_token; + } else { + h_state = h_general; + } + break; - default: - parser->state = s_header_value; - parser->header_state = h_general; - break; + /* looking for 'Connection: keep-alive' */ + case h_matching_connection_keep_alive: + parser->index++; + if (parser->index > sizeof(KEEP_ALIVE)-1 + || c != KEEP_ALIVE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(KEEP_ALIVE)-2) { + h_state = h_connection_keep_alive; + } + break; + + /* looking for 'Connection: close' */ + case h_matching_connection_close: + parser->index++; + if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(CLOSE)-2) { + h_state = h_connection_close; + } + break; + + /* looking for 'Connection: upgrade' */ + case h_matching_connection_upgrade: + parser->index++; + if (parser->index > sizeof(UPGRADE) - 1 || + c != UPGRADE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(UPGRADE)-2) { + h_state = h_connection_upgrade; + } + break; + + case h_matching_connection_token: + if (ch == ',') { + h_state = h_matching_connection_token_start; + parser->index = 0; + } + break; + + case h_transfer_encoding_chunked: + if (ch != ' ') h_state = h_general; + break; + + case h_connection_keep_alive: + case h_connection_close: + case h_connection_upgrade: + if (ch == ',') { + if (h_state == h_connection_keep_alive) { + parser->flags |= F_CONNECTION_KEEP_ALIVE; + } else if (h_state == h_connection_close) { + parser->flags |= F_CONNECTION_CLOSE; + } else if (h_state == h_connection_upgrade) { + parser->flags |= F_CONNECTION_UPGRADE; + } + h_state = h_matching_connection_token_start; + parser->index = 0; + } else if (ch != ' ') { + h_state = h_matching_connection_token; + } + break; + + default: + UPDATE_STATE(s_header_value); + h_state = h_general; + break; + } } + parser->header_state = h_state; + + COUNT_HEADER_SIZE(p - start); + + if (p == data + len) + --p; break; } @@ -1562,15 +1703,15 @@ size_t http_parser_execute (http_parser *parser, { STRICT_CHECK(ch != LF); - parser->state = s_header_value_lws; + UPDATE_STATE(s_header_value_lws); break; } case s_header_value_lws: { if (ch == ' ' || ch == '\t') { - parser->state = s_header_value_start; - goto reexecute_byte; + UPDATE_STATE(s_header_value_start); + REEXECUTE(); } /* finished the header */ @@ -1584,32 +1725,52 @@ size_t http_parser_execute (http_parser *parser, case h_transfer_encoding_chunked: parser->flags |= F_CHUNKED; break; + case h_connection_upgrade: + parser->flags |= F_CONNECTION_UPGRADE; + break; default: break; } - parser->state = s_header_field_start; - goto reexecute_byte; + UPDATE_STATE(s_header_field_start); + REEXECUTE(); } case s_header_value_discard_ws_almost_done: { STRICT_CHECK(ch != LF); - parser->state = s_header_value_discard_lws; + UPDATE_STATE(s_header_value_discard_lws); break; } case s_header_value_discard_lws: { if (ch == ' ' || ch == '\t') { - parser->state = s_header_value_discard_ws; + UPDATE_STATE(s_header_value_discard_ws); break; } else { + switch (parser->header_state) { + case h_connection_keep_alive: + parser->flags |= F_CONNECTION_KEEP_ALIVE; + break; + case h_connection_close: + parser->flags |= F_CONNECTION_CLOSE; + break; + case h_connection_upgrade: + parser->flags |= F_CONNECTION_UPGRADE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + default: + break; + } + /* header value was empty */ MARK(header_value); - parser->state = s_header_field_start; + UPDATE_STATE(s_header_field_start); CALLBACK_DATA_NOADVANCE(header_value); - goto reexecute_byte; + REEXECUTE(); } } @@ -1619,16 +1780,18 @@ size_t http_parser_execute (http_parser *parser, if (parser->flags & F_TRAILING) { /* End of a chunked request */ - parser->state = NEW_MESSAGE(); + UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); break; } - parser->state = s_headers_done; + UPDATE_STATE(s_headers_done); /* Set this here so that on_headers_complete() callbacks can see it */ parser->upgrade = - (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT); + ((parser->flags & (F_UPGRADE | F_CONNECTION_UPGRADE)) == + (F_UPGRADE | F_CONNECTION_UPGRADE) || + parser->method == HTTP_CONNECT); /* Here we call the headers_complete callback. This is somewhat * different than other callbacks because if the user returns 1, we @@ -1650,15 +1813,15 @@ size_t http_parser_execute (http_parser *parser, default: SET_ERRNO(HPE_CB_headers_complete); - return p - data; /* Error */ + RETURN(p - data); /* Error */ } } if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { - return p - data; + RETURN(p - data); } - goto reexecute_byte; + REEXECUTE(); } case s_headers_done: @@ -1669,34 +1832,34 @@ size_t http_parser_execute (http_parser *parser, /* Exit, the rest of the connect is in a different protocol. */ if (parser->upgrade) { - parser->state = NEW_MESSAGE(); + UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); - return (p - data) + 1; + RETURN((p - data) + 1); } if (parser->flags & F_SKIPBODY) { - parser->state = NEW_MESSAGE(); + UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); } else if (parser->flags & F_CHUNKED) { /* chunked encoding - ignore Content-Length header */ - parser->state = s_chunk_size_start; + UPDATE_STATE(s_chunk_size_start); } else { if (parser->content_length == 0) { /* Content-Length header given but zero: Content-Length: 0\r\n */ - parser->state = NEW_MESSAGE(); + UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); } else if (parser->content_length != ULLONG_MAX) { /* Content-Length header given and non-zero */ - parser->state = s_body_identity; + UPDATE_STATE(s_body_identity); } else { if (parser->type == HTTP_REQUEST || !http_message_needs_eof(parser)) { /* Assume content-length 0 - read the next */ - parser->state = NEW_MESSAGE(); + UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); } else { /* Read body until EOF */ - parser->state = s_body_identity_eof; + UPDATE_STATE(s_body_identity_eof); } } } @@ -1722,7 +1885,7 @@ size_t http_parser_execute (http_parser *parser, p += to_read - 1; if (parser->content_length == 0) { - parser->state = s_message_done; + UPDATE_STATE(s_message_done); /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. * @@ -1734,7 +1897,7 @@ size_t http_parser_execute (http_parser *parser, * important for applications, but let's keep it for now. */ CALLBACK_DATA_(body, p - body_mark + 1, p - data); - goto reexecute_byte; + REEXECUTE(); } break; @@ -1748,7 +1911,7 @@ size_t http_parser_execute (http_parser *parser, break; case s_message_done: - parser->state = NEW_MESSAGE(); + UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); break; @@ -1758,13 +1921,13 @@ size_t http_parser_execute (http_parser *parser, assert(parser->flags & F_CHUNKED); unhex_val = unhex[(unsigned char)ch]; - if (unhex_val == -1) { + if (UNLIKELY(unhex_val == -1)) { SET_ERRNO(HPE_INVALID_CHUNK_SIZE); goto error; } parser->content_length = unhex_val; - parser->state = s_chunk_size; + UPDATE_STATE(s_chunk_size); break; } @@ -1775,7 +1938,7 @@ size_t http_parser_execute (http_parser *parser, assert(parser->flags & F_CHUNKED); if (ch == CR) { - parser->state = s_chunk_size_almost_done; + UPDATE_STATE(s_chunk_size_almost_done); break; } @@ -1783,7 +1946,7 @@ size_t http_parser_execute (http_parser *parser, if (unhex_val == -1) { if (ch == ';' || ch == ' ') { - parser->state = s_chunk_parameters; + UPDATE_STATE(s_chunk_parameters); break; } @@ -1796,7 +1959,7 @@ size_t http_parser_execute (http_parser *parser, t += unhex_val; /* Overflow? Test against a conservative limit for simplicity. */ - if ((ULLONG_MAX - 16) / 16 < parser->content_length) { + if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); goto error; } @@ -1810,7 +1973,7 @@ size_t http_parser_execute (http_parser *parser, assert(parser->flags & F_CHUNKED); /* just ignore this shit. TODO check for overflow */ if (ch == CR) { - parser->state = s_chunk_size_almost_done; + UPDATE_STATE(s_chunk_size_almost_done); break; } break; @@ -1825,9 +1988,9 @@ size_t http_parser_execute (http_parser *parser, if (parser->content_length == 0) { parser->flags |= F_TRAILING; - parser->state = s_header_field_start; + UPDATE_STATE(s_header_field_start); } else { - parser->state = s_chunk_data; + UPDATE_STATE(s_chunk_data); } break; } @@ -1849,7 +2012,7 @@ size_t http_parser_execute (http_parser *parser, p += to_read - 1; if (parser->content_length == 0) { - parser->state = s_chunk_data_almost_done; + UPDATE_STATE(s_chunk_data_almost_done); } break; @@ -1859,7 +2022,7 @@ size_t http_parser_execute (http_parser *parser, assert(parser->flags & F_CHUNKED); assert(parser->content_length == 0); STRICT_CHECK(ch != CR); - parser->state = s_chunk_data_done; + UPDATE_STATE(s_chunk_data_done); CALLBACK_DATA(body); break; @@ -1867,7 +2030,7 @@ size_t http_parser_execute (http_parser *parser, assert(parser->flags & F_CHUNKED); STRICT_CHECK(ch != LF); parser->nread = 0; - parser->state = s_chunk_size_start; + UPDATE_STATE(s_chunk_size_start); break; default: @@ -1899,14 +2062,14 @@ size_t http_parser_execute (http_parser *parser, CALLBACK_DATA_NOADVANCE(body); CALLBACK_DATA_NOADVANCE(status); - return len; + RETURN(len); error: if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { SET_ERRNO(HPE_UNKNOWN); } - return (p - data); + RETURN(p - data); } diff --git a/third-party/http-parser/http_parser.h b/third-party/http-parser/http_parser.h index 2f4ab9bd..936cddb1 100644 --- a/third-party/http-parser/http_parser.h +++ b/third-party/http-parser/http_parser.h @@ -136,9 +136,10 @@ enum flags { F_CHUNKED = 1 << 0 , F_CONNECTION_KEEP_ALIVE = 1 << 1 , F_CONNECTION_CLOSE = 1 << 2 - , F_TRAILING = 1 << 3 - , F_UPGRADE = 1 << 4 - , F_SKIPBODY = 1 << 5 + , F_CONNECTION_UPGRADE = 1 << 3 + , F_TRAILING = 1 << 4 + , F_UPGRADE = 1 << 5 + , F_SKIPBODY = 1 << 6 }; diff --git a/third-party/http-parser/test.c b/third-party/http-parser/test.c index f09e1063..6c45d59d 100644 --- a/third-party/http-parser/test.c +++ b/third-party/http-parser/test.c @@ -950,6 +950,42 @@ const struct message requests[] = ,.body= "" } +#define CONNECTION_MULTI 35 +, {.name = "multiple connection header values with folding" + ,.type= HTTP_REQUEST + ,.raw= "GET /demo HTTP/1.1\r\n" + "Host: example.com\r\n" + "Connection: Something,\r\n" + " Upgrade, ,Keep-Alive\r\n" + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Upgrade: WebSocket\r\n" + "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="Hot diggity dogg" + ,.headers= { { "Host", "example.com" } + , { "Connection", "Something, Upgrade, ,Keep-Alive" } + , { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" } + , { "Sec-WebSocket-Protocol", "sample" } + , { "Upgrade", "WebSocket" } + , { "Sec-WebSocket-Key1", "4 @1 46546xW%0l 1 5" } + , { "Origin", "http://example.com" } + } + ,.body= "" + } + , {.name= NULL } /* sentinel */ };