Permalink
Browse files

Upgrade http-parser

  • Loading branch information...
1 parent 684740c commit af49187e57105a793ef4abf33339d854f2b5cda2 @ry ry committed Apr 14, 2010
Showing with 140 additions and 4 deletions.
  1. +32 −1 deps/http_parser/README.md
  2. +35 −0 deps/http_parser/http_parser.c
  3. +7 −0 deps/http_parser/http_parser.h
  4. +66 −3 deps/http_parser/test.c
@@ -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
---------
@@ -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;
@@ -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;
@@ -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 @@ test_error (const char *buf)
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();
}

0 comments on commit af49187

Please sign in to comment.