#include "http_parser.h"
#include <assert.h>
#include <stddef.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#ifndef ULLONG_MAX
# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */
#endif
#ifndef MIN
# define MIN(a,b) ((a) < (b) ? (a) : (b))
#endif
#ifndef ARRAY_SIZE
# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))
#endif
#ifndef BIT_AT
# define BIT_AT(a, i) \
(!!((unsigned
int
) (a)[(unsigned
int
) (i) >> 3] & \
(1 << ((unsigned
int
) (i) & 7))))
#endif
#ifndef ELEM_AT
# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v))
#endif
#define SET_ERRNO(e) \
do
{ \
parser->http_errno = (e); \
}
while
(0)
#define CALLBACK_NOTIFY_(FOR, ER) \
do
{ \
assert
(HTTP_PARSER_ERRNO(parser) == HPE_OK); \
\
if
(settings->on_##FOR) { \
if
(0 != settings->on_##FOR(parser)) { \
SET_ERRNO(HPE_CB_##FOR); \
} \
\
\
if
(HTTP_PARSER_ERRNO(parser) != HPE_OK) { \
return
(ER); \
} \
} \
}
while
(0)
#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1)
#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data)
#define CALLBACK_DATA_(FOR, LEN, ER) \
do
{ \
assert
(HTTP_PARSER_ERRNO(parser) == HPE_OK); \
\
if
(FOR##_mark) { \
if
(settings->on_##FOR) { \
if
(0 != settings->on_##FOR(parser, FOR##_mark, (LEN))) { \
SET_ERRNO(HPE_CB_##FOR); \
} \
\
\
if
(HTTP_PARSER_ERRNO(parser) != HPE_OK) { \
return
(ER); \
} \
} \
FOR##_mark = NULL; \
} \
}
while
(0)
#define CALLBACK_DATA(FOR) \
CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1)
#define CALLBACK_DATA_NOADVANCE(FOR) \
CALLBACK_DATA_(FOR, p - FOR##_mark, p - data)
#define MARK(FOR) \
do
{ \
if
(!FOR##_mark) { \
FOR##_mark = p; \
} \
}
while
(0)
#define PROXY_CONNECTION "proxy-connection"
#define CONNECTION "connection"
#define CONTENT_LENGTH "content-length"
#define TRANSFER_ENCODING "transfer-encoding"
#define UPGRADE "upgrade"
#define CHUNKED "chunked"
#define KEEP_ALIVE "keep-alive"
#define CLOSE "close"
static
const
char
*method_strings[] =
{
#define XX(num, name, string) #string,
HTTP_METHOD_MAP(XX)
#undef XX
};
static
const
char
tokens[256] = {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
0,
'!'
, 0,
'#'
,
'$'
,
'%'
,
'&'
,
'\''
,
0, 0,
'*'
,
'+'
, 0,
'-'
,
'.'
, 0,
'0'
,
'1'
,
'2'
,
'3'
,
'4'
,
'5'
,
'6'
,
'7'
,
'8'
,
'9'
, 0, 0, 0, 0, 0, 0,
0,
'a'
,
'b'
,
'c'
,
'd'
,
'e'
,
'f'
,
'g'
,
'h'
,
'i'
,
'j'
,
'k'
,
'l'
,
'm'
,
'n'
,
'o'
,
'p'
,
'q'
,
'r'
,
's'
,
't'
,
'u'
,
'v'
,
'w'
,
'x'
,
'y'
,
'z'
, 0, 0, 0,
'^'
,
'_'
,
'`'
,
'a'
,
'b'
,
'c'
,
'd'
,
'e'
,
'f'
,
'g'
,
'h'
,
'i'
,
'j'
,
'k'
,
'l'
,
'm'
,
'n'
,
'o'
,
'p'
,
'q'
,
'r'
,
's'
,
't'
,
'u'
,
'v'
,
'w'
,
'x'
,
'y'
,
'z'
, 0,
'|'
, 0,
'~'
, 0 };
static
const
int8_t unhex[256] =
{-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1
,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
};
#if HTTP_PARSER_STRICT
# define T(v) 0
#else
# define T(v) v
#endif
static
const
uint8_t normal_url_char[32] = {
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0,
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0,
0 | 2 | 4 | 0 | 16 | 32 | 64 | 128,
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
1 | 2 | 4 | 8 | 16 | 32 | 64 | 0,
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
1 | 2 | 4 | 8 | 16 | 32 | 64 | 128,
1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, };
#undef T
enum
state
{ s_dead = 1
, s_start_req_or_res
, s_res_or_resp_H
, s_start_res
, s_res_H
, 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_minor
, s_res_first_status_code
, s_res_status_code
, s_res_status
, s_res_line_almost_done
, s_start_req
, s_req_method
, s_req_spaces_before_url
, s_req_schema
, s_req_schema_slash
, s_req_schema_slash_slash
, s_req_server_start
, s_req_server
, s_req_server_with_at
, s_req_path
, s_req_query_string_start
, s_req_query_string
, s_req_fragment_start
, s_req_fragment
, s_req_http_start
, s_req_http_H
, s_req_http_HT
, s_req_http_HTT
, s_req_http_HTTP
, s_req_first_http_major
, s_req_http_major
, s_req_first_http_minor
, s_req_http_minor
, s_req_line_almost_done
, s_header_field_start
, s_header_field
, s_header_value_start
, s_header_value
, s_header_value_lws
, s_header_almost_done
, s_chunk_size_start
, s_chunk_size
, s_chunk_parameters
, s_chunk_size_almost_done
, s_headers_almost_done
, s_headers_done
, s_chunk_data
, s_chunk_data_almost_done
, s_chunk_data_done
, s_body_identity
, s_body_identity_eof
, s_message_done
};
#define PARSING_HEADER(state) (state <= s_headers_done)
enum
header_states
{ h_general = 0
, h_C
, h_CO
, h_CON
, h_matching_connection
, h_matching_proxy_connection
, h_matching_content_length
, h_matching_transfer_encoding
, h_matching_upgrade
, h_connection
, h_content_length
, h_transfer_encoding
, h_upgrade
, h_matching_transfer_encoding_chunked
, h_matching_connection_keep_alive
, h_matching_connection_close
, h_transfer_encoding_chunked
, h_connection_keep_alive
, h_connection_close
};
enum
http_host_state
{
s_http_host_dead = 1
, s_http_userinfo_start
, s_http_userinfo
, s_http_host_start
, s_http_host_v6_start
, s_http_host
, s_http_host_v6
, s_http_host_v6_end
, s_http_host_port_start
, s_http_host_port
};
#define CR '\r'
#define LF '\n'
#define LOWER(c) (unsigned char)(c | 0x20)
#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))
#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f'))
#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \
(c) ==
'!'
|| (c) ==
'~'
|| (c) ==
'*'
|| (c) ==
'\''
|| (c) ==
'('
|| \
(c) ==
')'
)
#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \
(c) ==
';'
|| (c) ==
':'
|| (c) ==
'&'
|| (c) ==
'='
|| (c) ==
'+'
|| \
(c) ==
'$'
|| (c) ==
','
)
#if HTTP_PARSER_STRICT
#define TOKEN(c) (tokens[(unsigned char)c])
#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c))
#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-')
#else
#define TOKEN(c) ((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) \
(IS_ALPHANUM(c) || (c) ==
'.'
|| (c) ==
'-'
|| (c) ==
'_'
)
#endif
#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)
#if HTTP_PARSER_STRICT
# 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)
# define NEW_MESSAGE() start_state
#endif
#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
int
http_message_needs_eof(
const
http_parser *parser);
static
enum
state
parse_url_char(
enum
state s,
const
char
ch)
{
if
(ch ==
' '
|| ch ==
'\r'
|| ch ==
'\n'
) {
return
s_dead;
}
#if HTTP_PARSER_STRICT
if
(ch ==
'\t'
|| ch ==
'\f'
) {
return
s_dead;
}
#endif
switch
(s) {
case
s_req_spaces_before_url:
if
(ch ==
'/'
|| ch ==
'*'
) {
return
s_req_path;
}
if
(IS_ALPHA(ch)) {
return
s_req_schema;
}
break
;
case
s_req_schema:
if
(IS_ALPHA(ch)) {
return
s;
}
if
(ch ==
':'
) {
return
s_req_schema_slash;
}
break
;
case
s_req_schema_slash:
if
(ch ==
'/'
) {
return
s_req_schema_slash_slash;
}
break
;
case
s_req_schema_slash_slash:
if
(ch ==
'/'
) {
return
s_req_server_start;
}
break
;
case
s_req_server_with_at:
if
(ch ==
'@'
) {
return
s_dead;
}
case
s_req_server_start:
case
s_req_server:
if
(ch ==
'/'
) {
return
s_req_path;
}
if
(ch ==
'?'
) {
return
s_req_query_string_start;
}
if
(ch ==
'@'
) {
return
s_req_server_with_at;
}
if
(IS_USERINFO_CHAR(ch) || ch ==
'['
|| ch ==
']'
) {
return
s_req_server;
}
break
;
case
s_req_path:
if
(IS_URL_CHAR(ch)) {
return
s;
}
switch
(ch) {
case
'?'
:
return
s_req_query_string_start;
case
'#'
:
return
s_req_fragment_start;
}
break
;
case
s_req_query_string_start:
case
s_req_query_string:
if
(IS_URL_CHAR(ch)) {
return
s_req_query_string;
}
switch
(ch) {
case
'?'
:
return
s_req_query_string;
case
'#'
:
return
s_req_fragment_start;
}
break
;
case
s_req_fragment_start:
if
(IS_URL_CHAR(ch)) {
return
s_req_fragment;
}
switch
(ch) {
case
'?'
:
return
s_req_fragment;
case
'#'
:
return
s;
}
break
;
case
s_req_fragment:
if
(IS_URL_CHAR(ch)) {
return
s;
}
switch
(ch) {
case
'?'
:
case
'#'
:
return
s;
}
break
;
default
:
break
;
}
return
s_dead;
}
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;
const
char
*header_field_mark = 0;
const
char
*header_value_mark = 0;
const
char
*url_mark = 0;
const
char
*body_mark = 0;
if
(HTTP_PARSER_ERRNO(parser) != HPE_OK) {
return
0;
}
if
(len == 0) {
switch
(parser->state) {
case
s_body_identity_eof:
CALLBACK_NOTIFY_NOADVANCE(message_complete);
return
0;
case
s_dead:
case
s_start_req_or_res:
case
s_start_res:
case
s_start_req:
return
0;
default
:
SET_ERRNO(HPE_INVALID_EOF_STATE);
return
1;
}
}
if
(parser->state == s_header_field)
header_field_mark = data;
if
(parser->state == s_header_value)
header_value_mark = data;
switch
(parser->state) {
case
s_req_path:
case
s_req_schema:
case
s_req_schema_slash:
case
s_req_schema_slash_slash:
case
s_req_server_start:
case
s_req_server:
case
s_req_server_with_at:
case
s_req_query_string_start:
case
s_req_query_string:
case
s_req_fragment_start:
case
s_req_fragment:
url_mark = data;
break
;
}
for
(p=data; p != data + len; p++) {
ch = *p;
if
(PARSING_HEADER(parser->state)) {
++parser->nread;
if
(parser->nread > HTTP_MAX_HEADER_SIZE) {
SET_ERRNO(HPE_HEADER_OVERFLOW);
goto
error;
}
}
reexecute_byte:
switch
(parser->state) {
case
s_dead:
if
(ch == CR || ch == LF)
break
;
SET_ERRNO(HPE_CLOSED_CONNECTION);
goto
error;
case
s_start_req_or_res:
{
if
(ch == CR || ch == LF)
break
;
parser->flags = 0;
parser->content_length = ULLONG_MAX;
if
(ch ==
'H'
) {
parser->state = s_res_or_resp_H;
CALLBACK_NOTIFY(message_begin);
}
else
{
parser->type = HTTP_REQUEST;
parser->state = s_start_req;
goto
reexecute_byte;
}
break
;
}
case
s_res_or_resp_H:
if
(ch ==
'T'
) {
parser->type = HTTP_RESPONSE;
parser->state = s_res_HT;
}
else
{
if
(ch !=
'E'
) {
SET_ERRNO(HPE_INVALID_CONSTANT);
goto
error;
}
parser->type = HTTP_REQUEST;
parser->method = HTTP_HEAD;
parser->index = 2;
parser->state = s_req_method;
}
break
;
case
s_start_res:
{
parser->flags = 0;
parser->content_length = ULLONG_MAX;
switch
(ch) {
case
'H'
:
parser->state = s_res_H;
break
;
case
CR:
case
LF:
break
;
default
:
SET_ERRNO(HPE_INVALID_CONSTANT);
goto
error;
}
CALLBACK_NOTIFY(message_begin);
break
;
}
case
s_res_H:
STRICT_CHECK(ch !=
'T'
);
parser->state = s_res_HT;
break
;
case
s_res_HT:
STRICT_CHECK(ch !=
'T'
);
parser->state = s_res_HTT;
break
;
case
s_res_HTT:
STRICT_CHECK(ch !=
'P'
);
parser->state = s_res_HTTP;
break
;
case
s_res_HTTP:
STRICT_CHECK(ch !=
'/'
);
parser->state = s_res_first_http_major;
break
;
case
s_res_first_http_major:
if
(ch <
'0'
|| ch >
'9'
) {
SET_ERRNO(HPE_INVALID_VERSION);
goto
error;
}
parser->http_major = ch -
'0'
;
parser->state = s_res_http_major;
break
;
case
s_res_http_major:
{
if
(ch ==
'.'
) {
parser->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
(parser->http_major > 999) {
SET_ERRNO(HPE_INVALID_VERSION);
goto
error;
}
break
;
}
case
s_res_first_http_minor:
if
(!IS_NUM(ch)) {
SET_ERRNO(HPE_INVALID_VERSION);
goto
error;
}
parser->http_minor = ch -
'0'
;
parser->state = s_res_http_minor;
break
;
case
s_res_http_minor:
{
if
(ch ==
' '
) {
parser->state = s_res_first_status_code;
break
;
}
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) {
SET_ERRNO(HPE_INVALID_VERSION);
goto
error;
}
break
;
}
case
s_res_first_status_code:
{
if
(!IS_NUM(ch)) {
if
(ch ==
' '
) {
break
;
}
SET_ERRNO(HPE_INVALID_STATUS);
goto
error;
}
parser->status_code = ch -
'0'
;
parser->state = s_res_status_code;
break
;
}
case
s_res_status_code:
{
if
(!IS_NUM(ch)) {
switch
(ch) {
case
' '
:
parser->state = s_res_status;
break
;
case
CR:
parser->state = s_res_line_almost_done;
break
;
case
LF:
parser->state = s_header_field_start;
break
;
default
:
SET_ERRNO(HPE_INVALID_STATUS);
goto
error;
}
break
;
}
parser->status_code *= 10;
parser->status_code += ch -
'0'
;
if
(parser->status_code > 999) {
SET_ERRNO(HPE_INVALID_STATUS);
goto
error;
}
break
;
}
case
s_res_status:
if
(ch == CR) {
parser->state = s_res_line_almost_done;
break
;
}
if
(ch == LF) {
parser->state = s_header_field_start;
break
;
}
break
;
case
s_res_line_almost_done:
STRICT_CHECK(ch != LF);
parser->state = s_header_field_start;
break
;
case
s_start_req:
{
if
(ch == CR || ch == LF)
break
;
parser->flags = 0;
parser->content_length = ULLONG_MAX;
if
(!IS_ALPHA(ch)) {
SET_ERRNO(HPE_INVALID_METHOD);
goto
error;
}
parser->method = (
enum
http_method) 0;
parser->index = 1;
switch
(ch) {
case
'C'
: parser->method = HTTP_CONNECT;
break
;
case
'D'
: parser->method = HTTP_DELETE;
break
;
case
'G'
: parser->method = HTTP_GET;
break
;
case
'H'
: parser->method = HTTP_HEAD;
break
;
case
'L'
: parser->method = HTTP_LOCK;
break
;
case
'M'
: parser->method = HTTP_MKCOL;
break
;
case
'N'
: parser->method = HTTP_NOTIFY;
break
;
case
'O'
: parser->method = HTTP_OPTIONS;
break
;
case
'P'
: parser->method = HTTP_POST;
break
;
case
'R'
: parser->method = HTTP_REPORT;
break
;
case
'S'
: parser->method = HTTP_SUBSCRIBE;
break
;
case
'T'
: parser->method = HTTP_TRACE;
break
;
case
'U'
: parser->method = HTTP_UNLOCK;
break
;
default
:
SET_ERRNO(HPE_INVALID_METHOD);
goto
error;
}
parser->state = s_req_method;
CALLBACK_NOTIFY(message_begin);
break
;
}
case
s_req_method:
{
const
char
*matcher;
if
(ch ==
'\0'
) {
SET_ERRNO(HPE_INVALID_METHOD);
goto
error;
}
matcher = method_strings[parser->method];
if
(ch ==
' '
&& matcher[parser->index] ==
'\0'
) {
parser->state = s_req_spaces_before_url;
}
else
if
(ch == matcher[parser->index]) {
;
}
else
if
(parser->method == HTTP_CONNECT) {
if
(parser->index == 1 && ch ==
'H'
) {
parser->method = HTTP_CHECKOUT;
}
else
if
(parser->index == 2 && ch ==
'P'
) {
parser->method = HTTP_COPY;
}
else
{
goto
error;
}
}
else
if
(parser->method == HTTP_MKCOL) {
if
(parser->index == 1 && ch ==
'O'
) {
parser->method = HTTP_MOVE;
}
else
if
(parser->index == 1 && ch ==
'E'
) {
parser->method = HTTP_MERGE;
}
else
if
(parser->index == 1 && ch ==
'-'
) {
parser->method = HTTP_MSEARCH;
}
else
if
(parser->index == 2 && ch ==
'A'
) {
parser->method = HTTP_MKACTIVITY;
}
else
{
goto
error;
}
}
else
if
(parser->method == HTTP_SUBSCRIBE) {
if
(parser->index == 1 && ch ==
'E'
) {
parser->method = HTTP_SEARCH;
}
else
{
goto
error;
}
}
else
if
(parser->index == 1 && parser->method == HTTP_POST) {
if
(ch ==
'R'
) {
parser->method = HTTP_PROPFIND;
}
else
if
(ch ==
'U'
) {
parser->method = HTTP_PUT;
}
else
if
(ch ==
'A'
) {
parser->method = HTTP_PATCH;
}
else
{
goto
error;
}
}
else
if
(parser->index == 2) {
if
(parser->method == HTTP_PUT) {
if
(ch ==
'R'
) parser->method = HTTP_PURGE;
}
else
if
(parser->method == HTTP_UNLOCK) {
if
(ch ==
'S'
) parser->method = HTTP_UNSUBSCRIBE;
}
}
else
if
(parser->index == 4 && parser->method == HTTP_PROPFIND && ch ==
'P'
) {
parser->method = HTTP_PROPPATCH;
}
else
{
SET_ERRNO(HPE_INVALID_METHOD);
goto
error;
}
++parser->index;
break
;
}
case
s_req_spaces_before_url:
{
if
(ch ==
' '
)
break
;
MARK(url);
if
(parser->method == HTTP_CONNECT) {
parser->state = s_req_server_start;
}
parser->state = parse_url_char((
enum
state)parser->state, ch);
if
(parser->state == s_dead) {
SET_ERRNO(HPE_INVALID_URL);
goto
error;
}
break
;
}
case
s_req_schema:
case
s_req_schema_slash:
case
s_req_schema_slash_slash:
case
s_req_server_start:
{
switch
(ch) {
case
' '
:
case
CR:
case
LF:
SET_ERRNO(HPE_INVALID_URL);
goto
error;
default
:
parser->state = parse_url_char((
enum
state)parser->state, ch);
if
(parser->state == s_dead) {
SET_ERRNO(HPE_INVALID_URL);
goto
error;
}
}
break
;
}
case
s_req_server:
case
s_req_server_with_at:
case
s_req_path:
case
s_req_query_string_start:
case
s_req_query_string:
case
s_req_fragment_start:
case
s_req_fragment:
{
switch
(ch) {
case
' '
:
parser->state = s_req_http_start;
CALLBACK_DATA(url);
break
;
case
CR:
case
LF:
parser->http_major = 0;
parser->http_minor = 9;
parser->state = (ch == CR) ?
s_req_line_almost_done :
s_header_field_start;
CALLBACK_DATA(url);
break
;
default
:
parser->state = parse_url_char((
enum
state)parser->state, ch);
if
(parser->state == s_dead) {
SET_ERRNO(HPE_INVALID_URL);
goto
error;
}
}
break
;
}
case
s_req_http_start:
switch
(ch) {
case
'H'
:
parser->state = s_req_http_H;
break
;
case
' '
:
break
;
default
:
SET_ERRNO(HPE_INVALID_CONSTANT);
goto
error;
}
break
;
case
s_req_http_H:
STRICT_CHECK(ch !=
'T'
);
parser->state = s_req_http_HT;
break
;
case
s_req_http_HT:
STRICT_CHECK(ch !=
'T'
);
parser->state = s_req_http_HTT;
break
;
case
s_req_http_HTT:
STRICT_CHECK(ch !=
'P'
);
parser->state = s_req_http_HTTP;
break
;
case
s_req_http_HTTP:
STRICT_CHECK(ch !=
'/'
);
parser->state = s_req_first_http_major;
break
;
case
s_req_first_http_major:
if
(ch <
'1'
|| ch >
'9'
) {
SET_ERRNO(HPE_INVALID_VERSION);
goto
error;
}
parser->http_major = ch -
'0'
;
parser->state = s_req_http_major;
break
;
case
s_req_http_major:
{
if
(ch ==
'.'
) {
parser->state = s_req_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
(parser->http_major > 999) {
SET_ERRNO(HPE_INVALID_VERSION);
goto
error;
}
break
;
}
case
s_req_first_http_minor:
if
(!IS_NUM(ch)) {
SET_ERRNO(HPE_INVALID_VERSION);
goto
error;
}
parser->http_minor = ch -
'0'
;
parser->state = s_req_http_minor;
break
;
case
s_req_http_minor:
{
if
(ch == CR) {
parser->state = s_req_line_almost_done;
break
;
}
if
(ch == LF) {
parser->state = s_header_field_start;
break
;
}
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) {
SET_ERRNO(HPE_INVALID_VERSION);
goto
error;
}
break
;
}
case
s_req_line_almost_done:
{
if
(ch != LF) {
SET_ERRNO(HPE_LF_EXPECTED);
goto
error;
}
parser->state = s_header_field_start;
break
;
}
case
s_header_field_start:
{
if
(ch == CR) {
parser->state = s_headers_almost_done;
break
;
}
if
(ch == LF) {
parser->state = s_headers_almost_done;
goto
reexecute_byte;
}
c = TOKEN(ch);
if
(!c) {
SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
goto
error;
}
MARK(header_field);
parser->index = 0;
parser->state = s_header_field;
switch
(c) {
case
'c'
:
parser->header_state = h_C;
break
;
case
'p'
:
parser->header_state = h_matching_proxy_connection;
break
;
case
't'
:
parser->header_state = h_matching_transfer_encoding;
break
;
case
'u'
:
parser->header_state = h_matching_upgrade;
break
;
default
:
parser->header_state = h_general;
break
;
}
break
;
}
case
s_header_field:
{
c = TOKEN(ch);
if
(c) {
switch
(parser->header_state) {
case
h_general:
break
;
case
h_C:
parser->index++;
parser->header_state = (c ==
'o'
? h_CO : h_general);
break
;
case
h_CO:
parser->index++;
parser->header_state = (c ==
'n'
? h_CON : h_general);
break
;
case
h_CON:
parser->index++;
switch
(c) {
case
'n'
:
parser->header_state = h_matching_connection;
break
;
case
't'
:
parser->header_state = h_matching_content_length;
break
;
default
:
parser->header_state = h_general;
break
;
}
break
;
case
h_matching_connection:
parser->index++;
if
(parser->index >
sizeof
(CONNECTION)-1
|| c != CONNECTION[parser->index]) {
parser->header_state = h_general;
}
else
if
(parser->index ==
sizeof
(CONNECTION)-2) {
parser->header_state = h_connection;
}
break
;
case
h_matching_proxy_connection:
parser->index++;
if
(parser->index >
sizeof
(PROXY_CONNECTION)-1
|| c != PROXY_CONNECTION[parser->index]) {
parser->header_state = h_general;
}
else
if
(parser->index ==
sizeof
(PROXY_CONNECTION)-2) {
parser->header_state = h_connection;
}
break
;
case
h_matching_content_length:
parser->index++;
if
(parser->index >
sizeof
(CONTENT_LENGTH)-1
|| c != CONTENT_LENGTH[parser->index]) {
parser->header_state = h_general;
}
else
if
(parser->index ==
sizeof
(CONTENT_LENGTH)-2) {
parser->header_state = h_content_length;
}
break
;
case
h_matching_transfer_encoding:
parser->index++;
if
(parser->index >
sizeof
(TRANSFER_ENCODING)-1
|| c != TRANSFER_ENCODING[parser->index]) {
parser->header_state = h_general;
}
else
if
(parser->index ==
sizeof
(TRANSFER_ENCODING)-2) {
parser->header_state = h_transfer_encoding;
}
break
;
case
h_matching_upgrade:
parser->index++;
if
(parser->index >
sizeof
(UPGRADE)-1
|| c != UPGRADE[parser->index]) {
parser->header_state = h_general;
}
else
if
(parser->index ==
sizeof
(UPGRADE)-2) {
parser->header_state = h_upgrade;
}
break
;
case
h_connection:
case
h_content_length:
case
h_transfer_encoding:
case
h_upgrade:
if
(ch !=
' '
) parser->header_state = h_general;
break
;
default
:
assert
(0 &&
"Unknown header_state"
);
break
;
}
break
;
}
if
(ch ==
':'
) {
parser->state = s_header_value_start;
CALLBACK_DATA(header_field);
break
;
}
if
(ch == CR) {
parser->state = s_header_almost_done;
CALLBACK_DATA(header_field);
break
;
}
if
(ch == LF) {
parser->state = s_header_field_start;
CALLBACK_DATA(header_field);
break
;
}
SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
goto
error;
}
case
s_header_value_start:
{
if
(ch ==
' '
|| ch ==
'\t'
)
break
;
MARK(header_value);
parser->state = s_header_value;
parser->index = 0;
if
(ch == CR) {
parser->header_state = h_general;
parser->state = s_header_almost_done;
CALLBACK_DATA(header_value);
break
;
}
if
(ch == LF) {
parser->state = s_header_field_start;
CALLBACK_DATA(header_value);
break
;
}
c = LOWER(ch);
switch
(parser->header_state) {
case
h_upgrade:
parser->flags |= F_UPGRADE;
parser->header_state = h_general;
break
;
case
h_transfer_encoding:
if
(
'c'
== c) {
parser->header_state = h_matching_transfer_encoding_chunked;
}
else
{
parser->header_state = h_general;
}
break
;
case
h_content_length:
if
(!IS_NUM(ch)) {
SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
goto
error;
}
parser->content_length = ch -
'0'
;
break
;
case
h_connection:
if
(c ==
'k'
) {
parser->header_state = h_matching_connection_keep_alive;
}
else
if
(c ==
'c'
) {
parser->header_state = h_matching_connection_close;
}
else
{
parser->header_state = h_general;
}
break
;
default
:
parser->header_state = h_general;
break
;
}
break
;
}
case
s_header_value:
{
if
(ch == CR) {
parser->state = s_header_almost_done;
CALLBACK_DATA(header_value);
break
;
}
if
(ch == LF) {
parser->state = s_header_almost_done;
CALLBACK_DATA_NOADVANCE(header_value);
goto
reexecute_byte;
}
c = LOWER(ch);
switch
(parser->header_state) {
case
h_general:
break
;
case
h_connection:
case
h_transfer_encoding:
assert
(0 &&
"Shouldn't get here."
);
break
;
case
h_content_length:
{
uint64_t t;
if
(ch ==
' '
)
break
;
if
(!IS_NUM(ch)) {
SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
goto
error;
}
t = parser->content_length;
t *= 10;
t += ch -
'0'
;
if
(t < parser->content_length || t == ULLONG_MAX) {
SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
goto
error;
}
parser->content_length = t;
break
;
}
case
h_matching_transfer_encoding_chunked:
parser->index++;
if
(parser->index >
sizeof
(CHUNKED)-1
|| c != CHUNKED[parser->index]) {
parser->header_state = h_general;
}
else
if
(parser->index ==
sizeof
(CHUNKED)-2) {
parser->header_state = h_transfer_encoding_chunked;
}
break
;
case
h_matching_connection_keep_alive:
parser->index++;
if
(parser->index >
sizeof
(KEEP_ALIVE)-1
|| c != KEEP_ALIVE[parser->index]) {
parser->header_state = h_general;
}
else
if
(parser->index ==
sizeof
(KEEP_ALIVE)-2) {
parser->header_state = h_connection_keep_alive;
}
break
;
case
h_matching_connection_close:
parser->index++;
if
(parser->index >
sizeof
(CLOSE)-1 || c != CLOSE[parser->index]) {
parser->header_state = h_general;
}
else
if
(parser->index ==
sizeof
(CLOSE)-2) {
parser->header_state = h_connection_close;
}
break
;
case
h_transfer_encoding_chunked:
case
h_connection_keep_alive:
case
h_connection_close:
if
(ch !=
' '
) parser->header_state = h_general;
break
;
default
:
parser->state = s_header_value;
parser->header_state = h_general;
break
;
}
break
;
}
case
s_header_almost_done:
{
STRICT_CHECK(ch != LF);
parser->state = s_header_value_lws;
switch
(parser->header_state) {
case
h_connection_keep_alive:
parser->flags |= F_CONNECTION_KEEP_ALIVE;
break
;
case
h_connection_close:
parser->flags |= F_CONNECTION_CLOSE;
break
;
case
h_transfer_encoding_chunked:
parser->flags |= F_CHUNKED;
break
;
default
:
break
;
}
break
;
}
case
s_header_value_lws:
{
if
(ch ==
' '
|| ch ==
'\t'
)
parser->state = s_header_value_start;
else
{
parser->state = s_header_field_start;
goto
reexecute_byte;
}
break
;
}
case
s_headers_almost_done:
{
STRICT_CHECK(ch != LF);
if
(parser->flags & F_TRAILING) {
parser->state = NEW_MESSAGE();
CALLBACK_NOTIFY(message_complete);
break
;
}
parser->state = s_headers_done;
parser->upgrade =
(parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT);
if
(settings->on_headers_complete) {
switch
(settings->on_headers_complete(parser)) {
case
0:
break
;
case
1:
parser->flags |= F_SKIPBODY;
break
;
default
:
SET_ERRNO(HPE_CB_headers_complete);
return
p - data;
}
}
if
(HTTP_PARSER_ERRNO(parser) != HPE_OK) {
return
p - data;
}
goto
reexecute_byte;
}
case
s_headers_done:
{
STRICT_CHECK(ch != LF);
parser->nread = 0;
if
(parser->upgrade) {
parser->state = NEW_MESSAGE();
CALLBACK_NOTIFY(message_complete);
return
(p - data) + 1;
}
if
(parser->flags & F_SKIPBODY) {
parser->state = NEW_MESSAGE();
CALLBACK_NOTIFY(message_complete);
}
else
if
(parser->flags & F_CHUNKED) {
parser->state = s_chunk_size_start;
}
else
{
if
(parser->content_length == 0) {
parser->state = NEW_MESSAGE();
CALLBACK_NOTIFY(message_complete);
}
else
if
(parser->content_length != ULLONG_MAX) {
parser->state = s_body_identity;
}
else
{
if
(parser->type == HTTP_REQUEST ||
!http_message_needs_eof(parser)) {
parser->state = NEW_MESSAGE();
CALLBACK_NOTIFY(message_complete);
}
else
{
parser->state = s_body_identity_eof;
}
}
}
break
;
}
case
s_body_identity:
{
uint64_t to_read = MIN(parser->content_length,
(uint64_t) ((data + len) - p));
assert
(parser->content_length != 0
&& parser->content_length != ULLONG_MAX);
MARK(body);
parser->content_length -= to_read;
p += to_read - 1;
if
(parser->content_length == 0) {
parser->state = s_message_done;
CALLBACK_DATA_(body, p - body_mark + 1, p - data);
goto
reexecute_byte;
}
break
;
}
case
s_body_identity_eof:
MARK(body);
p = data + len - 1;
break
;
case
s_message_done:
parser->state = NEW_MESSAGE();
CALLBACK_NOTIFY(message_complete);
break
;
case
s_chunk_size_start:
{
assert
(parser->nread == 1);
assert
(parser->flags & F_CHUNKED);
unhex_val = unhex[(unsigned
char
)ch];
if
(unhex_val == -1) {
SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
goto
error;
}
parser->content_length = unhex_val;
parser->state = s_chunk_size;
break
;
}
case
s_chunk_size:
{
uint64_t t;
assert
(parser->flags & F_CHUNKED);
if
(ch == CR) {
parser->state = s_chunk_size_almost_done;
break
;
}
unhex_val = unhex[(unsigned
char
)ch];
if
(unhex_val == -1) {
if
(ch ==
';'
|| ch ==
' '
) {
parser->state = s_chunk_parameters;
break
;
}
SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
goto
error;
}
t = parser->content_length;
t *= 16;
t += unhex_val;
if
(t < parser->content_length || t == ULLONG_MAX) {
SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
goto
error;
}
parser->content_length = t;
break
;
}
case
s_chunk_parameters:
{
assert
(parser->flags & F_CHUNKED);
if
(ch == CR) {
parser->state = s_chunk_size_almost_done;
break
;
}
break
;
}
case
s_chunk_size_almost_done:
{
assert
(parser->flags & F_CHUNKED);
STRICT_CHECK(ch != LF);
parser->nread = 0;
if
(parser->content_length == 0) {
parser->flags |= F_TRAILING;
parser->state = s_header_field_start;
}
else
{
parser->state = s_chunk_data;
}
break
;
}
case
s_chunk_data:
{
uint64_t to_read = MIN(parser->content_length,
(uint64_t) ((data + len) - p));
assert
(parser->flags & F_CHUNKED);
assert
(parser->content_length != 0
&& parser->content_length != ULLONG_MAX);
MARK(body);
parser->content_length -= to_read;
p += to_read - 1;
if
(parser->content_length == 0) {
parser->state = s_chunk_data_almost_done;
}
break
;
}
case
s_chunk_data_almost_done:
assert
(parser->flags & F_CHUNKED);
assert
(parser->content_length == 0);
STRICT_CHECK(ch != CR);
parser->state = s_chunk_data_done;
CALLBACK_DATA(body);
break
;
case
s_chunk_data_done:
assert
(parser->flags & F_CHUNKED);
STRICT_CHECK(ch != LF);
parser->nread = 0;
parser->state = s_chunk_size_start;
break
;
default
:
assert
(0 &&
"unhandled state"
);
SET_ERRNO(HPE_INVALID_INTERNAL_STATE);
goto
error;
}
}
assert
(((header_field_mark ? 1 : 0) +
(header_value_mark ? 1 : 0) +
(url_mark ? 1 : 0) +
(body_mark ? 1 : 0)) <= 1);
CALLBACK_DATA_NOADVANCE(header_field);
CALLBACK_DATA_NOADVANCE(header_value);
CALLBACK_DATA_NOADVANCE(url);
CALLBACK_DATA_NOADVANCE(body);
return
len;
error:
if
(HTTP_PARSER_ERRNO(parser) == HPE_OK) {
SET_ERRNO(HPE_UNKNOWN);
}
return
(p - data);
}
int
http_message_needs_eof (
const
http_parser *parser)
{
if
(parser->type == HTTP_REQUEST) {
return
0;
}
if
(parser->status_code / 100 == 1 ||
parser->status_code == 204 ||
parser->status_code == 304 ||
parser->flags & F_SKIPBODY) {
return
0;
}
if
((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) {
return
0;
}
return
1;
}
int
http_should_keep_alive (
const
http_parser *parser)
{
if
(parser->http_major > 0 && parser->http_minor > 0) {
if
(parser->flags & F_CONNECTION_CLOSE) {
return
0;
}
}
else
{
if
(!(parser->flags & F_CONNECTION_KEEP_ALIVE)) {
return
0;
}
}
return
!http_message_needs_eof(parser);
}
const
char
*
http_method_str (
enum
http_method m)
{
return
ELEM_AT(method_strings, m,
"<unknown>"
);
}
void
http_parser_init (http_parser *parser,
enum
http_parser_type t)
{
void
*data = parser->data;
memset
(parser, 0,
sizeof
(*parser));
parser->data = data;
parser->type = t;
parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res));
parser->http_errno = HPE_OK;
}
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;
}
static
enum
http_host_state
http_parse_host_char(
enum
http_host_state s,
const
char
ch) {
switch
(s) {
case
s_http_userinfo:
case
s_http_userinfo_start:
if
(ch ==
'@'
) {
return
s_http_host_start;
}
if
(IS_USERINFO_CHAR(ch)) {
return
s_http_userinfo;
}
break
;
case
s_http_host_start:
if
(ch ==
'['
) {
return
s_http_host_v6_start;
}
if
(IS_HOST_CHAR(ch)) {
return
s_http_host;
}
break
;
case
s_http_host:
if
(IS_HOST_CHAR(ch)) {
return
s_http_host;
}
case
s_http_host_v6_end:
if
(ch ==
':'
) {
return
s_http_host_port_start;
}
break
;
case
s_http_host_v6:
if
(ch ==
']'
) {
return
s_http_host_v6_end;
}
case
s_http_host_v6_start:
if
(IS_HEX(ch) || ch ==
':'
) {
return
s_http_host_v6;
}
break
;
case
s_http_host_port:
case
s_http_host_port_start:
if
(IS_NUM(ch)) {
return
s_http_host_port;
}
break
;
default
:
break
;
}
return
s_http_host_dead;
}
static
int
http_parse_host(
const
char
* buf,
struct
http_parser_url *u,
int
found_at) {
enum
http_host_state s;
const
char
*p;
size_t
buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len;
u->field_data[UF_HOST].len = 0;
s = found_at ? s_http_userinfo_start : s_http_host_start;
for
(p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) {
enum
http_host_state new_s = http_parse_host_char(s, *p);
if
(new_s == s_http_host_dead) {
return
1;
}
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].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].len++;
break
;
case
s_http_host_port:
if
(s != s_http_host_port) {
u->field_data[UF_PORT].off = p - buf;
u->field_data[UF_PORT].len = 0;
u->field_set |= (1 << UF_PORT);
}
u->field_data[UF_PORT].len++;
break
;
case
s_http_userinfo:
if
(s != s_http_userinfo) {
u->field_data[UF_USERINFO].off = p - buf ;
u->field_data[UF_USERINFO].len = 0;
u->field_set |= (1 << UF_USERINFO);
}
u->field_data[UF_USERINFO].len++;
break
;
default
:
break
;
}
s = new_s;
}
switch
(s) {
case
s_http_host_start:
case
s_http_host_v6_start:
case
s_http_host_v6:
case
s_http_host_port_start:
case
s_http_userinfo:
case
s_http_userinfo_start:
return
1;
default
:
break
;
}
return
0;
}
int
http_parser_parse_url(
const
char
*buf,
size_t
buflen,
int
is_connect,
struct
http_parser_url *u)
{
enum
state s;
const
char
*p;
enum
http_parser_url_fields uf, old_uf;
int
found_at = 0;
u->port = u->field_set = 0;
s = is_connect ? s_req_server_start : s_req_spaces_before_url;
uf = old_uf = UF_MAX;
for
(p = buf; p < buf + buflen; p++) {
s = parse_url_char(s, *p);
switch
(s) {
case
s_dead:
return
1;
case
s_req_schema_slash:
case
s_req_schema_slash_slash:
case
s_req_server_start:
case
s_req_query_string_start:
case
s_req_fragment_start:
continue
;
case
s_req_schema:
uf = UF_SCHEMA;
break
;
case
s_req_server_with_at:
found_at = 1;
case
s_req_server:
uf = UF_HOST;
break
;
case
s_req_path:
uf = UF_PATH;
break
;
case
s_req_query_string:
uf = UF_QUERY;
break
;
case
s_req_fragment:
uf = UF_FRAGMENT;
break
;
default
:
assert
(!
"Unexpected state"
);
return
1;
}
if
(uf == old_uf) {
u->field_data[uf].len++;
continue
;
}
u->field_data[uf].off = p - buf;
u->field_data[uf].len = 1;
u->field_set |= (1 << uf);
old_uf = uf;
}
if
((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) {
if
(http_parse_host(buf, u, found_at) != 0) {
return
1;
}
}
if
(is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) {
return
1;
}
if
(u->field_set & (1 << UF_PORT)) {
unsigned
long
v =
strtoul
(buf + u->field_data[UF_PORT].off, NULL, 10);
if
(v > 0xffff) {
return
1;
}
u->port = (uint16_t) v;
}
return
0;
}
void
http_parser_pause(http_parser *parser,
int
paused) {
if
(HTTP_PARSER_ERRNO(parser) == HPE_OK ||
HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) {
SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK);
}
else
{
assert
(0 &&
"Attempting to pause parser in error state"
);
}
}
int
http_body_is_final(
const
struct
http_parser *parser) {
return
parser->state == s_message_done;
}