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_start_req_or_res
, s_res_or_resp_H , s_res_or_resp_H
, s_res_or_resp_HT
, s_res_or_resp_HTT
, s_res_or_resp_HTTP
, s_start_res , s_start_res
, s_res_H , s_res_H
, s_res_HT , 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_req_schema_slash;
} }


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


case s_req_schema_slash: 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 *header_value_mark = 0;
const char *url_mark = 0; const char *url_mark = 0;
const char *body_mark = 0; const char *body_mark = 0;
const char *method_mark = 0;


/* We're in an error state. Don't bother doing anything. */ /* We're in an error state. Don't bother doing anything. */
if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { 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: case s_res_or_resp_H:
if (ch == 'T') { if (ch == 'T') {
parser->type = HTTP_RESPONSE; parser->state = s_res_or_resp_HT;
parser->state = s_res_HT;
} else { } else {
if (ch != 'E') { parser->type = HTTP_REQUEST;
SET_ERRNO(HPE_INVALID_CONSTANT); if (ch == 'E') {
goto error; 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->index = 2;
parser->state = s_req_method; parser->state = s_req_method;
} }
break; 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: case s_start_res:
{ {
parser->flags = 0; 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: case s_res_line_almost_done:
STRICT_CHECK(ch != LF); STRICT_CHECK(ch != LF);
parser->state = s_header_field_start; parser->state = s_header_field_start;
CALLBACK_NOTIFY(status_complete);
break; break;


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


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


Expand All @@ -917,8 +953,19 @@ size_t http_parser_execute (http_parser *parser,
goto error; 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]; matcher = method_strings[parser->method];

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

This comment was marked as off-topic.

} else if (ch == matcher[parser->index]) { } else if (ch == matcher[parser->index]) {
; /* nada */ ; /* 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') { } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') {
parser->method = HTTP_PROPPATCH; parser->method = HTTP_PROPPATCH;
} else { } else {
SET_ERRNO(HPE_INVALID_METHOD); parser->method = HTTP_GENERIC;
goto error;
} }


++parser->index; ++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 */ \ /* RFC-5789 */ \
XX(24, PATCH, PATCH) \ XX(24, PATCH, PATCH) \
XX(25, PURGE, PURGE) \ XX(25, PURGE, PURGE) \
XX(26, GENERIC, GENERIC) \


enum http_method enum http_method
{ {
Expand Down Expand Up @@ -142,12 +143,14 @@ enum flags
\ \
/* Callback-related errors */ \ /* Callback-related errors */ \
XX(CB_message_begin, "the on_message_begin callback failed") \ 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_url, "the on_url callback failed") \
XX(CB_header_field, "the on_header_field callback failed") \ XX(CB_header_field, "the on_header_field callback failed") \
XX(CB_header_value, "the on_header_value callback failed") \ XX(CB_header_value, "the on_header_value callback failed") \
XX(CB_headers_complete, "the on_headers_complete callback failed") \ XX(CB_headers_complete, "the on_headers_complete callback failed") \
XX(CB_body, "the on_body callback failed") \ XX(CB_body, "the on_body callback failed") \
XX(CB_message_complete, "the on_message_complete callback failed") \ XX(CB_message_complete, "the on_message_complete callback failed") \
XX(CB_method, "the on_method callback failed") \
\ \
/* Parsing-related errors */ \ /* Parsing-related errors */ \
XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ 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 { struct http_parser_settings {
http_cb on_message_begin; http_cb on_message_begin;
http_data_cb on_method;
http_data_cb on_url; http_data_cb on_url;
http_cb on_status_complete;
http_data_cb on_header_field; http_data_cb on_header_field;
http_data_cb on_header_value; http_data_cb on_header_value;
http_cb on_headers_complete; 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= "" ,.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 */ , {.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; return 0;
} }


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

int int
header_field_cb (http_parser *p, const char *buf, size_t len) 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; 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 /* Verify that we can pause parsing at any of the bytes in the
* message and still get the result that we're expecting. */ * message and still get the result that we're expecting. */
void void
Expand Down Expand Up @@ -3242,13 +3281,13 @@ main (void)


/// REQUESTS /// 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("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("ASDF / HTTP/1.1\r\n\r\n", HPE_OK);
test_simple("PROPPATCHA / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD); test_simple("PROPPATCHA / HTTP/1.1\r\n\r\n", HPE_OK);
test_simple("GETA / HTTP/1.1\r\n\r\n", HPE_INVALID_METHOD); test_simple("GETA / HTTP/1.1\r\n\r\n", HPE_OK);


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


test_status_complete();

puts("requests okay"); puts("requests okay");


return 0; return 0;
Expand Down