in Sources/CNIOHTTPParser/c_nio_http_parser.c [644:2158]
size_t c_nio_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;
const char *status_mark = 0;
enum state p_state = (enum state) parser->state;
const unsigned int lenient = parser->lenient_http_headers;
const unsigned int allow_chunked_length = parser->allow_chunked_length;
uint32_t nread = parser->nread;
/* We're in an error state. Don't bother doing anything. */
if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
return 0;
}
if (len == 0) {
switch (CURRENT_STATE()) {
case s_body_identity_eof:
/* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if
* we got paused.
*/
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 (CURRENT_STATE() == s_header_field)
header_field_mark = data;
if (CURRENT_STATE() == s_header_value)
header_value_mark = data;
switch (CURRENT_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;
case s_res_status:
status_mark = data;
break;
default:
break;
}
for (p=data; p != data + len; p++) {
ch = *p;
if (PARSING_HEADER(CURRENT_STATE()))
COUNT_HEADER_SIZE(1);
reexecute:
switch (CURRENT_STATE()) {
case s_dead:
/* this state is used after a 'Connection: close' message
* the parser will error out if it reads another message
*/
if (LIKELY(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->uses_transfer_encoding = 0;
parser->content_length = ULLONG_MAX;
if (ch == 'H') {
UPDATE_STATE(s_res_or_resp_H);
CALLBACK_NOTIFY(message_begin);
} else {
parser->type = HTTP_REQUEST;
UPDATE_STATE(s_start_req);
REEXECUTE();
}
break;
}
case s_res_or_resp_H:
if (ch == 'T') {
parser->type = HTTP_RESPONSE;
UPDATE_STATE(s_res_HT);
} else {
if (UNLIKELY(ch != 'E')) {
SET_ERRNO(HPE_INVALID_CONSTANT);
goto error;
}
parser->type = HTTP_REQUEST;
parser->method = HTTP_HEAD;
parser->index = 2;
UPDATE_STATE(s_req_method);
}
break;
case s_start_res:
{
if (ch == CR || ch == LF)
break;
parser->flags = 0;
parser->uses_transfer_encoding = 0;
parser->content_length = ULLONG_MAX;
if (ch == 'H') {
UPDATE_STATE(s_res_H);
} else {
SET_ERRNO(HPE_INVALID_CONSTANT);
goto error;
}
CALLBACK_NOTIFY(message_begin);
break;
}
case s_res_H:
STRICT_CHECK(ch != 'T');
UPDATE_STATE(s_res_HT);
break;
case s_res_HT:
STRICT_CHECK(ch != 'T');
UPDATE_STATE(s_res_HTT);
break;
case s_res_HTT:
STRICT_CHECK(ch != 'P');
UPDATE_STATE(s_res_HTTP);
break;
case s_res_HTTP:
STRICT_CHECK(ch != '/');
UPDATE_STATE(s_res_http_major);
break;
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_dot);
break;
case s_res_http_dot:
{
if (UNLIKELY(ch != '.')) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
UPDATE_STATE(s_res_http_minor);
break;
}
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_end);
break;
case s_res_http_end:
{
if (UNLIKELY(ch != ' ')) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
UPDATE_STATE(s_res_first_status_code);
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';
UPDATE_STATE(s_res_status_code);
break;
}
case s_res_status_code:
{
if (!IS_NUM(ch)) {
switch (ch) {
case ' ':
UPDATE_STATE(s_res_status_start);
break;
case CR:
case LF:
UPDATE_STATE(s_res_status_start);
REEXECUTE();
break;
default:
SET_ERRNO(HPE_INVALID_STATUS);
goto error;
}
break;
}
parser->status_code *= 10;
parser->status_code += ch - '0';
if (UNLIKELY(parser->status_code > 999)) {
SET_ERRNO(HPE_INVALID_STATUS);
goto error;
}
break;
}
case s_res_status_start:
{
MARK(status);
UPDATE_STATE(s_res_status);
parser->index = 0;
if (ch == CR || ch == LF)
REEXECUTE();
break;
}
case s_res_status:
if (ch == CR) {
UPDATE_STATE(s_res_line_almost_done);
CALLBACK_DATA(status);
break;
}
if (ch == LF) {
UPDATE_STATE(s_header_field_start);
CALLBACK_DATA(status);
break;
}
break;
case s_res_line_almost_done:
STRICT_CHECK(ch != LF);
UPDATE_STATE(s_header_field_start);
break;
case s_start_req:
{
if (ch == CR || ch == LF)
break;
parser->flags = 0;
parser->uses_transfer_encoding = 0;
parser->content_length = ULLONG_MAX;
if (UNLIKELY(!IS_ALPHA(ch))) {
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
}
parser->method = (enum http_method) 0;
parser->index = 1;
switch (ch) {
case 'A': parser->method = HTTP_ACL; break;
case 'B': parser->method = HTTP_BIND; break;
case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ 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; /* or LINK */ break;
case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break;
case 'N': parser->method = HTTP_NOTIFY; break;
case 'O': parser->method = HTTP_OPTIONS; break;
case 'P': parser->method = HTTP_POST;
/* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */
break;
case 'R': parser->method = HTTP_REPORT; /* or REBIND */ 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:
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
}
UPDATE_STATE(s_req_method);
CALLBACK_NOTIFY(message_begin);
break;
}
case s_req_method:
{
const char *matcher;
if (UNLIKELY(ch == '\0')) {
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
}
matcher = method_strings[parser->method];
if (ch == ' ' && matcher[parser->index] == '\0') {
UPDATE_STATE(s_req_spaces_before_url);
} else if (ch == matcher[parser->index]) {
; /* nada */
} else if ((ch >= 'A' && ch <= 'Z') || ch == '-') {
switch (parser->method << 16 | parser->index << 8 | ch) {
#define XX(meth, pos, ch, new_meth) \
case (HTTP_##meth << 16 | pos << 8 | ch): \
parser->method = HTTP_##new_meth; break;
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(PROPFIND, 4, 'P', PROPPATCH)
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 {
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) {
UPDATE_STATE(s_req_server_start);
}
UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
if (UNLIKELY(CURRENT_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) {
/* No whitespace allowed here */
case ' ':
case CR:
case LF:
SET_ERRNO(HPE_INVALID_URL);
goto error;
default:
UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
if (UNLIKELY(CURRENT_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 ' ':
UPDATE_STATE(s_req_http_start);
CALLBACK_DATA(url);
break;
case CR:
case LF:
parser->http_major = 0;
parser->http_minor = 9;
UPDATE_STATE((ch == CR) ?
s_req_line_almost_done :
s_header_field_start);
CALLBACK_DATA(url);
break;
default:
UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch));
if (UNLIKELY(CURRENT_STATE() == s_dead)) {
SET_ERRNO(HPE_INVALID_URL);
goto error;
}
}
break;
}
case s_req_http_start:
switch (ch) {
case ' ':
break;
case 'H':
UPDATE_STATE(s_req_http_H);
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;
}
break;
case s_req_http_H:
STRICT_CHECK(ch != 'T');
UPDATE_STATE(s_req_http_HT);
break;
case s_req_http_HT:
STRICT_CHECK(ch != 'T');
UPDATE_STATE(s_req_http_HTT);
break;
case s_req_http_HTT:
STRICT_CHECK(ch != 'P');
UPDATE_STATE(s_req_http_HTTP);
break;
case s_req_http_I:
STRICT_CHECK(ch != 'C');
UPDATE_STATE(s_req_http_IC);
break;
case s_req_http_IC:
STRICT_CHECK(ch != 'E');
UPDATE_STATE(s_req_http_HTTP); /* Treat "ICE" as "HTTP". */
break;
case s_req_http_HTTP:
STRICT_CHECK(ch != '/');
UPDATE_STATE(s_req_http_major);
break;
case s_req_http_major:
if (UNLIKELY(!IS_NUM(ch))) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
parser->http_major = ch - '0';
UPDATE_STATE(s_req_http_dot);
break;
case s_req_http_dot:
{
if (UNLIKELY(ch != '.')) {
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
}
UPDATE_STATE(s_req_http_minor);
break;
}
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_end);
break;
case s_req_http_end:
{
if (ch == CR) {
UPDATE_STATE(s_req_line_almost_done);
break;
}
if (ch == LF) {
UPDATE_STATE(s_header_field_start);
break;
}
SET_ERRNO(HPE_INVALID_VERSION);
goto error;
break;
}
/* end of request line */
case s_req_line_almost_done:
{
if (UNLIKELY(ch != LF)) {
SET_ERRNO(HPE_LF_EXPECTED);
goto error;
}
UPDATE_STATE(s_header_field_start);
break;
}
case s_header_field_start:
{
if (ch == CR) {
UPDATE_STATE(s_headers_almost_done);
break;
}
if (ch == LF) {
/* they might be just sending \n instead of \r\n so this would be
* the second \n to denote the end of headers*/
UPDATE_STATE(s_headers_almost_done);
REEXECUTE();
}
c = TOKEN(ch);
if (UNLIKELY(!c)) {
SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
goto error;
}
MARK(header_field);
parser->index = 0;
UPDATE_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:
{
const char* start = p;
for (; p != data + len; p++) {
ch = *p;
c = TOKEN(ch);
if (!c)
break;
switch (parser->header_state) {
case h_general: {
size_t left = data + len - p;
const char* pe = p + MIN(left, max_header_size);
while (p+1 < pe && TOKEN(p[1])) {
p++;
}
break;
}
case h_C:
parser->index++;
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;
/* connection */
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;
/* proxy-connection */
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;
/* content-length */
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;
/* transfer-encoding */
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;
parser->uses_transfer_encoding = 1;
}
break;
/* upgrade */
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;
}
}
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);
break;
}
SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
goto error;
}
case s_header_value_discard_ws:
if (ch == ' ' || ch == '\t') break;
if (ch == CR) {
UPDATE_STATE(s_header_value_discard_ws_almost_done);
break;
}
if (ch == LF) {
UPDATE_STATE(s_header_value_discard_lws);
break;
}
/* fall through */
case s_header_value_start:
{
MARK(header_value);
UPDATE_STATE(s_header_value);
parser->index = 0;
c = LOWER(ch);
switch (parser->header_state) {
case h_upgrade:
parser->flags |= F_UPGRADE;
parser->header_state = h_general;
break;
case h_transfer_encoding:
/* looking for 'Transfer-Encoding: chunked' */
if ('c' == c) {
parser->header_state = h_matching_transfer_encoding_chunked;
} else {
parser->header_state = h_matching_transfer_encoding_token;
}
break;
/* Multi-value `Transfer-Encoding` header */
case h_matching_transfer_encoding_token_start:
break;
case h_content_length:
if (UNLIKELY(!IS_NUM(ch))) {
SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
goto error;
}
if (parser->flags & F_CONTENTLENGTH) {
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;
/* 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:
/* looking for 'Connection: keep-alive' */
if (c == 'k') {
parser->header_state = h_matching_connection_keep_alive;
/* looking for 'Connection: close' */
} else if (c == 'c') {
parser->header_state = h_matching_connection_close;
} else if (c == 'u') {
parser->header_state = h_matching_connection_upgrade;
} else {
parser->header_state = h_matching_connection_token;
}
break;
/* Multi-value `Connection` header */
case h_matching_connection_token_start:
break;
default:
parser->header_state = h_general;
break;
}
break;
}
case s_header_value:
{
const char* start = p;
enum header_states h_state = (enum header_states) parser->header_state;
for (; p != data + len; p++) {
ch = *p;
if (ch == CR) {
UPDATE_STATE(s_header_almost_done);
parser->header_state = h_state;
CALLBACK_DATA(header_value);
break;
}
if (ch == LF) {
UPDATE_STATE(s_header_almost_done);
COUNT_HEADER_SIZE(p - start);
parser->header_state = h_state;
CALLBACK_DATA_NOADVANCE(header_value);
REEXECUTE();
}
if (!lenient && !IS_HEADER_CHAR(ch)) {
SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
goto error;
}
c = LOWER(ch);
switch (h_state) {
case h_general:
{
size_t left = data + len - p;
const char* pe = p + MIN(left, max_header_size);
for (; p != pe; p++) {
ch = *p;
if (ch == CR || ch == LF) {
--p;
break;
}
if (!lenient && !IS_HEADER_CHAR(ch)) {
SET_ERRNO(HPE_INVALID_HEADER_TOKEN);
goto error;
}
}
if (p == data + len)
--p;
break;
}
case h_connection:
case h_transfer_encoding:
assert(0 && "Shouldn't get here.");
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 == ' ') {
h_state = h_content_length_ws;
break;
}
if (UNLIKELY(!IS_NUM(ch))) {
SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
parser->header_state = h_state;
goto error;
}
t = parser->content_length;
t *= 10;
t += ch - '0';
/* Overflow? Test against a conservative limit for simplicity. */
if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) {
SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
parser->header_state = h_state;
goto error;
}
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_token_start:
/* looking for 'Transfer-Encoding: chunked' */
if ('c' == c) {
h_state = h_matching_transfer_encoding_chunked;
} else if (STRICT_TOKEN(c)) {
/* TODO(indutny): similar code below does this, but why?
* At the very least it seems to be inconsistent given that
* h_matching_transfer_encoding_token does not check for
* `STRICT_TOKEN`
*/
h_state = h_matching_transfer_encoding_token;
} else if (c == ' ' || c == '\t') {
/* Skip lws */
} else {
h_state = h_general;
}
break;
case h_matching_transfer_encoding_chunked:
parser->index++;
if (parser->index > sizeof(CHUNKED)-1
|| c != CHUNKED[parser->index]) {
h_state = h_matching_transfer_encoding_token;
} else if (parser->index == sizeof(CHUNKED)-2) {
h_state = h_transfer_encoding_chunked;
}
break;
case h_matching_transfer_encoding_token:
if (ch == ',') {
h_state = h_matching_transfer_encoding_token_start;
parser->index = 0;
}
break;
case h_matching_connection_token_start:
/* looking for 'Connection: keep-alive' */
if (c == 'k') {
h_state = h_matching_connection_keep_alive;
/* looking for 'Connection: close' */
} else if (c == 'c') {
h_state = h_matching_connection_close;
} else if (c == 'u') {
h_state = h_matching_connection_upgrade;
} else if (STRICT_TOKEN(c)) {
h_state = h_matching_connection_token;
} else if (c == ' ' || c == '\t') {
/* Skip lws */
} else {
h_state = h_general;
}
break;
/* looking for 'Connection: keep-alive' */
case h_matching_connection_keep_alive:
parser->index++;
if (parser->index > sizeof(KEEP_ALIVE)-1
|| c != KEEP_ALIVE[parser->index]) {
h_state = h_matching_connection_token;
} else if (parser->index == sizeof(KEEP_ALIVE)-2) {
h_state = h_connection_keep_alive;
}
break;
/* looking for 'Connection: close' */
case h_matching_connection_close:
parser->index++;
if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) {
h_state = h_matching_connection_token;
} else if (parser->index == sizeof(CLOSE)-2) {
h_state = h_connection_close;
}
break;
/* looking for 'Connection: upgrade' */
case h_matching_connection_upgrade:
parser->index++;
if (parser->index > sizeof(UPGRADE) - 1 ||
c != UPGRADE[parser->index]) {
h_state = h_matching_connection_token;
} else if (parser->index == sizeof(UPGRADE)-2) {
h_state = h_connection_upgrade;
}
break;
case h_matching_connection_token:
if (ch == ',') {
h_state = h_matching_connection_token_start;
parser->index = 0;
}
break;
case h_transfer_encoding_chunked:
if (ch != ' ') h_state = h_matching_transfer_encoding_token;
break;
case h_connection_keep_alive:
case h_connection_close:
case h_connection_upgrade:
if (ch == ',') {
if (h_state == h_connection_keep_alive) {
parser->flags |= F_CONNECTION_KEEP_ALIVE;
} else if (h_state == h_connection_close) {
parser->flags |= F_CONNECTION_CLOSE;
} else if (h_state == h_connection_upgrade) {
parser->flags |= F_CONNECTION_UPGRADE;
}
h_state = h_matching_connection_token_start;
parser->index = 0;
} else if (ch != ' ') {
h_state = h_matching_connection_token;
}
break;
default:
UPDATE_STATE(s_header_value);
h_state = h_general;
break;
}
}
parser->header_state = h_state;
if (p == data + len)
--p;
COUNT_HEADER_SIZE(p - start);
break;
}
case s_header_almost_done:
{
if (UNLIKELY(ch != LF)) {
SET_ERRNO(HPE_LF_EXPECTED);
goto error;
}
UPDATE_STATE(s_header_value_lws);
break;
}
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();
}
/* finished the header */
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;
case h_connection_upgrade:
parser->flags |= F_CONNECTION_UPGRADE;
break;
default:
break;
}
UPDATE_STATE(s_header_field_start);
REEXECUTE();
}
case s_header_value_discard_ws_almost_done:
{
STRICT_CHECK(ch != LF);
UPDATE_STATE(s_header_value_discard_lws);
break;
}
case s_header_value_discard_lws:
{
if (ch == ' ' || ch == '\t') {
UPDATE_STATE(s_header_value_discard_ws);
break;
} else {
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_connection_upgrade:
parser->flags |= F_CONNECTION_UPGRADE;
break;
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;
}
/* header value was empty */
MARK(header_value);
UPDATE_STATE(s_header_field_start);
CALLBACK_DATA_NOADVANCE(header_value);
REEXECUTE();
}
}
case s_headers_almost_done:
{
STRICT_CHECK(ch != LF);
if (parser->flags & F_TRAILING) {
/* End of a chunked request */
UPDATE_STATE(s_message_done);
CALLBACK_NOTIFY_NOADVANCE(chunk_complete);
REEXECUTE();
}
/* Cannot use transfer-encoding and a content-length header together
per the HTTP specification. (RFC 7230 Section 3.3.3) */
if ((parser->uses_transfer_encoding == 1) &&
(parser->flags & F_CONTENTLENGTH)) {
/* Allow it for lenient parsing as long as `Transfer-Encoding` is
* not `chunked` or allow_length_with_encoding is set
*/
if (parser->flags & F_CHUNKED) {
if (!allow_chunked_length) {
SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
goto error;
}
} else if (!lenient) {
SET_ERRNO(HPE_UNEXPECTED_CONTENT_LENGTH);
goto error;
}
}
UPDATE_STATE(s_headers_done);
/* Set this here so that on_headers_complete() callbacks can see it */
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
* will interpret that as saying that this message has no body. This
* is needed for the annoying case of recieving a response to a HEAD
* request.
*
* We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so
* we have to simulate it by handling a change in errno below.
*/
if (settings->on_headers_complete) {
switch (settings->on_headers_complete(parser)) {
case 0:
break;
case 2:
parser->upgrade = 1;
/* fall through */
case 1:
parser->flags |= F_SKIPBODY;
break;
default:
SET_ERRNO(HPE_CB_headers_complete);
RETURN(p - data); /* Error */
}
}
if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
RETURN(p - data);
}
REEXECUTE();
}
case s_headers_done:
{
int hasBody;
STRICT_CHECK(ch != LF);
parser->nread = 0;
nread = 0;
hasBody = parser->flags & F_CHUNKED ||
(parser->content_length > 0 && parser->content_length != ULLONG_MAX);
if (parser->upgrade && (parser->method == HTTP_CONNECT ||
(parser->flags & F_SKIPBODY) || !hasBody)) {
/* Exit, the rest of the message is in a different protocol. */
UPDATE_STATE(NEW_MESSAGE());
CALLBACK_NOTIFY(message_complete);
RETURN((p - data) + 1);
}
if (parser->flags & F_SKIPBODY) {
UPDATE_STATE(NEW_MESSAGE());
CALLBACK_NOTIFY(message_complete);
} else if (parser->flags & F_CHUNKED) {
/* chunked encoding - ignore Content-Length header,
* prepare for a chunk */
UPDATE_STATE(s_chunk_size_start);
} else if (parser->uses_transfer_encoding == 1) {
if (parser->type == HTTP_REQUEST && !lenient) {
/* RFC 7230 3.3.3 */
/* If a Transfer-Encoding header field
* is present in a request and the chunked transfer coding is not
* the final encoding, the message body length cannot be determined
* reliably; the server MUST respond with the 400 (Bad Request)
* status code and then close the connection.
*/
SET_ERRNO(HPE_INVALID_TRANSFER_ENCODING);
RETURN(p - data); /* Error */
} else {
/* RFC 7230 3.3.3 */
/* If a Transfer-Encoding header field is present in a response and
* the chunked transfer coding is not the final encoding, the
* message body length is determined by reading the connection until
* it is closed by the server.
*/
UPDATE_STATE(s_body_identity_eof);
}
} else {
if (parser->content_length == 0) {
/* Content-Length header given but zero: Content-Length: 0\r\n */
UPDATE_STATE(NEW_MESSAGE());
CALLBACK_NOTIFY(message_complete);
} else if (parser->content_length != ULLONG_MAX) {
/* Content-Length header given and non-zero */
UPDATE_STATE(s_body_identity);
} else {
if (!c_nio_http_message_needs_eof(parser)) {
/* Assume content-length 0 - read the next */
UPDATE_STATE(NEW_MESSAGE());
CALLBACK_NOTIFY(message_complete);
} else {
/* Read body until EOF */
UPDATE_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);
/* The difference between advancing content_length and p is because
* the latter will automaticaly advance on the next loop iteration.
* Further, if content_length ends up at 0, we want to see the last
* byte again for our message complete callback.
*/
MARK(body);
parser->content_length -= to_read;
p += to_read - 1;
if (parser->content_length == 0) {
UPDATE_STATE(s_message_done);
/* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte.
*
* The alternative to doing this is to wait for the next byte to
* trigger the data callback, just as in every other case. The
* problem with this is that this makes it difficult for the test
* harness to distinguish between complete-on-EOF and
* complete-on-length. It's not clear that this distinction is
* important for applications, but let's keep it for now.
*/
CALLBACK_DATA_(body, p - body_mark + 1, p - data);
REEXECUTE();
}
break;
}
/* read until EOF */
case s_body_identity_eof:
MARK(body);
p = data + len - 1;
break;
case s_message_done:
UPDATE_STATE(NEW_MESSAGE());
CALLBACK_NOTIFY(message_complete);
if (parser->upgrade) {
/* Exit, the rest of the message is in a different protocol. */
RETURN((p - data) + 1);
}
break;
case s_chunk_size_start:
{
assert(nread == 1);
assert(parser->flags & F_CHUNKED);
unhex_val = unhex[(unsigned char)ch];
if (UNLIKELY(unhex_val == -1)) {
SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
goto error;
}
parser->content_length = unhex_val;
UPDATE_STATE(s_chunk_size);
break;
}
case s_chunk_size:
{
uint64_t t;
assert(parser->flags & F_CHUNKED);
if (ch == CR) {
UPDATE_STATE(s_chunk_size_almost_done);
break;
}
unhex_val = unhex[(unsigned char)ch];
if (unhex_val == -1) {
if (ch == ';' || ch == ' ') {
UPDATE_STATE(s_chunk_parameters);
break;
}
SET_ERRNO(HPE_INVALID_CHUNK_SIZE);
goto error;
}
t = parser->content_length;
t *= 16;
t += unhex_val;
/* Overflow? Test against a conservative limit for simplicity. */
if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) {
SET_ERRNO(HPE_INVALID_CONTENT_LENGTH);
goto error;
}
parser->content_length = t;
break;
}
case s_chunk_parameters:
{
assert(parser->flags & F_CHUNKED);
/* just ignore this shit. TODO check for overflow */
if (ch == CR) {
UPDATE_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;
nread = 0;
if (parser->content_length == 0) {
parser->flags |= F_TRAILING;
UPDATE_STATE(s_header_field_start);
} else {
UPDATE_STATE(s_chunk_data);
}
CALLBACK_NOTIFY(chunk_header);
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);
/* See the explanation in s_body_identity for why the content
* length and data pointers are managed this way.
*/
MARK(body);
parser->content_length -= to_read;
p += to_read - 1;
if (parser->content_length == 0) {
UPDATE_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);
UPDATE_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;
nread = 0;
UPDATE_STATE(s_chunk_size_start);
CALLBACK_NOTIFY(chunk_complete);
break;
default:
assert(0 && "unhandled state");
SET_ERRNO(HPE_INVALID_INTERNAL_STATE);
goto error;
}
}
/* 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).
*
* We use the NOADVANCE() variety of callbacks here because 'p' has already
* overflowed 'data' and this allows us to correct for the off-by-one that
* we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p'
* value that's in-bounds).
*/
assert(((header_field_mark ? 1 : 0) +
(header_value_mark ? 1 : 0) +
(url_mark ? 1 : 0) +
(body_mark ? 1 : 0) +
(status_mark ? 1 : 0)) <= 1);
CALLBACK_DATA_NOADVANCE(header_field);
CALLBACK_DATA_NOADVANCE(header_value);
CALLBACK_DATA_NOADVANCE(url);
CALLBACK_DATA_NOADVANCE(body);
CALLBACK_DATA_NOADVANCE(status);
RETURN(len);
error:
if (HTTP_PARSER_ERRNO(parser) == HPE_OK) {
SET_ERRNO(HPE_UNKNOWN);
}
RETURN(p - data);
}