Use http-parser 0d0a24e19eb5ba232d2ea8859aba2a7cc6c42bc4

This commit is contained in:
Tatsuhiro Tsujikawa 2019-01-17 23:08:27 +09:00
parent 439dbce679
commit ff87a54202
8 changed files with 717 additions and 314 deletions

View File

@ -1,4 +0,0 @@
Contributors must agree to the Contributor License Agreement before patches
can be accepted.
http://spreadsheets2.google.com/viewform?hl=en&formkey=dDJXOGUwbzlYaWM4cHN1MERwQS1CSnc6MQ

View File

@ -1,8 +1,4 @@
http_parser.c is 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

View File

@ -72,9 +72,9 @@ if (parser->upgrade) {
}
```
HTTP needs to know where the end of the stream is. For example, sometimes
`http_parser` needs to know where the end of the stream is. For example, sometimes
servers send responses without Content-Length and expect the client to
consume input (for the body) until EOF. To tell http_parser about EOF, give
consume input (for the body) until EOF. To tell `http_parser` about EOF, give
`0` as the fourth parameter to `http_parser_execute()`. Callbacks and errors
can still be encountered during an EOF, so one must still be prepared
to receive them.
@ -93,7 +93,7 @@ the on_body callback.
The Special Problem of Upgrade
------------------------------
HTTP supports upgrading the connection to a different protocol. An
`http_parser` supports upgrading the connection to a different protocol. An
increasingly common example of this is the WebSocket protocol which sends
a request like
@ -144,7 +144,7 @@ parse a request, and then give a response over that socket. By instantiation
of a thread-local struct containing relevant data (e.g. accepted socket,
allocated memory for callbacks to write into, etc), a parser's callbacks are
able to communicate data between the scope of the thread and the scope of the
callback in a threadsafe manner. This allows http-parser to be used in
callback in a threadsafe manner. This allows `http_parser` to be used in
multi-threaded contexts.
Example:
@ -202,7 +202,7 @@ void http_parser_thread(socket_t sock) {
In case you parse HTTP message in chunks (i.e. `read()` request line
from socket, parse, read half headers, parse, etc) your data callbacks
may be called more than once. Http-parser guarantees that data pointer is only
may be called more than once. `http_parser` guarantees that data pointer is only
valid for the lifetime of callback. You can also `read()` into a heap allocated
buffer to avoid copying memory around if this fits your application.

View File

@ -20,10 +20,14 @@
*/
#include "http_parser.h"
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
/* 8 gb */
static const int64_t kBytes = 8LL << 30;
static const char data[] =
"POST /joyent/http-parser HTTP/1.1\r\n"
"Host: github.com\r\n"
@ -38,7 +42,7 @@ static const char data[] =
"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";
"Cache-Control: max-age=0\r\n\r\nb\r\nhello world\r\n0\r\n";
static const size_t data_len = sizeof(data) - 1;
static int on_info(http_parser* p) {
@ -67,13 +71,13 @@ int bench(int iter_count, int silent) {
int err;
struct timeval start;
struct timeval end;
float rps;
if (!silent) {
err = gettimeofday(&start, NULL);
assert(err == 0);
}
fprintf(stderr, "req_len=%d\n", (int) data_len);
for (i = 0; i < iter_count; i++) {
size_t parsed;
http_parser_init(&parser, HTTP_REQUEST);
@ -83,17 +87,27 @@ int bench(int iter_count, int silent) {
}
if (!silent) {
double elapsed;
double bw;
double total;
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);
elapsed = (double) (end.tv_sec - start.tv_sec) +
(end.tv_usec - start.tv_usec) * 1e-6f;
total = (double) iter_count * data_len;
bw = (double) total / elapsed;
fprintf(stdout, "%.2f mb | %.2f mb/s | %.2f req/sec | %.2f s\n",
(double) total / (1024 * 1024),
bw / (1024 * 1024),
(double) iter_count / elapsed,
elapsed);
rps = (float) iter_count / rps;
fprintf(stdout, "%f req/sec\n", rps);
fflush(stdout);
}
@ -101,11 +115,14 @@ int bench(int iter_count, int silent) {
}
int main(int argc, char** argv) {
int64_t iterations;
iterations = kBytes / (int64_t) data_len;
if (argc == 2 && strcmp(argv[1], "infinite") == 0) {
for (;;)
bench(5000000, 1);
bench(iterations, 1);
return 0;
} else {
return bench(5000000, 0);
return bench(iterations, 0);
}
}

View File

@ -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

View File

@ -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 <assert.h>
#include <stddef.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
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,6 +376,8 @@ enum header_states
, h_connection
, h_content_length
, h_content_length_num
, h_content_length_ws
, h_transfer_encoding
, h_upgrade
@ -421,14 +425,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 +546,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 +650,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) {
@ -757,21 +762,16 @@ reexecute:
case s_start_res:
{
if (ch == CR || ch == LF)
break;
parser->flags = 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 +795,48 @@ reexecute:
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 +863,9 @@ reexecute:
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 +887,13 @@ reexecute:
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;
}
@ -980,7 +946,7 @@ reexecute:
/* 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 +973,7 @@ reexecute:
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 +982,28 @@ reexecute:
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 +1088,17 @@ reexecute:
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 +1120,53 @@ reexecute:
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 +1178,8 @@ reexecute:
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 +1256,14 @@ reexecute:
break;
switch (parser->header_state) {
case h_general:
case h_general: {
size_t limit = data + len - p;
limit = MIN(limit, max_header_size);
while (p+1 < data + limit && TOKEN(p[1])) {
p++;
}
break;
}
case h_C:
parser->index++;
@ -1407,13 +1363,14 @@ reexecute:
}
}
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 +1394,7 @@ reexecute:
break;
}
/* FALLTHROUGH */
/* fall through */
case s_header_value_start:
{
@ -1476,6 +1433,12 @@ reexecute:
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:
@ -1538,7 +1501,7 @@ reexecute:
const char* p_lf;
size_t limit = data + len - p;
limit = MIN(limit, HTTP_MAX_HEADER_SIZE);
limit = MIN(limit, max_header_size);
p_cr = (const char*) memchr(p, CR, limit);
p_lf = (const char*) memchr(p, LF, limit);
@ -1553,7 +1516,6 @@ reexecute:
p = data + len;
}
--p;
break;
}
@ -1563,10 +1525,18 @@ reexecute:
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,6 +1559,12 @@ reexecute:
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:
parser->index++;
@ -1687,10 +1663,10 @@ reexecute:
}
parser->header_state = h_state;
COUNT_HEADER_SIZE(p - start);
if (p == data + len)
--p;
COUNT_HEADER_SIZE(p - start);
break;
}
@ -1708,6 +1684,10 @@ reexecute:
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 +1740,11 @@ reexecute:
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;
}
@ -1794,10 +1779,17 @@ reexecute:
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 +1808,7 @@ reexecute:
case 2:
parser->upgrade = 1;
/* fall through */
case 1:
parser->flags |= F_SKIPBODY;
break;
@ -1839,6 +1832,7 @@ reexecute:
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 +1927,7 @@ reexecute:
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 +1995,7 @@ reexecute:
STRICT_CHECK(ch != LF);
parser->nread = 0;
nread = 0;
if (parser->content_length == 0) {
parser->flags |= F_TRAILING;
@ -2047,6 +2042,7 @@ reexecute:
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 +2054,7 @@ reexecute:
}
}
/* 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).
*
@ -2140,6 +2136,16 @@ http_method_str (enum http_method m)
return ELEM_AT(method_strings, m, "<unknown>");
}
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 "<unknown>";
}
}
void
http_parser_init (http_parser *parser, enum http_parser_type t)
@ -2200,7 +2206,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 +2219,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 +2235,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 +2281,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 +2300,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 +2309,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 +2354,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 +2385,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 +2413,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 +2439,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);
uint16_t off;
uint16_t len;
const char* p;
const char* end;
unsigned long v;
/* Ports have a max value of 2^16 */
if (v > 0xffff) {
return 1;
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 +2476,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 +2494,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;
}

View File

@ -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 0
#include <sys/types.h>
#include <stddef.h>
#if defined(_WIN32) && !defined(__MINGW32__) && \
(!defined(_MSC_VER) || _MSC_VER<1600) && !defined(__WINE__)
#include <BaseTsd.h>
#include <stddef.h>
typedef __int8 int8_t;
typedef unsigned __int8 uint8_t;
typedef __int16 int16_t;
@ -90,6 +89,76 @@ 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);
@ -356,6 +430,9 @@ 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

View File

@ -27,9 +27,7 @@
#include <stdarg.h>
#if defined(__APPLE__)
# undef strlcat
# undef strlncpy
# undef strlcpy
#endif /* defined(__APPLE__) */
#undef TRUE
@ -43,7 +41,9 @@
#define MIN(a,b) ((a) < (b) ? (a) : (b))
static http_parser *parser;
#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x))
static http_parser parser;
struct message {
const char *name; // for debugging purposes
@ -78,6 +78,7 @@ struct message {
int message_begin_cb_called;
int headers_complete_cb_called;
int message_complete_cb_called;
int status_cb_called;
int message_complete_on_eof;
int body_is_final;
};
@ -152,10 +153,10 @@ const struct message requests[] =
,.body= ""
}
#define DUMBFUCK 2
, {.name= "dumbfuck"
#define DUMBLUCK 2
, {.name= "dumbluck"
,.type= HTTP_REQUEST
,.raw= "GET /dumbfuck HTTP/1.1\r\n"
,.raw= "GET /dumbluck HTTP/1.1\r\n"
"aaaaaaaaaaaaa:++++++++++\r\n"
"\r\n"
,.should_keep_alive= TRUE
@ -165,8 +166,8 @@ const struct message requests[] =
,.method= HTTP_GET
,.query_string= ""
,.fragment= ""
,.request_path= "/dumbfuck"
,.request_url= "/dumbfuck"
,.request_path= "/dumbluck"
,.request_url= "/dumbluck"
,.num_headers= 1
,.headers=
{ { "aaaaaaaaaaaaa", "++++++++++" }
@ -370,13 +371,13 @@ const struct message requests[] =
,.chunk_lengths= { 5, 6 }
}
#define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11
, {.name= "with bullshit after the length"
#define CHUNKED_W_NONSENSE_AFTER_LENGTH 11
, {.name= "with nonsense after the length"
,.type= HTTP_REQUEST
,.raw= "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n"
,.raw= "POST /chunked_w_nonsense_after_length HTTP/1.1\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n"
"5; ilovew3;whattheluck=aretheseparametersfor\r\nhello\r\n"
"6; blahblah; blah\r\n world\r\n"
"0\r\n"
"\r\n"
@ -387,8 +388,8 @@ const struct message requests[] =
,.method= HTTP_POST
,.query_string= ""
,.fragment= ""
,.request_path= "/chunked_w_bullshit_after_length"
,.request_url= "/chunked_w_bullshit_after_length"
,.request_path= "/chunked_w_nonsense_after_length"
,.request_url= "/chunked_w_nonsense_after_length"
,.num_headers= 1
,.headers=
{ { "Transfer-Encoding", "chunked" }
@ -1131,7 +1132,7 @@ const struct message requests[] =
}
#define UNLINK_REQUEST 41
, {.name = "link request"
, {.name = "unlink request"
,.type= HTTP_REQUEST
,.raw= "UNLINK /images/my_dog.jpg HTTP/1.1\r\n"
"Host: example.com\r\n"
@ -1153,7 +1154,45 @@ const struct message requests[] =
,.body= ""
}
, {.name= NULL } /* sentinel */
#define SOURCE_REQUEST 42
, {.name = "source request"
,.type= HTTP_REQUEST
,.raw= "SOURCE /music/sweet/music HTTP/1.1\r\n"
"Host: example.com\r\n"
"\r\n"
,.should_keep_alive= TRUE
,.message_complete_on_eof= FALSE
,.http_major= 1
,.http_minor= 1
,.method= HTTP_SOURCE
,.request_path= "/music/sweet/music"
,.request_url= "/music/sweet/music"
,.query_string= ""
,.fragment= ""
,.num_headers= 1
,.headers= { { "Host", "example.com" } }
,.body= ""
}
#define SOURCE_ICE_REQUEST 42
, {.name = "source request"
,.type= HTTP_REQUEST
,.raw= "SOURCE /music/sweet/music ICE/1.0\r\n"
"Host: example.com\r\n"
"\r\n"
,.should_keep_alive= FALSE
,.message_complete_on_eof= FALSE
,.http_major= 1
,.http_minor= 0
,.method= HTTP_SOURCE
,.request_path= "/music/sweet/music"
,.request_url= "/music/sweet/music"
,.query_string= ""
,.fragment= ""
,.num_headers= 1
,.headers= { { "Host", "example.com" } }
,.body= ""
}
};
/* * R E S P O N S E S * */
@ -1771,7 +1810,166 @@ const struct message responses[] =
,.chunk_lengths= { 2 }
}
, {.name= NULL } /* sentinel */
#define HTTP_101_RESPONSE_WITH_UPGRADE_HEADER 22
, {.name= "HTTP 101 response with Upgrade header"
,.type= HTTP_RESPONSE
,.raw= "HTTP/1.1 101 Switching Protocols\r\n"
"Connection: upgrade\r\n"
"Upgrade: h2c\r\n"
"\r\n"
"proto"
,.should_keep_alive= TRUE
,.message_complete_on_eof= FALSE
,.http_major= 1
,.http_minor= 1
,.status_code= 101
,.response_status= "Switching Protocols"
,.upgrade= "proto"
,.num_headers= 2
,.headers=
{ { "Connection", "upgrade" }
, { "Upgrade", "h2c" }
}
}
#define HTTP_101_RESPONSE_WITH_UPGRADE_HEADER_AND_CONTENT_LENGTH 23
, {.name= "HTTP 101 response with Upgrade and Content-Length header"
,.type= HTTP_RESPONSE
,.raw= "HTTP/1.1 101 Switching Protocols\r\n"
"Connection: upgrade\r\n"
"Upgrade: h2c\r\n"
"Content-Length: 4\r\n"
"\r\n"
"body"
"proto"
,.should_keep_alive= TRUE
,.message_complete_on_eof= FALSE
,.http_major= 1
,.http_minor= 1
,.status_code= 101
,.response_status= "Switching Protocols"
,.body= "body"
,.upgrade= "proto"
,.num_headers= 3
,.headers=
{ { "Connection", "upgrade" }
, { "Upgrade", "h2c" }
, { "Content-Length", "4" }
}
}
#define HTTP_101_RESPONSE_WITH_UPGRADE_HEADER_AND_TRANSFER_ENCODING 24
, {.name= "HTTP 101 response with Upgrade and Transfer-Encoding header"
,.type= HTTP_RESPONSE
,.raw= "HTTP/1.1 101 Switching Protocols\r\n"
"Connection: upgrade\r\n"
"Upgrade: h2c\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"2\r\n"
"bo\r\n"
"2\r\n"
"dy\r\n"
"0\r\n"
"\r\n"
"proto"
,.should_keep_alive= TRUE
,.message_complete_on_eof= FALSE
,.http_major= 1
,.http_minor= 1
,.status_code= 101
,.response_status= "Switching Protocols"
,.body= "body"
,.upgrade= "proto"
,.num_headers= 3
,.headers=
{ { "Connection", "upgrade" }
, { "Upgrade", "h2c" }
, { "Transfer-Encoding", "chunked" }
}
,.num_chunks_complete= 3
,.chunk_lengths= { 2, 2 }
}
#define HTTP_200_RESPONSE_WITH_UPGRADE_HEADER 25
, {.name= "HTTP 200 response with Upgrade header"
,.type= HTTP_RESPONSE
,.raw= "HTTP/1.1 200 OK\r\n"
"Connection: upgrade\r\n"
"Upgrade: h2c\r\n"
"\r\n"
"body"
,.should_keep_alive= FALSE
,.message_complete_on_eof= TRUE
,.http_major= 1
,.http_minor= 1
,.status_code= 200
,.response_status= "OK"
,.body= "body"
,.upgrade= NULL
,.num_headers= 2
,.headers=
{ { "Connection", "upgrade" }
, { "Upgrade", "h2c" }
}
}
#define HTTP_200_RESPONSE_WITH_UPGRADE_HEADER_AND_CONTENT_LENGTH 26
, {.name= "HTTP 200 response with Upgrade and Content-Length header"
,.type= HTTP_RESPONSE
,.raw= "HTTP/1.1 200 OK\r\n"
"Connection: upgrade\r\n"
"Upgrade: h2c\r\n"
"Content-Length: 4\r\n"
"\r\n"
"body"
,.should_keep_alive= TRUE
,.message_complete_on_eof= FALSE
,.http_major= 1
,.http_minor= 1
,.status_code= 200
,.response_status= "OK"
,.num_headers= 3
,.body= "body"
,.upgrade= NULL
,.headers=
{ { "Connection", "upgrade" }
, { "Upgrade", "h2c" }
, { "Content-Length", "4" }
}
}
#define HTTP_200_RESPONSE_WITH_UPGRADE_HEADER_AND_TRANSFER_ENCODING 27
, {.name= "HTTP 200 response with Upgrade and Transfer-Encoding header"
,.type= HTTP_RESPONSE
,.raw= "HTTP/1.1 200 OK\r\n"
"Connection: upgrade\r\n"
"Upgrade: h2c\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"2\r\n"
"bo\r\n"
"2\r\n"
"dy\r\n"
"0\r\n"
"\r\n"
,.should_keep_alive= TRUE
,.message_complete_on_eof= FALSE
,.http_major= 1
,.http_minor= 1
,.status_code= 200
,.response_status= "OK"
,.num_headers= 3
,.body= "body"
,.upgrade= NULL
,.headers=
{ { "Connection", "upgrade" }
, { "Upgrade", "h2c" }
, { "Transfer-Encoding", "chunked" }
}
,.num_chunks_complete= 3
,.chunk_lengths= { 2, 2 }
}
};
/* strnlen() is a POSIX.2008 addition. Can't rely on it being available so
@ -1811,12 +2009,6 @@ strlncat(char *dst, size_t len, const char *src, size_t n)
return slen + dlen;
}
size_t
strlcat(char *dst, const char *src, size_t len)
{
return strlncat(dst, len, src, (size_t) -1);
}
size_t
strlncpy(char *dst, size_t len, const char *src, size_t n)
{
@ -1835,16 +2027,10 @@ strlncpy(char *dst, size_t len, const char *src, size_t n)
return slen;
}
size_t
strlcpy(char *dst, const char *src, size_t len)
{
return strlncpy(dst, len, src, (size_t) -1);
}
int
request_url_cb (http_parser *p, const char *buf, size_t len)
{
assert(p == parser);
assert(p == &parser);
strlncat(messages[num_messages].request_url,
sizeof(messages[num_messages].request_url),
buf,
@ -1855,7 +2041,7 @@ request_url_cb (http_parser *p, const char *buf, size_t len)
int
header_field_cb (http_parser *p, const char *buf, size_t len)
{
assert(p == parser);
assert(p == &parser);
struct message *m = &messages[num_messages];
if (m->last_header_element != FIELD)
@ -1874,7 +2060,7 @@ header_field_cb (http_parser *p, const char *buf, size_t len)
int
header_value_cb (http_parser *p, const char *buf, size_t len)
{
assert(p == parser);
assert(p == &parser);
struct message *m = &messages[num_messages];
strlncat(m->headers[m->num_headers-1][1],
@ -1903,7 +2089,7 @@ check_body_is_final (const http_parser *p)
int
body_cb (http_parser *p, const char *buf, size_t len)
{
assert(p == parser);
assert(p == &parser);
strlncat(messages[num_messages].body,
sizeof(messages[num_messages].body),
buf,
@ -1917,7 +2103,7 @@ body_cb (http_parser *p, const char *buf, size_t len)
int
count_body_cb (http_parser *p, const char *buf, size_t len)
{
assert(p == parser);
assert(p == &parser);
assert(buf);
messages[num_messages].body_size += len;
check_body_is_final(p);
@ -1927,7 +2113,8 @@ count_body_cb (http_parser *p, const char *buf, size_t len)
int
message_begin_cb (http_parser *p)
{
assert(p == parser);
assert(p == &parser);
assert(!messages[num_messages].message_begin_cb_called);
messages[num_messages].message_begin_cb_called = TRUE;
return 0;
}
@ -1935,21 +2122,22 @@ message_begin_cb (http_parser *p)
int
headers_complete_cb (http_parser *p)
{
assert(p == parser);
messages[num_messages].method = parser->method;
messages[num_messages].status_code = parser->status_code;
messages[num_messages].http_major = parser->http_major;
messages[num_messages].http_minor = parser->http_minor;
assert(p == &parser);
messages[num_messages].method = parser.method;
messages[num_messages].status_code = parser.status_code;
messages[num_messages].http_major = parser.http_major;
messages[num_messages].http_minor = parser.http_minor;
messages[num_messages].headers_complete_cb_called = TRUE;
messages[num_messages].should_keep_alive = http_should_keep_alive(parser);
messages[num_messages].should_keep_alive = http_should_keep_alive(&parser);
return 0;
}
int
message_complete_cb (http_parser *p)
{
assert(p == parser);
if (messages[num_messages].should_keep_alive != http_should_keep_alive(parser))
assert(p == &parser);
if (messages[num_messages].should_keep_alive !=
http_should_keep_alive(&parser))
{
fprintf(stderr, "\n\n *** Error http_should_keep_alive() should have same "
"value in both on_message_complete and on_headers_complete "
@ -1980,7 +2168,10 @@ message_complete_cb (http_parser *p)
int
response_status_cb (http_parser *p, const char *buf, size_t len)
{
assert(p == parser);
assert(p == &parser);
messages[num_messages].status_cb_called = TRUE;
strlncat(messages[num_messages].response_status,
sizeof(messages[num_messages].response_status),
buf,
@ -1991,7 +2182,7 @@ response_status_cb (http_parser *p, const char *buf, size_t len)
int
chunk_header_cb (http_parser *p)
{
assert(p == parser);
assert(p == &parser);
int chunk_idx = messages[num_messages].num_chunks;
messages[num_messages].num_chunks++;
if (chunk_idx < MAX_CHUNKS) {
@ -2004,7 +2195,7 @@ chunk_header_cb (http_parser *p)
int
chunk_complete_cb (http_parser *p)
{
assert(p == parser);
assert(p == &parser);
/* Here we want to verify that each chunk_header_cb is matched by a
* chunk_complete_cb, so not only should the total number of calls to
@ -2209,7 +2400,7 @@ connect_headers_complete_cb (http_parser *p)
int
connect_message_complete_cb (http_parser *p)
{
messages[num_messages].should_keep_alive = http_should_keep_alive(parser);
messages[num_messages].should_keep_alive = http_should_keep_alive(&parser);
return message_complete_cb(p);
}
@ -2282,30 +2473,15 @@ void
parser_init (enum http_parser_type type)
{
num_messages = 0;
assert(parser == NULL);
parser = malloc(sizeof(http_parser));
http_parser_init(parser, type);
http_parser_init(&parser, type);
memset(&messages, 0, sizeof messages);
}
void
parser_free ()
{
assert(parser);
free(parser);
parser = NULL;
}
size_t parse (const char *buf, size_t len)
{
size_t nparsed;
currently_parsing_eof = (len == 0);
nparsed = http_parser_execute(parser, &settings, buf, len);
nparsed = http_parser_execute(&parser, &settings, buf, len);
return nparsed;
}
@ -2313,7 +2489,7 @@ size_t parse_count_body (const char *buf, size_t len)
{
size_t nparsed;
currently_parsing_eof = (len == 0);
nparsed = http_parser_execute(parser, &settings_count_body, buf, len);
nparsed = http_parser_execute(&parser, &settings_count_body, buf, len);
return nparsed;
}
@ -2324,7 +2500,7 @@ size_t parse_pause (const char *buf, size_t len)
currently_parsing_eof = (len == 0);
current_pause_parser = &s;
nparsed = http_parser_execute(parser, current_pause_parser, buf, len);
nparsed = http_parser_execute(&parser, current_pause_parser, buf, len);
return nparsed;
}
@ -2332,7 +2508,7 @@ size_t parse_connect (const char *buf, size_t len)
{
size_t nparsed;
currently_parsing_eof = (len == 0);
nparsed = http_parser_execute(parser, &settings_connect, buf, len);
nparsed = http_parser_execute(&parser, &settings_connect, buf, len);
return nparsed;
}
@ -2405,6 +2581,7 @@ message_eq (int index, int connect, const struct message *expected)
} else {
MESSAGE_CHECK_NUM_EQ(expected, m, status_code);
MESSAGE_CHECK_STR_EQ(expected, m, response_status);
assert(m->status_cb_called);
}
if (!connect) {
@ -2476,7 +2653,9 @@ message_eq (int index, int connect, const struct message *expected)
if (!r) return 0;
}
MESSAGE_CHECK_STR_EQ(expected, m, upgrade);
if (!connect) {
MESSAGE_CHECK_STR_EQ(expected, m, upgrade);
}
return 1;
}
@ -2549,7 +2728,7 @@ static void
print_error (const char *raw, size_t error_location)
{
fprintf(stderr, "\n*** %s ***\n\n",
http_errno_description(HTTP_PARSER_ERRNO(parser)));
http_errno_description(HTTP_PARSER_ERRNO(&parser)));
int this_line = 0, char_len = 0;
size_t i, j, len = strlen(raw), error_location_line = 0;
@ -3092,6 +3271,24 @@ const struct url_test url_tests[] =
,.rv=1 /* s_dead */
}
, {.name="empty url"
,.url=""
,.is_connect=0
,.rv=1
}
, {.name="NULL url"
,.url=NULL
,.is_connect=0
,.rv=1
}
, {.name="full of spaces url"
,.url=" "
,.is_connect=0
,.rv=1
}
#if HTTP_PARSER_STRICT
, {.name="tab in URL"
@ -3176,7 +3373,7 @@ test_parse_url (void)
memset(&u, 0, sizeof(u));
rv = http_parser_parse_url(test->url,
strlen(test->url),
test->url ? strlen(test->url) : 0,
test->is_connect,
&u);
@ -3216,6 +3413,14 @@ test_method_str (void)
assert(0 == strcmp("<unknown>", http_method_str(1337)));
}
void
test_status_str (void)
{
assert(0 == strcmp("OK", http_status_str(HTTP_STATUS_OK)));
assert(0 == strcmp("Not Found", http_status_str(HTTP_STATUS_NOT_FOUND)));
assert(0 == strcmp("<unknown>", http_status_str(1337)));
}
void
test_message (const struct message *message)
{
@ -3230,9 +3435,18 @@ test_message (const struct message *message)
size_t msg2len = raw_len - msg1len;
if (msg1len) {
assert(num_messages == 0);
messages[0].headers_complete_cb_called = FALSE;
read = parse(msg1, msg1len);
if (message->upgrade && parser->upgrade && num_messages > 0) {
if (!messages[0].headers_complete_cb_called && parser.nread != read) {
assert(parser.nread == read);
print_error(msg1, read);
abort();
}
if (message->upgrade && parser.upgrade && num_messages > 0) {
messages[num_messages - 1].upgrade = msg1 + read;
goto test;
}
@ -3246,7 +3460,7 @@ test_message (const struct message *message)
read = parse(msg2, msg2len);
if (message->upgrade && parser->upgrade) {
if (message->upgrade && parser.upgrade) {
messages[num_messages - 1].upgrade = msg2 + read;
goto test;
}
@ -3271,8 +3485,6 @@ test_message (const struct message *message)
}
if(!message_eq(0, 0, message)) abort();
parser_free();
}
}
@ -3308,23 +3520,21 @@ test_message_count_body (const struct message *message)
}
if(!message_eq(0, 0, message)) abort();
parser_free();
}
void
test_simple (const char *buf, enum http_errno err_expected)
test_simple_type (const char *buf,
enum http_errno err_expected,
enum http_parser_type type)
{
parser_init(HTTP_REQUEST);
parser_init(type);
enum http_errno err;
parse(buf, strlen(buf));
err = HTTP_PARSER_ERRNO(parser);
err = HTTP_PARSER_ERRNO(&parser);
parse(NULL, 0);
parser_free();
/* In strict mode, allow us to pass with an unexpected HPE_STRICT as
* long as the caller isn't expecting success.
*/
@ -3339,6 +3549,12 @@ test_simple (const char *buf, enum http_errno err_expected)
}
}
void
test_simple (const char *buf, enum http_errno err_expected)
{
test_simple_type(buf, err_expected, HTTP_REQUEST);
}
void
test_invalid_header_content (int req, const char* str)
{
@ -3488,6 +3704,30 @@ test_header_cr_no_lf_error (int req)
abort();
}
void
test_no_overflow_parse_url (void)
{
int rv;
struct http_parser_url u;
http_parser_url_init(&u);
rv = http_parser_parse_url("http://example.com:8001", 22, 0, &u);
if (rv != 0) {
fprintf(stderr,
"\n*** test_no_overflow_parse_url invalid return value=%d\n",
rv);
abort();
}
if (u.port != 800) {
fprintf(stderr,
"\n*** test_no_overflow_parse_url invalid port number=%d\n",
u.port);
abort();
}
}
void
test_header_overflow_error (int req)
{
@ -3634,7 +3874,7 @@ test_multiple3 (const struct message *r1, const struct message *r2, const struct
read = parse(total, strlen(total));
if (parser->upgrade) {
if (parser.upgrade) {
upgrade_message_fix(total, read, 3, r1, r2, r3);
goto test;
}
@ -3661,8 +3901,6 @@ test:
if (!message_eq(0, 0, r1)) abort();
if (message_count > 1 && !message_eq(1, 0, r2)) abort();
if (message_count > 2 && !message_eq(2, 0, r3)) abort();
parser_free();
}
/* SCAN through every possible breaking to make sure the
@ -3716,9 +3954,17 @@ test_scan (const struct message *r1, const struct message *r2, const struct mess
strlncpy(buf3, sizeof(buf1), total+j, buf3_len);
buf3[buf3_len] = 0;
assert(num_messages == 0);
messages[0].headers_complete_cb_called = FALSE;
read = parse(buf1, buf1_len);
if (parser->upgrade) goto test;
if (!messages[0].headers_complete_cb_called && parser.nread != read) {
print_error(buf1, read);
goto error;
}
if (parser.upgrade) goto test;
if (read != buf1_len) {
print_error(buf1, read);
@ -3727,7 +3973,7 @@ test_scan (const struct message *r1, const struct message *r2, const struct mess
read += parse(buf2, buf2_len);
if (parser->upgrade) goto test;
if (parser.upgrade) goto test;
if (read != buf1_len + buf2_len) {
print_error(buf2, read);
@ -3736,7 +3982,7 @@ test_scan (const struct message *r1, const struct message *r2, const struct mess
read += parse(buf3, buf3_len);
if (parser->upgrade) goto test;
if (parser.upgrade) goto test;
if (read != buf1_len + buf2_len + buf3_len) {
print_error(buf3, read);
@ -3746,7 +3992,7 @@ test_scan (const struct message *r1, const struct message *r2, const struct mess
parse(NULL, 0);
test:
if (parser->upgrade) {
if (parser.upgrade) {
upgrade_message_fix(total, read, 3, r1, r2, r3);
}
@ -3770,8 +4016,6 @@ test:
fprintf(stderr, "\n\nError matching messages[2] in test_scan.\n");
goto error;
}
parser_free();
}
}
}
@ -3835,7 +4079,7 @@ test_message_pause (const struct message *msg)
// completion callback.
if (messages[0].message_complete_cb_called &&
msg->upgrade &&
parser->upgrade) {
parser.upgrade) {
messages[0].upgrade = buf + nread;
goto test;
}
@ -3843,17 +4087,16 @@ test_message_pause (const struct message *msg)
if (nread < buflen) {
// Not much do to if we failed a strict-mode check
if (HTTP_PARSER_ERRNO(parser) == HPE_STRICT) {
parser_free();
if (HTTP_PARSER_ERRNO(&parser) == HPE_STRICT) {
return;
}
assert (HTTP_PARSER_ERRNO(parser) == HPE_PAUSED);
assert (HTTP_PARSER_ERRNO(&parser) == HPE_PAUSED);
}
buf += nread;
buflen -= nread;
http_parser_pause(parser, 0);
http_parser_pause(&parser, 0);
} while (buflen > 0);
nread = parse_pause(NULL, 0);
@ -3866,8 +4109,6 @@ test:
}
if(!message_eq(0, 0, msg)) abort();
parser_free();
}
/* Verify that body and next message won't be parsed in responses to CONNECT */
@ -3887,17 +4128,12 @@ test_message_connect (const struct message *msg)
}
if(!message_eq(0, 1, msg)) abort();
parser_free();
}
int
main (void)
{
parser = NULL;
int i, j, k;
int request_count;
int response_count;
unsigned i, j, k;
unsigned long version;
unsigned major;
unsigned minor;
@ -3911,18 +4147,17 @@ main (void)
printf("sizeof(http_parser) = %u\n", (unsigned int)sizeof(http_parser));
for (request_count = 0; requests[request_count].name; request_count++);
for (response_count = 0; responses[response_count].name; response_count++);
//// API
test_preserve_data();
test_parse_url();
test_method_str();
test_status_str();
//// NREAD
test_header_nread_value();
//// OVERFLOW CONDITIONS
test_no_overflow_parse_url();
test_header_overflow_error(HTTP_REQUEST);
test_no_overflow_long_body(HTTP_REQUEST, 1000);
@ -3947,25 +4182,74 @@ main (void)
test_invalid_header_field_token_error(HTTP_RESPONSE);
test_invalid_header_field_content_error(HTTP_RESPONSE);
test_simple_type(
"POST / HTTP/1.1\r\n"
"Content-Length:\r\n" // empty
"\r\n",
HPE_INVALID_CONTENT_LENGTH,
HTTP_REQUEST);
test_simple_type(
"POST / HTTP/1.1\r\n"
"Content-Length: 42 \r\n" // Note the surrounding whitespace.
"\r\n",
HPE_OK,
HTTP_REQUEST);
test_simple_type(
"POST / HTTP/1.1\r\n"
"Content-Length: 4 2\r\n"
"\r\n",
HPE_INVALID_CONTENT_LENGTH,
HTTP_REQUEST);
test_simple_type(
"POST / HTTP/1.1\r\n"
"Content-Length: 13 37\r\n"
"\r\n",
HPE_INVALID_CONTENT_LENGTH,
HTTP_REQUEST);
test_simple_type(
"POST / HTTP/1.1\r\n"
"Content-Length: 42\r\n"
" Hello world!\r\n",
HPE_INVALID_CONTENT_LENGTH,
HTTP_REQUEST);
test_simple_type(
"POST / HTTP/1.1\r\n"
"Content-Length: 42\r\n"
" \r\n",
HPE_OK,
HTTP_REQUEST);
//// RESPONSES
for (i = 0; i < response_count; i++) {
test_simple_type("HTP/1.1 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
test_simple_type("HTTP/01.1 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
test_simple_type("HTTP/11.1 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
test_simple_type("HTTP/1.01 200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
test_simple_type("HTTP/1.1\t200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
test_simple_type("\rHTTP/1.1\t200 OK\r\n\r\n", HPE_INVALID_VERSION, HTTP_RESPONSE);
for (i = 0; i < ARRAY_SIZE(responses); i++) {
test_message(&responses[i]);
}
for (i = 0; i < response_count; i++) {
for (i = 0; i < ARRAY_SIZE(responses); i++) {
test_message_pause(&responses[i]);
}
for (i = 0; i < response_count; i++) {
for (i = 0; i < ARRAY_SIZE(responses); i++) {
test_message_connect(&responses[i]);
}
for (i = 0; i < response_count; i++) {
for (i = 0; i < ARRAY_SIZE(responses); i++) {
if (!responses[i].should_keep_alive) continue;
for (j = 0; j < response_count; j++) {
for (j = 0; j < ARRAY_SIZE(responses); j++) {
if (!responses[j].should_keep_alive) continue;
for (k = 0; k < response_count; k++) {
for (k = 0; k < ARRAY_SIZE(responses); k++) {
test_multiple3(&responses[i], &responses[j], &responses[k]);
}
}
@ -4025,7 +4309,12 @@ main (void)
/// REQUESTS
test_simple("GET / IHTTP/1.0\r\n\r\n", HPE_INVALID_CONSTANT);
test_simple("GET / ICE/1.0\r\n\r\n", HPE_INVALID_CONSTANT);
test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION);
test_simple("GET / HTTP/01.1\r\n\r\n", HPE_INVALID_VERSION);
test_simple("GET / HTTP/11.1\r\n\r\n", HPE_INVALID_VERSION);
test_simple("GET / HTTP/1.01\r\n\r\n", HPE_INVALID_VERSION);
// Extended characters - see nodejs/test/parallel/test-http-headers-obstext.js
test_simple("GET / HTTP/1.1\r\n"
@ -4109,9 +4398,9 @@ main (void)
"\r\n",
HPE_INVALID_HEADER_TOKEN);
const char *dumbfuck2 =
const char *dumbluck2 =
"GET / HTTP/1.1\r\n"
"X-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n"
"X-SSL-Nonsense: -----BEGIN CERTIFICATE-----\r\n"
"\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n"
"\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n"
"\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n"
@ -4144,7 +4433,7 @@ main (void)
"\tRA==\r\n"
"\t-----END CERTIFICATE-----\r\n"
"\r\n";
test_simple(dumbfuck2, HPE_OK);
test_simple(dumbluck2, HPE_OK);
const char *corrupted_connection =
"GET / HTTP/1.1\r\n"
@ -4178,19 +4467,19 @@ main (void)
/* check to make sure our predefined requests are okay */
for (i = 0; requests[i].name; i++) {
for (i = 0; i < ARRAY_SIZE(requests); i++) {
test_message(&requests[i]);
}
for (i = 0; i < request_count; i++) {
for (i = 0; i < ARRAY_SIZE(requests); i++) {
test_message_pause(&requests[i]);
}
for (i = 0; i < request_count; i++) {
for (i = 0; i < ARRAY_SIZE(requests); i++) {
if (!requests[i].should_keep_alive) continue;
for (j = 0; j < request_count; j++) {
for (j = 0; j < ARRAY_SIZE(requests); j++) {
if (!requests[j].should_keep_alive) continue;
for (k = 0; k < request_count; k++) {
for (k = 0; k < ARRAY_SIZE(requests); k++) {
test_multiple3(&requests[i], &requests[j], &requests[k]);
}
}
@ -4211,7 +4500,7 @@ main (void)
printf("request scan 3/4 ");
test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END]
, &requests[CHUNKED_W_TRAILING_HEADERS]
, &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH]
, &requests[CHUNKED_W_NONSENSE_AFTER_LENGTH]
);
printf("request scan 4/4 ");