From 76f0f1690fb4ea80e655327a96c7549f4c1fdf5d Mon Sep 17 00:00:00 2001 From: David Wragg Date: Thu, 28 Nov 2013 16:17:31 +0000 Subject: [PATCH] Fix issues around multi-line headers Always discard leading whitespace in a header value, even if it is folded. Pay attention to values of interesting headers (Connection, Content-Length, etc.) even when they come on a continuation line. Add a test case to check that requests and responses using only LF to separate lines are handled correctly. --- http_parser.c | 60 +++++++++++++++++++++++++++++++++++---------------- test.c | 50 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 89 insertions(+), 21 deletions(-) diff --git a/http_parser.c b/http_parser.c index a0049b6..70cc9bd 100644 --- a/http_parser.c +++ b/http_parser.c @@ -281,6 +281,8 @@ enum state , s_header_field_start , s_header_field , s_header_value_discard_ws + , s_header_value_discard_ws_almost_done + , s_header_value_discard_lws , s_header_value_start , s_header_value , s_header_value_lws @@ -1404,28 +1406,26 @@ size_t http_parser_execute (http_parser *parser, case s_header_value_discard_ws: if (ch == ' ' || ch == '\t') break; - /* FALLTHROUGH */ - - case s_header_value_start: - { - 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); + parser->state = s_header_value_discard_ws_almost_done; break; } if (ch == LF) { - parser->state = s_header_field_start; - CALLBACK_DATA(header_value); + parser->state = s_header_value_discard_lws; break; } + /* FALLTHROUGH */ + + case s_header_value_start: + { + MARK(header_value); + + parser->state = s_header_value; + parser->index = 0; + c = LOWER(ch); switch (parser->header_state) { @@ -1573,7 +1573,17 @@ size_t http_parser_execute (http_parser *parser, STRICT_CHECK(ch != LF); parser->state = s_header_value_lws; + break; + } + case s_header_value_lws: + { + if (ch == ' ' || ch == '\t') { + parser->state = s_header_value_start; + goto reexecute_byte; + } + + /* finished the header */ switch (parser->header_state) { case h_connection_keep_alive: parser->flags |= F_CONNECTION_KEEP_ALIVE; @@ -1588,17 +1598,29 @@ size_t http_parser_execute (http_parser *parser, break; } + parser->state = s_header_field_start; + goto reexecute_byte; + } + + case s_header_value_discard_ws_almost_done: + { + STRICT_CHECK(ch != LF); + parser->state = s_header_value_discard_lws; break; } - case s_header_value_lws: + case s_header_value_discard_lws: { - if (ch == ' ' || ch == '\t') - parser->state = s_header_value_start; - else + if (ch == ' ' || ch == '\t') { + parser->state = s_header_value_discard_ws; + break; + } else { + /* header value was empty */ + MARK(header_value); parser->state = s_header_field_start; - - goto reexecute_byte; + CALLBACK_DATA_NOADVANCE(header_value); + goto reexecute_byte; + } } case s_headers_almost_done: diff --git a/test.c b/test.c index fdeae86..9799dc6 100644 --- a/test.c +++ b/test.c @@ -608,8 +608,14 @@ const struct message requests[] = " mno \r\n" "\t \tqrs\r\n" "Line2: \t line2\t\r\n" + "Line3:\r\n" + " line3\r\n" + "Line4: \r\n" + " \r\n" + "Connection:\r\n" + " close\r\n" "\r\n" - ,.should_keep_alive= TRUE + ,.should_keep_alive= FALSE ,.message_complete_on_eof= FALSE ,.http_major= 1 ,.http_minor= 1 @@ -618,9 +624,12 @@ const struct message requests[] = ,.fragment= "" ,.request_path= "/" ,.request_url= "/" - ,.num_headers= 2 + ,.num_headers= 5 ,.headers= { { "Line1", "abc\tdef ghi\t\tjkl mno \t \tqrs" } , { "Line2", "line2\t" } + , { "Line3", "line3" } + , { "Line4", "" } + , { "Connection", "close" }, } ,.body= "" } @@ -904,6 +913,43 @@ const struct message requests[] = ,.body= "" } +#define LINE_FOLDING_IN_HEADER_WITH_LF 34 +, {.name= "line folding in header value" + ,.type= HTTP_REQUEST + ,.raw= "GET / HTTP/1.1\n" + "Line1: abc\n" + "\tdef\n" + " ghi\n" + "\t\tjkl\n" + " mno \n" + "\t \tqrs\n" + "Line2: \t line2\t\n" + "Line3:\n" + " line3\n" + "Line4: \n" + " \n" + "Connection:\n" + " close\n" + "\n" + ,.should_keep_alive= FALSE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/" + ,.request_url= "/" + ,.num_headers= 5 + ,.headers= { { "Line1", "abc\tdef ghi\t\tjkl mno \t \tqrs" } + , { "Line2", "line2\t" } + , { "Line3", "line3" } + , { "Line4", "" } + , { "Connection", "close" }, + } + ,.body= "" + } + , {.name= NULL } /* sentinel */ };