diff options
Diffstat (limited to 'deps/http_parser/http_parser.c')
-rw-r--r-- | deps/http_parser/http_parser.c | 334 |
1 files changed, 239 insertions, 95 deletions
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; } |