Skip to content
This repository has been archived by the owner on Nov 6, 2022. It is now read-only.

Generic method #145

Closed
wants to merge 11 commits into from
68 changes: 57 additions & 11 deletions http_parser.c
Expand Up @@ -237,6 +237,9 @@ enum state

, s_start_req_or_res
, s_res_or_resp_H
, s_res_or_resp_HT
, s_res_or_resp_HTT
, s_res_or_resp_HTTP
, s_start_res
, s_res_H
, s_res_HT
Expand Down Expand Up @@ -459,6 +462,8 @@ parse_url_char(enum state s, const char ch)
return s_req_schema_slash;
}

return s_dead; /* handles the case of an invalid URI (no path, no colon
to mark the schema) */
break;

case s_req_schema_slash:
Expand Down Expand Up @@ -581,6 +586,7 @@ size_t http_parser_execute (http_parser *parser,
const char *header_value_mark = 0;
const char *url_mark = 0;
const char *body_mark = 0;
const char *method_mark = 0;

/* We're in an error state. Don't bother doing anything. */
if (HTTP_PARSER_ERRNO(parser) != HPE_OK) {
Expand Down Expand Up @@ -676,21 +682,50 @@ size_t http_parser_execute (http_parser *parser,

case s_res_or_resp_H:
if (ch == 'T') {
parser->type = HTTP_RESPONSE;
parser->state = s_res_HT;
parser->state = s_res_or_resp_HT;
} else {
if (ch != 'E') {
SET_ERRNO(HPE_INVALID_CONSTANT);
goto error;
parser->type = HTTP_REQUEST;
if (ch == 'E') {
parser->method = HTTP_HEAD;
} else {
parser->method = HTTP_GENERIC;
}

This comment was marked as off-topic.

This comment was marked as off-topic.

This comment was marked as off-topic.


parser->type = HTTP_REQUEST;
parser->method = HTTP_HEAD;
parser->index = 2;
parser->state = s_req_method;
}
break;

case s_res_or_resp_HT:
if (ch == 'T') {
parser->state = s_res_or_resp_HTT;
} else {
parser->type = HTTP_REQUEST;
parser->method = HTTP_GENERIC;
parser->state = s_req_method;
}
break;

case s_res_or_resp_HTT:
if (ch == 'P') {
parser->state = s_res_or_resp_HTTP;
} else {
parser->type = HTTP_REQUEST;
parser->method = HTTP_GENERIC;
parser->state = s_req_method;
}
break;

case s_res_or_resp_HTTP:
if (ch == '/') {
parser->state = s_res_first_http_major;
} else {
parser->type = HTTP_REQUEST;
parser->method = HTTP_GENERIC;
parser->state = s_req_method;
}
break;

case s_start_res:
{
parser->flags = 0;
Expand Down Expand Up @@ -866,6 +901,7 @@ size_t http_parser_execute (http_parser *parser,
case s_res_line_almost_done:
STRICT_CHECK(ch != LF);
parser->state = s_header_field_start;
CALLBACK_NOTIFY(status_complete);
break;

case s_start_req:
Expand All @@ -880,6 +916,7 @@ size_t http_parser_execute (http_parser *parser,
goto error;
}

MARK(method);
parser->method = (enum http_method) 0;
parser->index = 1;
switch (ch) {
Expand All @@ -899,8 +936,7 @@ size_t http_parser_execute (http_parser *parser,
case 'T': parser->method = HTTP_TRACE; break;
case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break;
default:
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
parser->method = HTTP_GENERIC; break;
}
parser->state = s_req_method;

Expand All @@ -917,8 +953,19 @@ size_t http_parser_execute (http_parser *parser,
goto error;
}

if (parser->method == HTTP_GENERIC) {
if (ch == ' ') {
CALLBACK_DATA(method);
parser->state = s_req_spaces_before_url;
}
break;
}

matcher = method_strings[parser->method];

/* TODO: parse full method before deciding it isn't generic */
if (ch == ' ' && matcher[parser->index] == '\0') {
CALLBACK_DATA(method);
parser->state = s_req_spaces_before_url;

This comment was marked as off-topic.

} else if (ch == matcher[parser->index]) {
; /* nada */
Expand Down Expand Up @@ -967,8 +1014,7 @@ size_t http_parser_execute (http_parser *parser,
} else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') {
parser->method = HTTP_PROPPATCH;
} else {
SET_ERRNO(HPE_INVALID_METHOD);
goto error;
parser->method = HTTP_GENERIC;
}

++parser->index;
Expand Down
5 changes: 5 additions & 0 deletions http_parser.h
Expand Up @@ -109,6 +109,7 @@ typedef int (*http_cb) (http_parser*);
/* RFC-5789 */ \
XX(24, PATCH, PATCH) \
XX(25, PURGE, PURGE) \
XX(26, GENERIC, GENERIC) \

enum http_method
{
Expand Down Expand Up @@ -142,12 +143,14 @@ enum flags
\
/* Callback-related errors */ \
XX(CB_message_begin, "the on_message_begin callback failed") \
XX(CB_status_complete, "the on_status_complete callback failed") \
XX(CB_url, "the on_url callback failed") \
XX(CB_header_field, "the on_header_field callback failed") \
XX(CB_header_value, "the on_header_value callback failed") \
XX(CB_headers_complete, "the on_headers_complete callback failed") \
XX(CB_body, "the on_body callback failed") \
XX(CB_message_complete, "the on_message_complete callback failed") \
XX(CB_method, "the on_method callback failed") \
\
/* Parsing-related errors */ \
XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \
Expand Down Expand Up @@ -221,7 +224,9 @@ struct http_parser {

struct http_parser_settings {
http_cb on_message_begin;
http_data_cb on_method;
http_data_cb on_url;
http_cb on_status_complete;
http_data_cb on_header_field;
http_data_cb on_header_value;
http_cb on_headers_complete;
Expand Down
49 changes: 45 additions & 4 deletions test.c
Expand Up @@ -897,6 +897,24 @@ const struct message requests[] =
,.body= ""
}

#define GENERIC_METHOD 34
, {.name= "use a generic extension method"
,.type= HTTP_REQUEST
,.raw= "LOOKOVERHERE http://example.com/ HTTP/1.1\r\n"
"\r\n"
,.should_keep_alive= TRUE
,.message_complete_on_eof= TRUE
,.http_major= 1
,.http_minor= 1
,.method= HTTP_GENERIC
,.fragment= ""
,.request_path= "/"
,.request_url= "http://example.com/"
,.host= "example.com"
,.num_headers= 0
,.headers= { }
,.body= ""
}

, {.name= NULL } /* sentinel */
};
Expand Down Expand Up @@ -1491,6 +1509,13 @@ request_url_cb (http_parser *p, const char *buf, size_t len)
return 0;
}

int
status_complete_cb (http_parser *p) {
assert(p == parser);
p->data++;
return 0;
}

int
header_field_cb (http_parser *p, const char *buf, size_t len)
{
Expand Down Expand Up @@ -3089,6 +3114,20 @@ create_large_chunked_message (int body_size_in_kb, const char* headers)
return buf;
}

void
test_status_complete (void)
{
parser_init(HTTP_RESPONSE);
parser->data = 0;
http_parser_settings settings = settings_null;
settings.on_status_complete = status_complete_cb;

char *response = "don't mind me, just a simple response";
http_parser_execute(parser, &settings, response, strlen(response));
assert(parser->data == (void*)0); // the status_complete callback was never called
assert(parser->http_errno == HPE_INVALID_CONSTANT); // the errno for an invalid status line
}

/* Verify that we can pause parsing at any of the bytes in the
* message and still get the result that we're expecting. */
void
Expand Down Expand Up @@ -3242,13 +3281,13 @@ main (void)

/// REQUESTS

test_simple("hello world", HPE_INVALID_METHOD);
test_simple("hello world", HPE_OK);
test_simple("GET / HTP/1.1\r\n\r\n", HPE_INVALID_VERSION);


test_simple("ASDF / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD);
test_simple("PROPPATCHA / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD);
test_simple("GETA / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD);
test_simple("ASDF / HTTP/1.1\r\n\r\n", HPE_OK);
test_simple("PROPPATCHA / HTTP/1.1\r\n\r\n", HPE_OK);
test_simple("GETA / HTTP/1.1\r\n\r\n", HPE_OK);

// Well-formed but incomplete
test_simple("GET / HTTP/1.1\r\n"
Expand Down Expand Up @@ -3396,6 +3435,8 @@ main (void)
, &requests[CONNECT_REQUEST]
);

test_status_complete();

puts("requests okay");

return 0;
Expand Down