diff --git a/Sources/CHTTPParser/http_parser.c b/Sources/CHTTPParser/http_parser.c index 8eb847e..fbd10ab 100644 --- a/Sources/CHTTPParser/http_parser.c +++ b/Sources/CHTTPParser/http_parser.c @@ -25,7 +25,6 @@ #include #include #include -#include #include #include @@ -53,6 +52,7 @@ #define SET_ERRNO(e) \ do { \ + parser->nread = nread; \ parser->http_errno = (e); \ } while(0) @@ -60,6 +60,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); @@ -153,8 +154,8 @@ do { \ */ #define COUNT_HEADER_SIZE(V) \ do { \ - parser->nread += (V); \ - if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) { \ + nread += (V); \ + if (UNLIKELY(nread > (HTTP_MAX_HEADER_SIZE))) { \ SET_ERRNO(HPE_HEADER_OVERFLOW); \ goto error; \ } \ @@ -196,7 +197,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 */ @@ -374,6 +375,8 @@ enum header_states , h_connection , h_content_length + , h_content_length_num + , h_content_length_ws , h_transfer_encoding , h_upgrade @@ -421,14 +424,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 +545,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 +649,7 @@ 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; + uint32_t nread = parser->nread; /* We're in an error state. Don't bother doing anything. */ if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { @@ -980,7 +984,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: @@ -1023,6 +1027,7 @@ size_t http_parser_execute (http_parser *parser, XX(MKCOL, 2, 'A', MKACTIVITY) XX(MKCOL, 3, 'A', MKCALENDAR) XX(SUBSCRIBE, 1, 'E', SEARCH) + XX(SUBSCRIBE, 1, '0', SOURCE) XX(REPORT, 2, 'B', REBIND) XX(POST, 1, 'R', PROPFIND) XX(PROPFIND, 4, 'P', PROPPATCH) @@ -1306,8 +1311,14 @@ size_t http_parser_execute (http_parser *parser, break; switch (parser->header_state) { - case h_general: + case h_general: { + size_t limit = data + len - p; + limit = MIN(limit, HTTP_MAX_HEADER_SIZE); + while (p+1 < data + limit && TOKEN(p[1])) { + p++; + } break; + } case h_C: parser->index++; @@ -1406,14 +1417,14 @@ size_t http_parser_execute (http_parser *parser, break; } } - - 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 +1448,7 @@ size_t http_parser_execute (http_parser *parser, break; } - /* FALLTHROUGH */ + /* fall through */ case s_header_value_start: { @@ -1473,9 +1484,9 @@ size_t http_parser_execute (http_parser *parser, SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH); goto error; } - parser->flags |= F_CONTENTLENGTH; parser->content_length = ch - '0'; + parser->header_state = h_content_length_num; break; case h_connection: @@ -1563,10 +1574,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); @@ -1588,6 +1607,11 @@ size_t http_parser_execute (http_parser *parser, parser->content_length = t; 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_chunked: @@ -1687,11 +1711,10 @@ size_t http_parser_execute (http_parser *parser, } parser->header_state = h_state; - COUNT_HEADER_SIZE(p - start); - if (p == data + len) --p; - break; + COUNT_HEADER_SIZE(p - start); + break; } case s_header_almost_done: @@ -1839,6 +1862,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); @@ -1933,7 +1957,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]; @@ -1952,7 +1976,6 @@ size_t http_parser_execute (http_parser *parser, uint64_t t; assert(parser->flags & F_CHUNKED); - if (ch == CR) { UPDATE_STATE(s_chunk_size_almost_done); break; @@ -1999,8 +2022,8 @@ size_t http_parser_execute (http_parser *parser, { assert(parser->flags & F_CHUNKED); STRICT_CHECK(ch != LF); - parser->nread = 0; + nread = 0; if (parser->content_length == 0) { parser->flags |= F_TRAILING; @@ -2047,6 +2070,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; @@ -2140,6 +2164,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 +2234,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 +2247,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 +2263,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 == '_' || @@ -2347,6 +2381,9 @@ http_parser_parse_url(const char *buf, size_t buflen, int is_connect, const char *p; 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; @@ -2375,7 +2412,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; @@ -2429,14 +2466,28 @@ 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(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 +2502,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"); diff --git a/Sources/CHTTPParser/include/http_parser.h b/Sources/CHTTPParser/include/http_parser.h index 612b16a..5ef4703 100644 --- a/Sources/CHTTPParser/include/http_parser.h +++ b/Sources/CHTTPParser/include/http_parser.h @@ -26,7 +26,7 @@ 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_MINOR 8 #define HTTP_PARSER_VERSION_PATCH 1 #include @@ -90,6 +90,75 @@ typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); typedef int (*http_cb) (http_parser*); + /* Status Codes */ +#define HTTP_STATUS_MAP(XX) \ +XX(100, CONTINUE, Continue) \ +XX(101, SWITCHING_PROTOCOLS, Switching Protocols) \ +XX(102, PROCESSING, Processing) \ +XX(200, OK, OK) \ +XX(201, CREATED, Created) \ +XX(202, ACCEPTED, Accepted) \ +XX(203, NON_AUTHORITATIVE_INFORMATION, Non-Authoritative Information) \ +XX(204, NO_CONTENT, No Content) \ +XX(205, RESET_CONTENT, Reset Content) \ +XX(206, PARTIAL_CONTENT, Partial Content) \ +XX(207, MULTI_STATUS, Multi-Status) \ +XX(208, ALREADY_REPORTED, Already Reported) \ +XX(226, IM_USED, IM Used) \ +XX(300, MULTIPLE_CHOICES, Multiple Choices) \ +XX(301, MOVED_PERMANENTLY, Moved Permanently) \ +XX(302, FOUND, Found) \ +XX(303, SEE_OTHER, See Other) \ +XX(304, NOT_MODIFIED, Not Modified) \ +XX(305, USE_PROXY, Use Proxy) \ +XX(307, TEMPORARY_REDIRECT, Temporary Redirect) \ +XX(308, PERMANENT_REDIRECT, Permanent Redirect) \ +XX(400, BAD_REQUEST, Bad Request) \ +XX(401, UNAUTHORIZED, Unauthorized) \ +XX(402, PAYMENT_REQUIRED, Payment Required) \ +XX(403, FORBIDDEN, Forbidden) \ +XX(404, NOT_FOUND, Not Found) \ +XX(405, METHOD_NOT_ALLOWED, Method Not Allowed) \ +XX(406, NOT_ACCEPTABLE, Not Acceptable) \ +XX(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required) \ +XX(408, REQUEST_TIMEOUT, Request Timeout) \ +XX(409, CONFLICT, Conflict) \ +XX(410, GONE, Gone) \ +XX(411, LENGTH_REQUIRED, Length Required) \ +XX(412, PRECONDITION_FAILED, Precondition Failed) \ +XX(413, PAYLOAD_TOO_LARGE, Payload Too Large) \ +XX(414, URI_TOO_LONG, URI Too Long) \ +XX(415, UNSUPPORTED_MEDIA_TYPE, Unsupported Media Type) \ +XX(416, RANGE_NOT_SATISFIABLE, Range Not Satisfiable) \ +XX(417, EXPECTATION_FAILED, Expectation Failed) \ +XX(421, MISDIRECTED_REQUEST, Misdirected Request) \ +XX(422, UNPROCESSABLE_ENTITY, Unprocessable Entity) \ +XX(423, LOCKED, Locked) \ +XX(424, FAILED_DEPENDENCY, Failed Dependency) \ +XX(426, UPGRADE_REQUIRED, Upgrade Required) \ +XX(428, PRECONDITION_REQUIRED, Precondition Required) \ +XX(429, TOO_MANY_REQUESTS, Too Many Requests) \ +XX(431, REQUEST_HEADER_FIELDS_TOO_LARGE, Request Header Fields Too Large) \ +XX(451, UNAVAILABLE_FOR_LEGAL_REASONS, Unavailable For Legal Reasons) \ +XX(500, INTERNAL_SERVER_ERROR, Internal Server Error) \ +XX(501, NOT_IMPLEMENTED, Not Implemented) \ +XX(502, BAD_GATEWAY, Bad Gateway) \ +XX(503, SERVICE_UNAVAILABLE, Service Unavailable) \ +XX(504, GATEWAY_TIMEOUT, Gateway Timeout) \ +XX(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported) \ +XX(506, VARIANT_ALSO_NEGOTIATES, Variant Also Negotiates) \ +XX(507, INSUFFICIENT_STORAGE, Insufficient Storage) \ +XX(508, LOOP_DETECTED, Loop Detected) \ +XX(510, NOT_EXTENDED, Not Extended) \ +XX(511, NETWORK_AUTHENTICATION_REQUIRED, Network Authentication Required) \ + +enum http_status + { +#define XX(num, name, string) HTTP_STATUS_##name = num, + HTTP_STATUS_MAP(XX) +#undef XX + }; + /* Request Methods */ #define HTTP_METHOD_MAP(XX) \ XX(0, DELETE, DELETE) \ @@ -132,6 +201,8 @@ typedef int (*http_cb) (http_parser*); /* RFC-2068, section 19.6.1.2 */ \ XX(31, LINK, LINK) \ XX(32, UNLINK, UNLINK) \ +/*icecast*/ \ + XX(33, SOURCE, SOURCE) \ enum http_method { @@ -336,6 +407,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);