diff --git a/README.md b/README.md index 3b1d422..6666482 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Features: * request path, query string, fragment * message body * Defends against buffer overflow attacks. + * Upgrade support Usage ----- @@ -59,7 +60,9 @@ When data is received on the socket execute the parser and check for errors. */ nparsed = http_parser_execute(parser, settings, buf, recved); - if (nparsed != recved) { + if (parser->upgrade) { + /* handle new protocol */ + } else if (nparsed != recved) { /* Handle error. Usually just close the connection. */ } @@ -85,6 +88,34 @@ need to inspect the body. Decoding gzip is non-neglagable amount of processing (and requires making allocations). HTTP proxies using this parser, for example, would not want such a feature. +The Special Problem of Upgrade +------------------------------ + +HTTP supports upgrading the connection to a different protocol. An +increasingly common example of this is the Web Socket protocol which sends +a request like + + GET /demo HTTP/1.1 + Upgrade: WebSocket + Connection: Upgrade + Host: example.com + Origin: http://example.com + WebSocket-Protocol: sample + +followed by non-HTTP data. + +(See http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 for more +information the Web Socket protocol.) + +To support this, the parser will treat this as a normal HTTP message without a +body. Issuing both on_headers_complete and on_message_complete callbacks. However +http_parser_execute() may finish without parsing the entire supplied buffer. + +The user needs to check if parser->upgrade has been set to 1 after +http_parser_execute() returns to determine if a premature exit was due to an +upgrade or an error. + + Callbacks --------- diff --git a/http_parser.c b/http_parser.c index 7e79290..e342560 100644 --- a/http_parser.c +++ b/http_parser.c @@ -78,6 +78,7 @@ do { \ #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" @@ -207,10 +208,12 @@ enum header_states , 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 @@ -227,6 +230,7 @@ enum flags , F_CONNECTION_KEEP_ALIVE = 1 << 1 , F_CONNECTION_CLOSE = 1 << 2 , F_TRAILING = 1 << 3 + , F_UPGRADE = 1 << 4 }; @@ -997,6 +1001,10 @@ size_t http_parser_execute (http_parser *parser, header_state = h_matching_transfer_encoding; break; + case 'u': + header_state = h_matching_upgrade; + break; + default: header_state = h_general; break; @@ -1086,9 +1094,22 @@ size_t http_parser_execute (http_parser *parser, } break; + /* upgrade */ + + case h_matching_upgrade: + index++; + if (index > sizeof(UPGRADE)-1 + || c != UPGRADE[index]) { + header_state = h_general; + } else if (index == sizeof(UPGRADE)-2) { + header_state = h_upgrade; + } + break; + case h_connection: case h_content_length: case h_transfer_encoding: + case h_upgrade: if (ch != ' ') header_state = h_general; break; @@ -1148,6 +1169,11 @@ size_t http_parser_execute (http_parser *parser, } switch (header_state) { + case h_upgrade: + parser->flags |= F_UPGRADE; + header_state = h_general; + break; + case h_transfer_encoding: /* looking for 'Transfer-Encoding: chunked' */ if ('c' == c) { @@ -1298,8 +1324,16 @@ size_t http_parser_execute (http_parser *parser, parser->body_read = 0; nread = 0; + if (parser->flags & F_UPGRADE) parser->upgrade = 1; + CALLBACK2(headers_complete); + // Exit, the rest of the connect is in a different protocol. + if (parser->flags & F_UPGRADE) { + CALLBACK2(message_complete); + return (p - data); + } + if (parser->flags & F_CHUNKED) { /* chunked encoding - ignore Content-Length header */ state = s_chunk_size_start; @@ -1492,6 +1526,7 @@ http_parser_init (http_parser *parser, enum http_parser_type t) parser->type = t; parser->state = (t == HTTP_REQUEST ? s_start_req : s_start_res); parser->nread = 0; + parser->upgrade = 0; parser->header_field_mark = NULL; parser->header_value_mark = NULL; diff --git a/http_parser.h b/http_parser.h index 5b648fa..f367872 100644 --- a/http_parser.h +++ b/http_parser.h @@ -93,6 +93,13 @@ struct http_parser { unsigned short header_state; size_t index; + /* 1 = Upgrade header was present and the parser has exited because of that. + * 0 = No upgrade header present. + * Should be checked when http_parser_execute() returns in addition to + * error checking. + */ + unsigned short upgrade; + char flags; size_t nread; diff --git a/test.c b/test.c index ed08854..8646184 100644 --- a/test.c +++ b/test.c @@ -52,6 +52,8 @@ struct message { char headers [MAX_HEADERS][2][MAX_ELEMENT_SIZE]; int should_keep_alive; + int upgrade; + unsigned short http_major; unsigned short http_minor; @@ -456,6 +458,40 @@ const struct message requests[] = ,.body= "" } +#define UPGRADE_REQUEST 16 +, {.name = "upgrade request" + ,.type= HTTP_REQUEST + ,.raw= "GET /demo HTTP/1.1\r\n" + "Host: example.com\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key2: 12998 5 Y3 1 .P00\r\n" + "Sec-WebSocket-Protocol: sample\r\n" + "Upgrade: WebSocket\r\n" + "Sec-WebSocket-Key1: 4 @1 46546xW%0l 1 5\r\n" + "Origin: http://example.com\r\n" + "\r\n" + ,.should_keep_alive= TRUE + ,.message_complete_on_eof= FALSE + ,.http_major= 1 + ,.http_minor= 1 + ,.method= HTTP_GET + ,.query_string= "" + ,.fragment= "" + ,.request_path= "/demo" + ,.request_url= "/demo" + ,.num_headers= 7 + ,.upgrade=1 + ,.headers= { { "Host", "example.com" } + , { "Connection", "Upgrade" } + , { "Sec-WebSocket-Key2", "12998 5 Y3 1 .P00" } + , { "Sec-WebSocket-Protocol", "sample" } + , { "Upgrade", "WebSocket" } + , { "Sec-WebSocket-Key1", "4 @1 46546xW%0l 1 5" } + , { "Origin", "http://example.com" } + } + ,.body= "" + } + , {.name= NULL } /* sentinel */ }; @@ -965,17 +1001,25 @@ test_message (const struct message *message) size_t read; read = parse(message->raw, strlen(message->raw)); + + if (message->upgrade && parser->upgrade) goto test; + if (read != strlen(message->raw)) { print_error(message->raw, read); exit(1); } read = parse(NULL, 0); + + if (message->upgrade && parser->upgrade) goto test; + if (read != 0) { print_error(message->raw, read); exit(1); } +test: + if (num_messages != 1) { printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name); exit(1); @@ -1009,6 +1053,13 @@ out: void test_multiple3 (const struct message *r1, const struct message *r2, const struct message *r3) { + int message_count = 1; + if (!r1->upgrade) { + message_count++; + if (!r2->upgrade) message_count++; + } + int has_upgrade = (message_count < 3 || r3->upgrade); + char total[ strlen(r1->raw) + strlen(r2->raw) + strlen(r3->raw) @@ -1025,25 +1076,37 @@ test_multiple3 (const struct message *r1, const struct message *r2, const struct size_t read; read = parse(total, strlen(total)); + + if (has_upgrade && parser->upgrade) goto test; + if (read != strlen(total)) { print_error(total, read); exit(1); } read = parse(NULL, 0); + + if (has_upgrade && parser->upgrade) goto test; + if (read != 0) { print_error(total, read); exit(1); } - if (3 != num_messages) { +test: + + if (message_count != num_messages) { fprintf(stderr, "\n\n*** Parser didn't see 3 messages only %d *** \n", num_messages); exit(1); } if (!message_eq(0, r1)) exit(1); - if (!message_eq(1, r2)) exit(1); - if (!message_eq(2, r3)) exit(1); + if (message_count > 1) { + if (!message_eq(1, r2)) exit(1); + if (message_count > 2) { + if (!message_eq(2, r3)) exit(1); + } + } parser_free(); }