Support Upgrade header

event_stream
Ryan Dahl 15 years ago
parent 6f72c780f0
commit 88d11b394d

@ -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 @@ 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();
}

Loading…
Cancel
Save