From e04f3486f21452b1f24d21fb5f480a00d031ae82 Mon Sep 17 00:00:00 2001 From: Raoul Hecky Date: Tue, 16 Jan 2024 13:40:48 +0000 Subject: [PATCH] update http_parser. We need to port to llhttp --- src/lib/http-parser/http_parser.c | 489 ++++++++++++++++++------------ src/lib/http-parser/http_parser.h | 35 ++- 2 files changed, 323 insertions(+), 201 deletions(-) diff --git a/src/lib/http-parser/http_parser.c b/src/lib/http-parser/http_parser.c index 895bf0c7..a6904686 100644 --- a/src/lib/http-parser/http_parser.c +++ b/src/lib/http-parser/http_parser.c @@ -1,7 +1,4 @@ -/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev - * - * Additional changes are licensed under the same terms as NGINX and - * copyright Joyent, Inc. and other Node contributors. All rights reserved. +/* Copyright Joyent, Inc. and other Node contributors. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to @@ -25,10 +22,11 @@ #include #include #include -#include #include #include +static uint32_t max_header_size = HTTP_MAX_HEADER_SIZE; + #ifndef ULLONG_MAX # define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ #endif @@ -53,6 +51,7 @@ #define SET_ERRNO(e) \ do { \ + parser->nread = nread; \ parser->http_errno = (e); \ } while(0) @@ -60,6 +59,7 @@ do { \ #define UPDATE_STATE(V) p_state = (enum state) (V); #define RETURN(V) \ do { \ + parser->nread = nread; \ parser->state = CURRENT_STATE(); \ return (V); \ } while (0); @@ -141,20 +141,20 @@ 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 + * line) to exceed 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 + * make the web a little safer. 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))) { \ + nread += (uint32_t)(V); \ + if (UNLIKELY(nread > max_header_size)) { \ SET_ERRNO(HPE_HEADER_OVERFLOW); \ goto error; \ } \ @@ -196,7 +196,7 @@ static const char tokens[256] = { /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ 0, 0, 0, 0, 0, 0, 0, 0, /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ - 0, '!', 0, '#', '$', '%', '&', '\'', + ' ', '!', 0, '#', '$', '%', '&', '\'', /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ 0, 0, '*', '+', 0, '-', '.', 0, /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ @@ -286,10 +286,10 @@ enum state , s_res_HT , s_res_HTT , s_res_HTTP - , s_res_first_http_major , s_res_http_major - , s_res_first_http_minor + , s_res_http_dot , s_res_http_minor + , s_res_http_end , s_res_first_status_code , s_res_status_code , s_res_status_start @@ -316,10 +316,12 @@ enum state , s_req_http_HT , s_req_http_HTT , s_req_http_HTTP - , s_req_first_http_major + , s_req_http_I + , s_req_http_IC , s_req_http_major - , s_req_first_http_minor + , s_req_http_dot , s_req_http_minor + , s_req_http_end , s_req_line_almost_done , s_header_field_start @@ -374,10 +376,15 @@ enum header_states , h_connection , h_content_length + , h_content_length_num + , h_content_length_ws , h_transfer_encoding , h_upgrade + , h_matching_transfer_encoding_token_start , h_matching_transfer_encoding_chunked + , h_matching_transfer_encoding_token + , h_matching_connection_token_start , h_matching_connection_keep_alive , h_matching_connection_close @@ -421,14 +428,14 @@ enum http_host_state (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ (c) == '$' || (c) == ',') -#define STRICT_TOKEN(c) (tokens[(unsigned char)c]) +#define STRICT_TOKEN(c) ((c == ' ') ? 0 : tokens[(unsigned char)c]) #if HTTP_PARSER_STRICT -#define TOKEN(c) (tokens[(unsigned char)c]) +#define TOKEN(c) STRICT_TOKEN(c) #define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) #define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') #else -#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) +#define TOKEN(c) tokens[(unsigned char)c] #define IS_URL_CHAR(c) \ (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) #define IS_HOST_CHAR(c) \ @@ -542,7 +549,7 @@ parse_url_char(enum state s, const char ch) return s_dead; } - /* FALLTHROUGH */ + /* fall through */ case s_req_server_start: case s_req_server: if (ch == '/') { @@ -646,6 +653,9 @@ size_t http_parser_execute (http_parser *parser, const char *status_mark = 0; enum state p_state = (enum state) parser->state; const unsigned int lenient = parser->lenient_http_headers; + const unsigned int allow_chunked_length = parser->allow_chunked_length; + + uint32_t nread = parser->nread; /* We're in an error state. Don't bother doing anything. */ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { @@ -723,6 +733,7 @@ size_t http_parser_execute (http_parser *parser, if (ch == CR || ch == LF) break; parser->flags = 0; + parser->uses_transfer_encoding = 0; parser->content_length = ULLONG_MAX; if (ch == 'H') { @@ -757,21 +768,17 @@ size_t http_parser_execute (http_parser *parser, case s_start_res: { + if (ch == CR || ch == LF) + break; parser->flags = 0; + parser->uses_transfer_encoding = 0; parser->content_length = ULLONG_MAX; - switch (ch) { - case 'H': - UPDATE_STATE(s_res_H); - break; - - case CR: - case LF: - break; - - default: - SET_ERRNO(HPE_INVALID_CONSTANT); - goto error; + if (ch == 'H') { + UPDATE_STATE(s_res_H); + } else { + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; } CALLBACK_NOTIFY(message_begin); @@ -795,75 +802,48 @@ size_t http_parser_execute (http_parser *parser, case s_res_HTTP: STRICT_CHECK(ch != '/'); - UPDATE_STATE(s_res_first_http_major); + UPDATE_STATE(s_res_http_major); break; - case s_res_first_http_major: - if (UNLIKELY(ch < '0' || ch > '9')) { + case s_res_http_major: + if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_major = ch - '0'; - UPDATE_STATE(s_res_http_major); + UPDATE_STATE(s_res_http_dot); break; - /* major HTTP version or dot */ - case s_res_http_major: + case s_res_http_dot: { - if (ch == '.') { - UPDATE_STATE(s_res_first_http_minor); - break; - } - - if (!IS_NUM(ch)) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_major *= 10; - parser->http_major += ch - '0'; - - if (UNLIKELY(parser->http_major > 999)) { + if (UNLIKELY(ch != '.')) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } + UPDATE_STATE(s_res_http_minor); break; } - /* first digit of minor HTTP version */ - case s_res_first_http_minor: + case s_res_http_minor: if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_minor = ch - '0'; - UPDATE_STATE(s_res_http_minor); + UPDATE_STATE(s_res_http_end); break; - /* minor HTTP version or end of request line */ - case s_res_http_minor: + case s_res_http_end: { - if (ch == ' ') { - UPDATE_STATE(s_res_first_status_code); - break; - } - - if (UNLIKELY(!IS_NUM(ch))) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_minor *= 10; - parser->http_minor += ch - '0'; - - if (UNLIKELY(parser->http_minor > 999)) { + if (UNLIKELY(ch != ' ')) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } + UPDATE_STATE(s_res_first_status_code); break; } @@ -890,10 +870,9 @@ size_t http_parser_execute (http_parser *parser, UPDATE_STATE(s_res_status_start); break; case CR: - UPDATE_STATE(s_res_line_almost_done); - break; case LF: - UPDATE_STATE(s_header_field_start); + UPDATE_STATE(s_res_status_start); + REEXECUTE(); break; default: SET_ERRNO(HPE_INVALID_STATUS); @@ -915,19 +894,13 @@ size_t http_parser_execute (http_parser *parser, case s_res_status_start: { - if (ch == CR) { - UPDATE_STATE(s_res_line_almost_done); - break; - } - - if (ch == LF) { - UPDATE_STATE(s_header_field_start); - break; - } - MARK(status); UPDATE_STATE(s_res_status); parser->index = 0; + + if (ch == CR || ch == LF) + REEXECUTE(); + break; } @@ -956,6 +929,7 @@ size_t http_parser_execute (http_parser *parser, if (ch == CR || ch == LF) break; parser->flags = 0; + parser->uses_transfer_encoding = 0; parser->content_length = ULLONG_MAX; if (UNLIKELY(!IS_ALPHA(ch))) { @@ -980,7 +954,7 @@ size_t http_parser_execute (http_parser *parser, /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ break; case 'R': parser->method = HTTP_REPORT; /* or REBIND */ break; - case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break; + case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH, SOURCE */ break; case 'T': parser->method = HTTP_TRACE; break; case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE, UNBIND, UNLINK */ break; default: @@ -1007,7 +981,7 @@ size_t http_parser_execute (http_parser *parser, UPDATE_STATE(s_req_spaces_before_url); } else if (ch == matcher[parser->index]) { ; /* nada */ - } else if (IS_ALPHA(ch)) { + } else if ((ch >= 'A' && ch <= 'Z') || ch == '-') { switch (parser->method << 16 | parser->index << 8 | ch) { #define XX(meth, pos, ch, new_meth) \ @@ -1016,31 +990,28 @@ size_t http_parser_execute (http_parser *parser, XX(POST, 1, 'U', PUT) XX(POST, 1, 'A', PATCH) + XX(POST, 1, 'R', PROPFIND) + XX(PUT, 2, 'R', PURGE) XX(CONNECT, 1, 'H', CHECKOUT) XX(CONNECT, 2, 'P', COPY) XX(MKCOL, 1, 'O', MOVE) XX(MKCOL, 1, 'E', MERGE) + XX(MKCOL, 1, '-', MSEARCH) XX(MKCOL, 2, 'A', MKACTIVITY) XX(MKCOL, 3, 'A', MKCALENDAR) XX(SUBSCRIBE, 1, 'E', SEARCH) + XX(SUBSCRIBE, 1, 'O', SOURCE) XX(REPORT, 2, 'B', REBIND) - XX(POST, 1, 'R', PROPFIND) XX(PROPFIND, 4, 'P', PROPPATCH) - XX(PUT, 2, 'R', PURGE) XX(LOCK, 1, 'I', LINK) XX(UNLOCK, 2, 'S', UNSUBSCRIBE) XX(UNLOCK, 2, 'B', UNBIND) XX(UNLOCK, 3, 'I', UNLINK) #undef XX - default: SET_ERRNO(HPE_INVALID_METHOD); goto error; } - } else if (ch == '-' && - parser->index == 1 && - parser->method == HTTP_MKCOL) { - parser->method = HTTP_MSEARCH; } else { SET_ERRNO(HPE_INVALID_METHOD); goto error; @@ -1125,11 +1096,17 @@ size_t http_parser_execute (http_parser *parser, case s_req_http_start: switch (ch) { + case ' ': + break; case 'H': UPDATE_STATE(s_req_http_H); break; - case ' ': - break; + case 'I': + if (parser->method == HTTP_SOURCE) { + UPDATE_STATE(s_req_http_I); + break; + } + /* fall through */ default: SET_ERRNO(HPE_INVALID_CONSTANT); goto error; @@ -1151,59 +1128,53 @@ size_t http_parser_execute (http_parser *parser, UPDATE_STATE(s_req_http_HTTP); break; - case s_req_http_HTTP: - STRICT_CHECK(ch != '/'); - UPDATE_STATE(s_req_first_http_major); + case s_req_http_I: + STRICT_CHECK(ch != 'C'); + UPDATE_STATE(s_req_http_IC); break; - /* first digit of major HTTP version */ - case s_req_first_http_major: - if (UNLIKELY(ch < '1' || ch > '9')) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } + case s_req_http_IC: + STRICT_CHECK(ch != 'E'); + UPDATE_STATE(s_req_http_HTTP); /* Treat "ICE" as "HTTP". */ + break; - parser->http_major = ch - '0'; + case s_req_http_HTTP: + STRICT_CHECK(ch != '/'); UPDATE_STATE(s_req_http_major); break; - /* major HTTP version or dot */ case s_req_http_major: - { - if (ch == '.') { - UPDATE_STATE(s_req_first_http_minor); - break; - } - if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } - parser->http_major *= 10; - parser->http_major += ch - '0'; + parser->http_major = ch - '0'; + UPDATE_STATE(s_req_http_dot); + break; - if (UNLIKELY(parser->http_major > 999)) { + case s_req_http_dot: + { + if (UNLIKELY(ch != '.')) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } + UPDATE_STATE(s_req_http_minor); break; } - /* first digit of minor HTTP version */ - case s_req_first_http_minor: + case s_req_http_minor: if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_VERSION); goto error; } parser->http_minor = ch - '0'; - UPDATE_STATE(s_req_http_minor); + UPDATE_STATE(s_req_http_end); break; - /* minor HTTP version or end of request line */ - case s_req_http_minor: + case s_req_http_end: { if (ch == CR) { UPDATE_STATE(s_req_line_almost_done); @@ -1215,21 +1186,8 @@ size_t http_parser_execute (http_parser *parser, break; } - /* XXX allow spaces after digit? */ - - if (UNLIKELY(!IS_NUM(ch))) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - - parser->http_minor *= 10; - parser->http_minor += ch - '0'; - - if (UNLIKELY(parser->http_minor > 999)) { - SET_ERRNO(HPE_INVALID_VERSION); - goto error; - } - + SET_ERRNO(HPE_INVALID_VERSION); + goto error; break; } @@ -1306,8 +1264,14 @@ size_t http_parser_execute (http_parser *parser, break; switch (parser->header_state) { - case h_general: + case h_general: { + size_t left = data + len - p; + const char* pe = p + MIN(left, max_header_size); + while (p+1 < pe && TOKEN(p[1])) { + p++; + } break; + } case h_C: parser->index++; @@ -1379,6 +1343,7 @@ size_t http_parser_execute (http_parser *parser, parser->header_state = h_general; } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { parser->header_state = h_transfer_encoding; + parser->uses_transfer_encoding = 1; } break; @@ -1407,13 +1372,14 @@ size_t http_parser_execute (http_parser *parser, } } - COUNT_HEADER_SIZE(p - start); - if (p == data + len) { --p; + COUNT_HEADER_SIZE(p - start); break; } + COUNT_HEADER_SIZE(p - start); + if (ch == ':') { UPDATE_STATE(s_header_value_discard_ws); CALLBACK_DATA(header_field); @@ -1437,7 +1403,7 @@ size_t http_parser_execute (http_parser *parser, break; } - /* FALLTHROUGH */ + /* fall through */ case s_header_value_start: { @@ -1459,10 +1425,14 @@ size_t http_parser_execute (http_parser *parser, if ('c' == c) { parser->header_state = h_matching_transfer_encoding_chunked; } else { - parser->header_state = h_general; + parser->header_state = h_matching_transfer_encoding_token; } break; + /* Multi-value `Transfer-Encoding` header */ + case h_matching_transfer_encoding_token_start: + break; + case h_content_length: if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); @@ -1476,6 +1446,12 @@ size_t http_parser_execute (http_parser *parser, parser->flags |= F_CONTENTLENGTH; parser->content_length = ch - '0'; + parser->header_state = h_content_length_num; + break; + + /* when obsolete line folding is encountered for content length + * continue to the s_header_value state */ + case h_content_length_ws: break; case h_connection: @@ -1533,29 +1509,25 @@ size_t http_parser_execute (http_parser *parser, 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 = (const char*) memchr(p, CR, limit); - p_lf = (const char*) 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; + { + size_t left = data + len - p; + const char* pe = p + MIN(left, max_header_size); + + for (; p != pe; p++) { + ch = *p; + if (ch == CR || ch == LF) { + --p; + break; + } + if (!lenient && !IS_HEADER_CHAR(ch)) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + } + if (p == data + len) + --p; + break; } - --p; - - break; - } case h_connection: case h_transfer_encoding: @@ -1563,10 +1535,18 @@ size_t http_parser_execute (http_parser *parser, break; case h_content_length: + if (ch == ' ') break; + h_state = h_content_length_num; + /* fall through */ + + case h_content_length_num: { uint64_t t; - if (ch == ' ') break; + if (ch == ' ') { + h_state = h_content_length_ws; + break; + } if (UNLIKELY(!IS_NUM(ch))) { SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); @@ -1589,17 +1569,48 @@ size_t http_parser_execute (http_parser *parser, break; } + case h_content_length_ws: + if (ch == ' ') break; + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + parser->header_state = h_state; + goto error; + /* Transfer-Encoding: chunked */ + case h_matching_transfer_encoding_token_start: + /* looking for 'Transfer-Encoding: chunked' */ + if ('c' == c) { + h_state = h_matching_transfer_encoding_chunked; + } else if (STRICT_TOKEN(c)) { + /* TODO(indutny): similar code below does this, but why? + * At the very least it seems to be inconsistent given that + * h_matching_transfer_encoding_token does not check for + * `STRICT_TOKEN` + */ + h_state = h_matching_transfer_encoding_token; + } else if (c == ' ' || c == '\t') { + /* Skip lws */ + } else { + h_state = h_general; + } + break; + case h_matching_transfer_encoding_chunked: parser->index++; if (parser->index > sizeof(CHUNKED)-1 || c != CHUNKED[parser->index]) { - h_state = h_general; + h_state = h_matching_transfer_encoding_token; } else if (parser->index == sizeof(CHUNKED)-2) { h_state = h_transfer_encoding_chunked; } break; + case h_matching_transfer_encoding_token: + if (ch == ',') { + h_state = h_matching_transfer_encoding_token_start; + parser->index = 0; + } + break; + case h_matching_connection_token_start: /* looking for 'Connection: keep-alive' */ if (c == 'k') { @@ -1658,7 +1669,7 @@ size_t http_parser_execute (http_parser *parser, break; case h_transfer_encoding_chunked: - if (ch != ' ') h_state = h_general; + if (ch != ' ') h_state = h_matching_transfer_encoding_token; break; case h_connection_keep_alive: @@ -1687,10 +1698,10 @@ size_t http_parser_execute (http_parser *parser, } parser->header_state = h_state; - COUNT_HEADER_SIZE(p - start); - if (p == data + len) --p; + + COUNT_HEADER_SIZE(p - start); break; } @@ -1708,6 +1719,10 @@ size_t http_parser_execute (http_parser *parser, case s_header_value_lws: { if (ch == ' ' || ch == '\t') { + if (parser->header_state == h_content_length_num) { + /* treat obsolete line folding as space */ + parser->header_state = h_content_length_ws; + } UPDATE_STATE(s_header_value_start); REEXECUTE(); } @@ -1760,6 +1775,11 @@ size_t http_parser_execute (http_parser *parser, case h_transfer_encoding_chunked: parser->flags |= F_CHUNKED; break; + case h_content_length: + /* do not allow empty content length */ + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + break; default: break; } @@ -1783,21 +1803,38 @@ size_t http_parser_execute (http_parser *parser, REEXECUTE(); } - /* Cannot use chunked encoding and a content-length header together - per the HTTP specification. */ - if ((parser->flags & F_CHUNKED) && + /* Cannot use transfer-encoding and a content-length header together + per the HTTP specification. (RFC 7230 Section 3.3.3) */ + if ((parser->uses_transfer_encoding == 1) && (parser->flags & F_CONTENTLENGTH)) { - SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); - goto error; + /* Allow it for lenient parsing as long as `Transfer-Encoding` is + * not `chunked` or allow_length_with_encoding is set + */ + if (parser->flags & F_CHUNKED) { + if (!allow_chunked_length) { + SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); + goto error; + } + } else if (!lenient) { + SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); + goto error; + } } UPDATE_STATE(s_headers_done); /* Set this here so that on_headers_complete() callbacks can see it */ - parser->upgrade = - ((parser->flags & (F_UPGRADE | F_CONNECTION_UPGRADE)) == - (F_UPGRADE | F_CONNECTION_UPGRADE) || - parser->method == HTTP_CONNECT); + if ((parser->flags & F_UPGRADE) && + (parser->flags & F_CONNECTION_UPGRADE)) { + /* For responses, "Upgrade: foo" and "Connection: upgrade" are + * mandatory only when it is a 101 Switching Protocols response, + * otherwise it is purely informational, to announce support. + */ + parser->upgrade = + (parser->type == HTTP_REQUEST || parser->status_code == 101); + } else { + parser->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 @@ -1816,6 +1853,7 @@ size_t http_parser_execute (http_parser *parser, case 2: parser->upgrade = 1; + /* fall through */ case 1: parser->flags |= F_SKIPBODY; break; @@ -1839,6 +1877,7 @@ size_t http_parser_execute (http_parser *parser, STRICT_CHECK(ch != LF); parser->nread = 0; + nread = 0; hasBody = parser->flags & F_CHUNKED || (parser->content_length > 0 && parser->content_length != ULLONG_MAX); @@ -1854,8 +1893,31 @@ size_t http_parser_execute (http_parser *parser, UPDATE_STATE(NEW_MESSAGE()); CALLBACK_NOTIFY(message_complete); } else if (parser->flags & F_CHUNKED) { - /* chunked encoding - ignore Content-Length header */ + /* chunked encoding - ignore Content-Length header, + * prepare for a chunk */ UPDATE_STATE(s_chunk_size_start); + } else if (parser->uses_transfer_encoding == 1) { + if (parser->type == HTTP_REQUEST && !lenient) { + /* RFC 7230 3.3.3 */ + + /* If a Transfer-Encoding header field + * is present in a request and the chunked transfer coding is not + * the final encoding, the message body length cannot be determined + * reliably; the server MUST respond with the 400 (Bad Request) + * status code and then close the connection. + */ + SET_ERRNO(HPE_INVALID_TRANSFER_ENCODING); + RETURN(p - data); /* Error */ + } else { + /* RFC 7230 3.3.3 */ + + /* If a Transfer-Encoding header field is present in a response and + * the chunked transfer coding is not the final encoding, the + * message body length is determined by reading the connection until + * it is closed by the server. + */ + UPDATE_STATE(s_body_identity_eof); + } } else { if (parser->content_length == 0) { /* Content-Length header given but zero: Content-Length: 0\r\n */ @@ -1933,7 +1995,7 @@ size_t http_parser_execute (http_parser *parser, case s_chunk_size_start: { - assert(parser->nread == 1); + assert(nread == 1); assert(parser->flags & F_CHUNKED); unhex_val = unhex[(unsigned char)ch]; @@ -2001,6 +2063,7 @@ size_t http_parser_execute (http_parser *parser, STRICT_CHECK(ch != LF); parser->nread = 0; + nread = 0; if (parser->content_length == 0) { parser->flags |= F_TRAILING; @@ -2047,6 +2110,7 @@ size_t http_parser_execute (http_parser *parser, assert(parser->flags & F_CHUNKED); STRICT_CHECK(ch != LF); parser->nread = 0; + nread = 0; UPDATE_STATE(s_chunk_size_start); CALLBACK_NOTIFY(chunk_complete); break; @@ -2058,7 +2122,7 @@ size_t http_parser_execute (http_parser *parser, } } - /* Run callbacks for any marks that we have leftover after we ran our of + /* Run callbacks for any marks that we have leftover after we ran out of * bytes. There should be at most one of these set, so it's OK to invoke * them in series (unset marks will not result in callbacks). * @@ -2107,6 +2171,12 @@ http_message_needs_eof (const http_parser *parser) return 0; } + /* RFC 7230 3.3.3, see `s_headers_almost_done` */ + if ((parser->uses_transfer_encoding == 1) && + (parser->flags & F_CHUNKED) == 0) { + return 1; + } + if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { return 0; } @@ -2140,6 +2210,16 @@ http_method_str (enum http_method m) return ELEM_AT(method_strings, m, ""); } +const char * +http_status_str (enum http_status s) +{ + switch (s) { +#define XX(num, name, string) case HTTP_STATUS_##name: return #string; + HTTP_STATUS_MAP(XX) +#undef XX + default: return ""; + } +} void http_parser_init (http_parser *parser, enum http_parser_type t) @@ -2200,7 +2280,7 @@ http_parse_host_char(enum http_host_state s, const char ch) { return s_http_host; } - /* FALLTHROUGH */ + /* fall through */ case s_http_host_v6_end: if (ch == ':') { return s_http_host_port_start; @@ -2213,7 +2293,7 @@ http_parse_host_char(enum http_host_state s, const char ch) { return s_http_host_v6_end; } - /* FALLTHROUGH */ + /* fall through */ case s_http_host_v6_start: if (IS_HEX(ch) || ch == ':' || ch == '.') { return s_http_host_v6; @@ -2229,7 +2309,7 @@ http_parse_host_char(enum http_host_state s, const char ch) { return s_http_host_v6_end; } - /* FALLTHROUGH */ + /* fall through */ case s_http_host_v6_zone_start: /* RFC 6874 Zone ID consists of 1*( unreserved / pct-encoded) */ if (IS_ALPHANUM(ch) || ch == '%' || ch == '.' || ch == '-' || ch == '_' || @@ -2275,14 +2355,14 @@ http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { switch(new_s) { case s_http_host: if (s != s_http_host) { - u->field_data[UF_HOST].off = p - buf; + u->field_data[UF_HOST].off = (uint16_t)(p - buf); } u->field_data[UF_HOST].len++; break; case s_http_host_v6: if (s != s_http_host_v6) { - u->field_data[UF_HOST].off = p - buf; + u->field_data[UF_HOST].off = (uint16_t)(p - buf); } u->field_data[UF_HOST].len++; break; @@ -2294,7 +2374,7 @@ http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { case s_http_host_port: if (s != s_http_host_port) { - u->field_data[UF_PORT].off = p - buf; + u->field_data[UF_PORT].off = (uint16_t)(p - buf); u->field_data[UF_PORT].len = 0; u->field_set |= (1 << UF_PORT); } @@ -2303,7 +2383,7 @@ http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { case s_http_userinfo: if (s != s_http_userinfo) { - u->field_data[UF_USERINFO].off = p - buf ; + u->field_data[UF_USERINFO].off = (uint16_t)(p - buf); u->field_data[UF_USERINFO].len = 0; u->field_set |= (1 << UF_USERINFO); } @@ -2348,6 +2428,10 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect, enum http_parser_url_fields uf, old_uf; int found_at = 0; + if (buflen == 0) { + return 1; + } + u->port = u->field_set = 0; s = is_connect ? s_req_server_start : s_req_spaces_before_url; old_uf = UF_MAX; @@ -2375,7 +2459,7 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect, case s_req_server_with_at: found_at = 1; - /* FALLTROUGH */ + /* fall through */ case s_req_server: uf = UF_HOST; break; @@ -2403,7 +2487,7 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect, continue; } - u->field_data[uf].off = p - buf; + u->field_data[uf].off = (uint16_t)(p - buf); u->field_data[uf].len = 1; u->field_set |= (1 << uf); @@ -2429,12 +2513,27 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect, } if (u->field_set & (1 << UF_PORT)) { - /* Don't bother with endp; we've already validated the string */ - unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); - - /* Ports have a max value of 2^16 */ - if (v > 0xffff) { - return 1; + uint16_t off; + uint16_t len; + const char* p; + const char* end; + unsigned long v; + + off = u->field_data[UF_PORT].off; + len = u->field_data[UF_PORT].len; + end = buf + off + len; + + /* NOTE: The characters are already validated and are in the [0-9] range */ + assert((size_t) (off + len) <= buflen && "Port number overflow"); + v = 0; + for (p = buf + off; p < end; p++) { + v *= 10; + v += *p - '0'; + + /* Ports have a max value of 2^16 */ + if (v > 0xffff) { + return 1; + } } u->port = (uint16_t) v; @@ -2451,6 +2550,7 @@ http_parser_pause(http_parser *parser, int paused) { */ if (HTTP_PARSER_ERRNO(parser) == HPE_OK || HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { + uint32_t nread = parser->nread; /* used by the SET_ERRNO macro */ SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); } else { assert(0 && "Attempting to pause parser in error state"); @@ -2468,3 +2568,8 @@ http_parser_version(void) { HTTP_PARSER_VERSION_MINOR * 0x00100 | HTTP_PARSER_VERSION_PATCH * 0x00001; } + +void +http_parser_set_max_header_size(uint32_t size) { + max_header_size = size; +} \ No newline at end of file diff --git a/src/lib/http-parser/http_parser.h b/src/lib/http-parser/http_parser.h index 45c72a07..2de7edcf 100644 --- a/src/lib/http-parser/http_parser.h +++ b/src/lib/http-parser/http_parser.h @@ -26,14 +26,13 @@ extern "C" { /* Also update SONAME in the Makefile whenever you change these. */ #define HTTP_PARSER_VERSION_MAJOR 2 -#define HTTP_PARSER_VERSION_MINOR 7 -#define HTTP_PARSER_VERSION_PATCH 1 +#define HTTP_PARSER_VERSION_MINOR 9 +#define HTTP_PARSER_VERSION_PATCH 4 -#include +#include #if defined(_WIN32) && !defined(__MINGW32__) && \ (!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__) #include -#include typedef __int8 int8_t; typedef unsigned __int8 uint8_t; typedef __int16 int16_t; @@ -42,6 +41,8 @@ typedef __int32 int32_t; typedef unsigned __int32 uint32_t; typedef __int64 int64_t; typedef unsigned __int64 uint64_t; +#elif (defined(__sun) || defined(__sun__)) && defined(__SunOS_5_9) +#include #else #include #endif @@ -202,6 +203,8 @@ enum http_status /* RFC-2068, section 19.6.1.2 */ \ XX(31, LINK, LINK) \ XX(32, UNLINK, UNLINK) \ + /* icecast */ \ + XX(33, SOURCE, SOURCE) \ enum http_method { @@ -274,7 +277,9 @@ enum flags XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ XX(STRICT, "strict mode assertion failed") \ XX(PAUSED, "parser is paused") \ - XX(UNKNOWN, "an unknown error occurred") + XX(UNKNOWN, "an unknown error occurred") \ + XX(INVALID_TRANSFER_ENCODING, \ + "request has invalid transfer-encoding") \ /* Define HPE_* values for each errno value above */ @@ -292,14 +297,20 @@ enum http_errno { struct http_parser { /** PRIVATE **/ unsigned int type : 2; /* enum http_parser_type */ - unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */ + unsigned int flags : 8; /* F_* values from 'flags' enum; semi-public */ unsigned int state : 7; /* enum state from http_parser.c */ unsigned int header_state : 7; /* enum header_state from http_parser.c */ - unsigned int index : 7; /* index into current matcher */ + unsigned int index : 5; /* index into current matcher */ + unsigned int uses_transfer_encoding : 1; /* Transfer-Encoding header is present */ + unsigned int allow_chunked_length : 1; /* Allow headers with both + * `Content-Length` and + * `Transfer-Encoding: chunked` set */ unsigned int lenient_http_headers : 1; uint32_t nread; /* # bytes read in various scenarios */ - uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ + uint64_t content_length; /* # bytes in body. `(uint64_t) -1` (all bits one) + * if no Content-Length header. + */ /** READ-ONLY **/ unsigned short http_major; @@ -406,6 +417,9 @@ int http_should_keep_alive(const http_parser *parser); /* Returns a string version of the HTTP method. */ const char *http_method_str(enum http_method m); +/* Returns a string version of the HTTP status code. */ +const char *http_status_str(enum http_status s); + /* Return a string name of the given error */ const char *http_errno_name(enum http_errno err); @@ -426,7 +440,10 @@ void http_parser_pause(http_parser *parser, int paused); /* Checks if this is the final chunk of the body. */ int http_body_is_final(const http_parser *parser); +/* Change the maximum header size provided at compile time. */ +void http_parser_set_max_header_size(uint32_t size); + #ifdef __cplusplus } #endif -#endif +#endif \ No newline at end of file