Browse Source

Upgrade http parser, change node as needed.

The latest version of http-parser is a bit more stringent EOF semantics.
v0.7.4-release
Ryan Dahl 15 years ago
parent
commit
b893859c34
  1. 16
      deps/http_parser/LICENSE
  2. 4
      deps/http_parser/Makefile
  3. 33
      deps/http_parser/README.md
  4. 5525
      deps/http_parser/http_parser.c
  5. 91
      deps/http_parser/http_parser.h
  6. 236
      deps/http_parser/http_parser.rl
  7. 165
      deps/http_parser/test.c
  8. 7
      lib/http.js
  9. 42
      src/http.cc
  10. 1
      src/http.h
  11. 22
      test/mjsunit/test-http-malformed-request.js
  12. 16
      test/mjsunit/test-http-proxy.js
  13. 16
      test/mjsunit/test-http-server.js

16
deps/http_parser/LICENSE

@ -1,4 +1,5 @@
Copyright 2009, Ryan Lienhart Dahl. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
deal in the Software without restriction, including without limitation the
@ -15,16 +16,13 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
IN THE SOFTWARE.
IN THE SOFTWARE.
http_parser is based on Zed Shaw's Mongrel. Mongrel's license is as follows.
-- CUT ---- CUT ---- CUT ---- CUT ---- CUT ---- CUT ---- CUT ---- CUT --
---- 8< ---- 8< ---- 8< ---- 8< ---- 8< ---- 8< ---- 8< ---- 8< ----
Mongrel Web Server (Mongrel) is copyrighted free software by Zed A. Shaw
<zedshaw at zedshaw dot com> and contributors. You can redistribute it
<zedshaw at zedshaw dot com> and contributors. You can redistribute it
and/or modify it under either the terms of the GPL2 or the conditions below:
1. You may make and give away verbatim copies of the source form of the
@ -66,9 +64,9 @@ and/or modify it under either the terms of the GPL2 or the conditions below:
software (possibly commercial). But some files in the distribution
are not written by the author, so that they are not under this terms.
5. The scripts and library files supplied as input to or produced as
5. The scripts and library files supplied as input to or produced as
output from the software do not automatically fall under the
copyright of the software, but belong to whomever generated them,
copyright of the software, but belong to whomever generated them,
and may be sold commercially, and may be aggregated with this
software.
@ -76,4 +74,4 @@ and/or modify it under either the terms of the GPL2 or the conditions below:
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.
-- CUT ---- CUT ---- CUT ---- CUT ---- CUT ---- CUT ---- CUT ---- CUT --
---- 8< ---- 8< ---- 8< ---- 8< ---- 8< ---- 8< ---- 8< ---- 8< ----

4
deps/http_parser/Makefile

@ -1,8 +1,8 @@
#OPT=-O0 -g -Wall -Wextra -Werror
OPT=-O2
test: http_parser.o test.c
gcc $(OPT) http_parser.o test.c -o $@
test: http_parser.o test.c
gcc $(OPT) http_parser.o test.c -o $@
http_parser.o: http_parser.c http_parser.h Makefile
gcc $(OPT) -c http_parser.c

33
deps/http_parser/README.md

@ -5,11 +5,11 @@ This is a parser for HTTP messages written in C. It parses both requests
and responses. The parser is designed to be used in performance HTTP
applications. It does not make any allocations, it does not buffer data, and
it can be interrupted at anytime. It only requires about 128 bytes of data
per message stream (in a web server that is per connection).
per message stream (in a web server that is per connection).
Features:
* No dependencies
* No dependencies
* Parses both requests and responses.
* Handles keep-alive streams.
* Decodes chunked encoding.
@ -43,21 +43,35 @@ When data is received on the socket execute the parser and check for errors.
char buf[len];
ssize_t recved;
recved = read(fd, buf, len);
if (recved != 0) // handle error
recved = recv(fd, buf, len, 0);
if (recved < 0) {
/* Handle error. */
}
/* Start up / continue the parser.
* Note we pass the recved==0 to http_parser_execute to signal
* that EOF has been recieved.
*/
http_parser_execute(parser, buf, recved);
if (http_parser_has_error(parser)) {
// handle error. usually just close the connection
/* Handle error. Usually just close the connection. */
}
HTTP needs to know where the end of the stream is. For example, sometimes
servers send responses without Content-Length and expect the client to
consume input (for the body) until EOF. To tell http_parser about EOF, give
`0` as the third parameter to `http_parser_execute()`. Callbacks and errors
can still be encountered during an EOF, so one must still be prepared
to receive them.
Scalar valued message information such as `status_code`, `method`, and the
HTTP version are stored in the parser structure. This data is only
temporarlly stored in `http_parser` and gets reset on each new message. If
this information is needed later, copy it out of the structure during the
`headers_complete` callback.
The parser decodes the transfer-encoding for both requests and responses
transparently. That is, a chunked encoding is decoded before being sent to
the on_body callback.
@ -129,3 +143,10 @@ Releases
* [0.1](http://s3.amazonaws.com/four.livejournal/20090427/http_parser-0.1.tar.gz)
The source repo is at [github](http://github.com/ry/http-parser).
Bindings
--------
* [Ruby](http://github.com/yakischloba/http-parser-ffi)
* [Lua](http://github.com/phoenixsol/lua-http-parser)

5525
deps/http_parser/http_parser.c

File diff suppressed because it is too large

91
deps/http_parser/http_parser.h

@ -2,7 +2,7 @@
* Based on Zed Shaw's Mongrel, copyright (c) Zed A. Shaw
*
* All rights reserved.
*
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
@ -10,31 +10,34 @@
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef http_parser_h
#define http_parser_h
#ifdef __cplusplus
extern "C" {
#endif
#endif
#include <sys/types.h>
#ifdef _MSC_VER
# include <stddef.h>
#endif
#include <sys/types.h>
typedef struct http_parser http_parser;
/* Callbacks should return non-zero to indicate an error. The parse will
* then halt execution.
*
* then halt execution.
*
* http_data_cb does not return data chunks. It will be call arbitrarally
* many times for each string. E.G. you might get 10 callbacks for "on_path"
* each providing just a few characters more data.
@ -58,60 +61,42 @@ typedef int (*http_cb) (http_parser*);
#define HTTP_TRACE 0x1000
#define HTTP_UNLOCK 0x2000
/* Transfer Encodings */
#define HTTP_IDENTITY 0x01
#define HTTP_CHUNKED 0x02
enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE };
#define HTTP_VERSION_OTHER 0x00
#define HTTP_VERSION_11 0x01
#define HTTP_VERSION_10 0x02
#define HTTP_VERSION_09 0x04
struct http_parser {
/** PRIVATE **/
int cs;
enum http_parser_type type;
size_t chunk_size;
/**
XXX
do this so no other code has to change, but make the field only 1 byte wide
instead of 2 (on x86/x86_64).
doing this not only shrinks the sizeof this struct by a byte but it ALSO
makes wrapping this in FFI way easier.
*/
union {
struct {
unsigned eating:1;
unsigned error:1;
};
struct {
unsigned char _flags;
};
};
char flags;
size_t body_read;
const char *header_field_mark;
size_t header_field_size;
const char *header_value_mark;
size_t header_value_size;
const char *query_string_mark;
size_t query_string_size;
const char *path_mark;
size_t path_size;
const char *uri_mark;
size_t uri_size;
const char *fragment_mark;
size_t fragment_size;
const char *header_field_mark;
size_t header_field_size;
const char *header_value_mark;
size_t header_value_size;
const char *query_string_mark;
size_t query_string_size;
const char *path_mark;
size_t path_size;
const char *uri_mark;
size_t uri_size;
const char *fragment_mark;
size_t fragment_size;
/** READ-ONLY **/
unsigned short status_code; /* responses only */
unsigned short method; /* requests only */
short transfer_encoding;
unsigned short version_major;
unsigned short version_minor;
short version;
short keep_alive;
size_t content_length;
ssize_t content_length;
/** PUBLIC **/
void *data; /* A pointer to get hook to the "connection" or "socket" object */
@ -134,17 +119,23 @@ struct http_parser {
};
/* Initializes an http_parser structure. The second argument specifies if
* it will be parsing requests or responses.
* it will be parsing requests or responses.
*/
void http_parser_init (http_parser *parser, enum http_parser_type);
size_t http_parser_execute (http_parser *parser, const char *data, size_t len);
void http_parser_execute (http_parser *parser, const char *data, size_t len);
int http_parser_has_error (http_parser *parser);
int http_parser_should_keep_alive (http_parser *parser);
static inline int
http_parser_should_keep_alive (http_parser *parser)
{
if (parser->keep_alive == -1) return (parser->version == HTTP_VERSION_11);
return parser->keep_alive;
}
#ifdef __cplusplus
}
#endif
#endif
#endif

236
deps/http_parser/http_parser.rl

@ -2,7 +2,7 @@
* Based on Zed Shaw's Mongrel, copyright (c) Zed A. Shaw
*
* All rights reserved.
*
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
@ -10,22 +10,28 @@
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "http_parser.h"
#include <limits.h>
#include <assert.h>
/* parser->flags */
#define EATING 0x01
#define ERROR 0x02
#define CHUNKED 0x04
#define EAT_FOREVER 0x10
static int unhex[] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
@ -35,12 +41,14 @@ static int unhex[] = {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1
,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1
};
#define TRUE 1
#define FALSE 0
#define MIN(a,b) (a < b ? a : b)
#define NULL (void*)(0)
#define MAX_FIELD_SIZE 80*1024
#undef MIN
#define MIN(a,b) ((a) < (b) ? (a) : (b))
#undef NULL
#define NULL ((void*)(0))
#define MAX_FIELD_SIZE (80*1024)
#define REMAINING (unsigned long)(pe - p)
#define CALLBACK(FOR) \
@ -48,34 +56,36 @@ do { \
if (parser->FOR##_mark) { \
parser->FOR##_size += p - parser->FOR##_mark; \
if (parser->FOR##_size > MAX_FIELD_SIZE) { \
parser->error = TRUE; \
return 0; \
parser->flags |= ERROR; \
return; \
} \
if (parser->on_##FOR) { \
callback_return_value = parser->on_##FOR(parser, \
parser->FOR##_mark, \
p - parser->FOR##_mark); \
} \
if (callback_return_value != 0) { \
parser->flags |= ERROR; \
return; \
} \
} \
} while(0)
#define RESET_PARSER(parser) \
parser->chunk_size = 0; \
parser->eating = 0; \
parser->header_field_mark = NULL; \
parser->header_value_mark = NULL; \
parser->query_string_mark = NULL; \
parser->path_mark = NULL; \
parser->uri_mark = NULL; \
parser->fragment_mark = NULL; \
parser->status_code = 0; \
parser->method = 0; \
parser->transfer_encoding = HTTP_IDENTITY; \
parser->version_major = 0; \
parser->version_minor = 0; \
parser->keep_alive = -1; \
parser->content_length = 0; \
parser->body_read = 0;
parser->chunk_size = 0; \
parser->flags = 0; \
parser->header_field_mark = NULL; \
parser->header_value_mark = NULL; \
parser->query_string_mark = NULL; \
parser->path_mark = NULL; \
parser->uri_mark = NULL; \
parser->fragment_mark = NULL; \
parser->status_code = 0; \
parser->method = 0; \
parser->version = HTTP_VERSION_OTHER; \
parser->keep_alive = -1; \
parser->content_length = -1; \
parser->body_read = 0
#define END_REQUEST \
do { \
@ -97,12 +107,12 @@ do { \
parser->body_read += tmp; \
parser->chunk_size -= tmp; \
if (0 == parser->chunk_size) { \
parser->eating = FALSE; \
if (parser->transfer_encoding == HTTP_IDENTITY) { \
parser->flags &= ~EATING; \
if (!(parser->flags & CHUNKED)) { \
END_REQUEST; \
} \
} else { \
parser->eating = TRUE; \
parser->flags |= EATING; \
} \
} \
} while (0)
@ -142,60 +152,36 @@ do { \
action header_field {
CALLBACK(header_field);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
parser->header_field_mark = NULL;
parser->header_field_size = 0;
}
action header_value {
CALLBACK(header_value);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
parser->header_value_mark = NULL;
parser->header_value_size = 0;
}
action request_uri {
action request_uri {
CALLBACK(uri);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
parser->uri_mark = NULL;
parser->uri_size = 0;
}
action fragment {
action fragment {
CALLBACK(fragment);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
parser->fragment_mark = NULL;
parser->fragment_size = 0;
}
action query_string {
action query_string {
CALLBACK(query_string);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
parser->query_string_mark = NULL;
parser->query_string_size = 0;
}
action request_path {
CALLBACK(path);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
}
parser->path_mark = NULL;
parser->path_size = 0;
}
@ -204,8 +190,8 @@ do { \
if(parser->on_headers_complete) {
callback_return_value = parser->on_headers_complete(parser);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
parser->flags |= ERROR;
return;
}
}
}
@ -214,16 +200,17 @@ do { \
if(parser->on_message_begin) {
callback_return_value = parser->on_message_begin(parser);
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
parser->flags |= ERROR;
return;
}
}
}
action content_length {
if (parser->content_length == -1) parser->content_length = 0;
if (parser->content_length > INT_MAX) {
parser->error = TRUE;
return 0;
parser->flags |= ERROR;
return;
}
parser->content_length *= 10;
parser->content_length += *p - '0';
@ -234,21 +221,14 @@ do { \
parser->status_code += *p - '0';
}
action use_identity_encoding { parser->transfer_encoding = HTTP_IDENTITY; }
action use_chunked_encoding { parser->transfer_encoding = HTTP_CHUNKED; }
action use_chunked_encoding { parser->flags |= CHUNKED; }
action set_keep_alive { parser->keep_alive = TRUE; }
action set_not_keep_alive { parser->keep_alive = FALSE; }
action set_keep_alive { parser->keep_alive = 1; }
action set_not_keep_alive { parser->keep_alive = 0; }
action version_major {
parser->version_major *= 10;
parser->version_major += *p - '0';
}
action version_minor {
parser->version_minor *= 10;
parser->version_minor += *p - '0';
}
action version_11 { parser->version = HTTP_VERSION_11; }
action version_10 { parser->version = HTTP_VERSION_10; }
action version_09 { parser->version = HTTP_VERSION_09; }
action add_to_chunk_size {
parser->chunk_size *= 16;
@ -258,15 +238,15 @@ do { \
action skip_chunk_data {
SKIP_BODY(MIN(parser->chunk_size, REMAINING));
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
parser->flags |= ERROR;
return;
}
fhold;
fhold;
if (parser->chunk_size > REMAINING) {
fbreak;
} else {
fgoto chunk_end;
fgoto chunk_end;
}
}
@ -280,18 +260,32 @@ do { \
}
action body_logic {
if (parser->transfer_encoding == HTTP_CHUNKED) {
if (parser->flags & CHUNKED) {
fnext ChunkedBody;
} else {
/* this is pretty stupid. i'd prefer to combine this with skip_chunk_data */
parser->chunk_size = parser->content_length;
p += 1;
/* this is pretty stupid. i'd prefer to combine this with
* skip_chunk_data */
if (parser->content_length < 0) {
/* If we didn't get a content length; if not keep-alive
* just read body until EOF */
if (!http_parser_should_keep_alive(parser)) {
parser->flags |= EAT_FOREVER;
parser->chunk_size = REMAINING;
} else {
/* Otherwise, if keep-alive, then assume the message
* has no body. */
parser->chunk_size = parser->content_length = 0;
}
} else {
parser->chunk_size = parser->content_length;
}
p += 1;
SKIP_BODY(MIN(REMAINING, parser->content_length));
SKIP_BODY(MIN(REMAINING, parser->chunk_size));
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
parser->flags |= ERROR;
return;
}
fhold;
@ -314,13 +308,13 @@ do { \
escape = ("%" xdigit xdigit);
uchar = (unreserved | escape | "\"");
pchar = (uchar | ":" | "@" | "&" | "=" | "+");
tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\""
tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\""
| "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t");
# elements
token = (ascii -- (CTL | tspecials));
quote = "\"";
# qdtext = token -- "\"";
# qdtext = token -- "\"";
# quoted_pair = "\" ascii;
# quoted_string = "\"" (qdtext | quoted_pair )* "\"";
@ -342,7 +336,11 @@ do { \
| "UNLOCK" %{ parser->method = HTTP_UNLOCK; }
); # Not allowing extension methods
HTTP_Version = "HTTP/" digit $version_major "." digit $version_minor;
HTTP_Version = "HTTP/" ( "1.1" %version_11
| "1.0" %version_10
| "0.9" %version_09
| (digit "." digit)
);
scheme = ( alpha | digit | "+" | "-" | "." )* ;
absolute_uri = (scheme ":" (uchar | reserved )*);
@ -364,12 +362,12 @@ do { \
hsep = ":" " "*;
header = (field_name hsep field_value) :> CRLF;
Header = ( ("Content-Length"i hsep digit+ $content_length)
| ("Connection"i hsep
| ("Connection"i hsep
( "Keep-Alive"i %set_keep_alive
| "close"i %set_not_keep_alive
)
)
| ("Transfer-Encoding"i %use_chunked_encoding hsep "identity" %use_identity_encoding)
| ("Transfer-Encoding"i hsep "chunked"i %use_chunked_encoding)
| (Field_Name hsep Field_Value)
) :> CRLF;
@ -387,11 +385,11 @@ do { \
chunk_ext_val = token*;
chunk_ext_name = token*;
chunk_extension = ( ";" " "* chunk_ext_name ("=" chunk_ext_val)? )*;
last_chunk = "0"+ chunk_extension CRLF;
chunk_size = (xdigit* [1-9a-fA-F] xdigit*) $add_to_chunk_size;
last_chunk = "0"+ ( chunk_extension | " "+) CRLF;
chunk_size = (xdigit* [1-9a-fA-F] xdigit* ) $add_to_chunk_size;
chunk_end = CRLF;
chunk_body = any >skip_chunk_data;
chunk_begin = chunk_size chunk_extension CRLF;
chunk_begin = chunk_size ( chunk_extension | " "+ ) CRLF;
chunk = chunk_begin chunk_body chunk_end;
ChunkedBody := chunk* last_chunk trailing_headers CRLF @end_chunked_body;
@ -415,13 +413,12 @@ do { \
%% write data;
void
http_parser_init (http_parser *parser, enum http_parser_type type)
http_parser_init (http_parser *parser, enum http_parser_type type)
{
int cs = 0;
%% write init;
parser->cs = cs;
parser->type = type;
parser->error = 0;
parser->on_message_begin = NULL;
parser->on_path = NULL;
@ -438,23 +435,39 @@ http_parser_init (http_parser *parser, enum http_parser_type type)
}
/** exec **/
size_t
void
http_parser_execute (http_parser *parser, const char *buffer, size_t len)
{
size_t tmp; // REMOVE ME this is extremely hacky
int callback_return_value = 0;
const char *p, *pe;
const char *p, *pe, *eof;
int cs = parser->cs;
p = buffer;
pe = buffer+len;
eof = len ? NULL : pe;
if (0 < parser->chunk_size && parser->eating) {
if (parser->flags & EAT_FOREVER) {
if (len == 0) {
if (parser->on_message_complete) {
callback_return_value = parser->on_message_complete(parser);
if (callback_return_value != 0) parser->flags |= ERROR;
}
} else {
if (parser->on_body) {
callback_return_value = parser->on_body(parser, p, len);
if (callback_return_value != 0) parser->flags |= ERROR;
}
}
return;
}
if (0 < parser->chunk_size && (parser->flags & EATING)) {
/* eat body */
SKIP_BODY(MIN(len, parser->chunk_size));
if (callback_return_value != 0) {
parser->error = TRUE;
return 0;
parser->flags |= ERROR;
return;
}
}
@ -477,26 +490,11 @@ http_parser_execute (http_parser *parser, const char *buffer, size_t len)
CALLBACK(uri);
assert(p <= pe && "buffer overflow after parsing execute");
return(p - buffer);
}
int
http_parser_has_error (http_parser *parser)
http_parser_has_error (http_parser *parser)
{
if (parser->error) return TRUE;
if (parser->flags & ERROR) return 1;
return parser->cs == http_parser_error;
}
int
http_parser_should_keep_alive (http_parser *parser)
{
if (parser->keep_alive == -1)
if (parser->version_major == 1)
return (parser->version_minor != 0);
else if (parser->version_major == 0)
return FALSE;
else
return TRUE;
else
return parser->keep_alive;
}

165
deps/http_parser/test.c

@ -38,7 +38,7 @@ struct message {
static struct message messages[5];
static int num_messages;
/* * R E Q U E S T S * */
/* * R E Q U E S T S * */
const struct message requests[] =
#define CURL_GET 0
{ {.name= "curl get"
@ -55,7 +55,7 @@ const struct message requests[] =
,.request_path= "/test"
,.request_uri= "/test"
,.num_headers= 3
,.headers=
,.headers=
{ { "User-Agent", "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1" }
, { "Host", "0.0.0.0=5000" }
, { "Accept", "*/*" }
@ -83,7 +83,7 @@ const struct message requests[] =
,.request_path= "/favicon.ico"
,.request_uri= "/favicon.ico"
,.num_headers= 8
,.headers=
,.headers=
{ { "Host", "0.0.0.0=5000" }
, { "User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0" }
, { "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" }
@ -109,7 +109,7 @@ const struct message requests[] =
,.request_path= "/dumbfuck"
,.request_uri= "/dumbfuck"
,.num_headers= 1
,.headers=
,.headers=
{ { "aaaaaaaaaaaaa", "++++++++++" }
}
,.body= ""
@ -126,7 +126,7 @@ const struct message requests[] =
,.fragment= "posts-17408"
,.request_path= "/forums/1/topics/2375"
/* XXX request uri does not include fragment? */
,.request_uri= "/forums/1/topics/2375?page=1"
,.request_uri= "/forums/1/topics/2375?page=1"
,.num_headers= 0
,.body= ""
}
@ -159,7 +159,7 @@ const struct message requests[] =
,.request_path= "/get_one_header_no_body"
,.request_uri= "/get_one_header_no_body"
,.num_headers= 1
,.headers=
,.headers=
{ { "Accept" , "*/*" }
}
,.body= ""
@ -179,7 +179,7 @@ const struct message requests[] =
,.request_path= "/get_funky_content_length_body_hello"
,.request_uri= "/get_funky_content_length_body_hello"
,.num_headers= 1
,.headers=
,.headers=
{ { "conTENT-Length" , "5" }
}
,.body= "HELLO"
@ -201,10 +201,10 @@ const struct message requests[] =
,.request_path= "/post_identity_body_world"
,.request_uri= "/post_identity_body_world?q=search"
,.num_headers= 3
,.headers=
,.headers=
{ { "Accept", "*/*" }
, { "Transfer-Encoding", "identity" }
, { "Content-Length", "5" }
, { "Content-Length", "5" }
}
,.body= "World"
}
@ -225,7 +225,7 @@ const struct message requests[] =
,.request_path= "/post_chunked_all_your_base"
,.request_uri= "/post_chunked_all_your_base"
,.num_headers= 1
,.headers=
,.headers=
{ { "Transfer-Encoding" , "chunked" }
}
,.body= "all your base are belong to us"
@ -248,13 +248,13 @@ const struct message requests[] =
,.request_path= "/two_chunks_mult_zero_end"
,.request_uri= "/two_chunks_mult_zero_end"
,.num_headers= 1
,.headers=
,.headers=
{ { "Transfer-Encoding", "chunked" }
}
,.body= "hello world"
}
#define CHUNKED_W_TRAILING_HEADERS 10
#define CHUNKED_W_TRAILING_HEADERS 10
, {.name= "chunked with trailing headers. blech."
,.type= HTTP_REQUEST
,.raw= "POST /chunked_w_trailing_headers HTTP/1.1\r\n"
@ -273,13 +273,13 @@ const struct message requests[] =
,.request_path= "/chunked_w_trailing_headers"
,.request_uri= "/chunked_w_trailing_headers"
,.num_headers= 1
,.headers=
,.headers=
{ { "Transfer-Encoding", "chunked" }
}
,.body= "hello world"
}
#define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11
#define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11
, {.name= "with bullshit after the length"
,.type= HTTP_REQUEST
,.raw= "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n"
@ -296,7 +296,7 @@ const struct message requests[] =
,.request_path= "/chunked_w_bullshit_after_length"
,.request_uri= "/chunked_w_bullshit_after_length"
,.num_headers= 1
,.headers=
,.headers=
{ { "Transfer-Encoding", "chunked" }
}
,.body= "hello world"
@ -320,8 +320,8 @@ const struct message requests[] =
, {.name= NULL } /* sentinel */
};
/* * R E S P O N S E S * */
const struct message responses[] =
/* * R E S P O N S E S * */
const struct message responses[] =
{ {.name= "google 301"
,.type= HTTP_RESPONSE
,.raw= "HTTP/1.1 301 Moved Permanently\r\n"
@ -342,7 +342,7 @@ const struct message responses[] =
,.should_keep_alive= TRUE
,.status_code= 301
,.num_headers= 7
,.headers=
,.headers=
{ { "Location", "http://www.google.com/" }
, { "Content-Type", "text/html; charset=UTF-8" }
, { "Date", "Sun, 26 Apr 2009 11:11:49 GMT" }
@ -359,6 +359,45 @@ const struct message responses[] =
"</BODY></HTML>\r\n"
}
, {.name= "no content-length response"
,.type= HTTP_RESPONSE
,.raw= "HTTP/1.1 200 OK\r\n"
"Date: Tue, 04 Aug 2009 07:59:32 GMT\r\n"
"Server: Apache\r\n"
"X-Powered-By: Servlet/2.5 JSP/2.1\r\n"
"Content-Type: text/xml; charset=utf-8\r\n"
"Connection: close\r\n"
"\r\n"
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
" <SOAP-ENV:Body>\n"
" <SOAP-ENV:Fault>\n"
" <faultcode>SOAP-ENV:Client</faultcode>\n"
" <faultstring>Client Error</faultstring>\n"
" </SOAP-ENV:Fault>\n"
" </SOAP-ENV:Body>\n"
"</SOAP-ENV:Envelope>"
,.should_keep_alive= FALSE
,.status_code= 200
,.num_headers= 5
,.headers=
{ { "Date", "Tue, 04 Aug 2009 07:59:32 GMT" }
, { "Server", "Apache" }
, { "X-Powered-By", "Servlet/2.5 JSP/2.1" }
, { "Content-Type", "text/xml; charset=utf-8" }
, { "Connection", "close" }
}
,.body= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
" <SOAP-ENV:Body>\n"
" <SOAP-ENV:Fault>\n"
" <faultcode>SOAP-ENV:Client</faultcode>\n"
" <faultstring>Client Error</faultstring>\n"
" </SOAP-ENV:Fault>\n"
" </SOAP-ENV:Body>\n"
"</SOAP-ENV:Envelope>"
}
, {.name= "404 no headers no body"
,.type= HTTP_RESPONSE
,.raw= "HTTP/1.1 404 Not Found\r\n\r\n"
@ -377,7 +416,34 @@ const struct message responses[] =
,.num_headers= 0
,.headers= {}
,.body= ""
}
}
, {.name="200 trailing space on chunked body"
,.type= HTTP_RESPONSE
,.raw= "HTTP/1.1 200 OK\r\n"
"Content-Type: text/plain\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"25 \r\n"
"This is the data in the first chunk\r\n"
"\r\n"
"1C\r\n"
"and this is the second one\r\n"
"\r\n"
"0 \r\n"
"\r\n"
,.should_keep_alive= TRUE
,.status_code= 200
,.num_headers= 2
,.headers=
{ {"Content-Type", "text/plain" }
, {"Transfer-Encoding", "chunked" }
}
,.body =
"This is the data in the first chunk\r\n"
"and this is the second one\r\n"
}
, {.name= NULL } /* sentinel */
};
@ -543,12 +609,14 @@ parse_messages (int message_count, const struct message *input_messages[])
}
// Parse the stream
size_t traversed = 0;
parser_init(HTTP_REQUEST);
traversed = http_parser_execute(&parser, total, length);
http_parser_execute(&parser, total, length);
assert(!http_parser_has_error(&parser));
http_parser_execute(&parser, NULL, 0);
assert(!http_parser_has_error(&parser));
assert(num_messages == message_count);
for (i = 0; i < message_count; i++) {
@ -560,11 +628,14 @@ parse_messages (int message_count, const struct message *input_messages[])
void
test_message (const struct message *message)
{
size_t traversed = 0;
parser_init(message->type);
traversed = http_parser_execute(&parser, message->raw, strlen(message->raw));
http_parser_execute(&parser, message->raw, strlen(message->raw));
assert(!http_parser_has_error(&parser));
http_parser_execute(&parser, NULL, 0);
assert(!http_parser_has_error(&parser));
assert(num_messages == 1);
message_eq(0, message);
@ -573,10 +644,10 @@ test_message (const struct message *message)
void
test_error (const char *buf)
{
size_t traversed = 0;
parser_init(HTTP_REQUEST);
traversed = http_parser_execute(&parser, buf, strlen(buf));
http_parser_execute(&parser, buf, strlen(buf));
http_parser_execute(&parser, NULL, 0);
assert(http_parser_has_error(&parser));
}
@ -584,30 +655,32 @@ test_error (const char *buf)
void
test_multiple3 (const struct message *r1, const struct message *r2, const struct message *r3)
{
char total[ strlen(r1->raw)
+ strlen(r2->raw)
+ strlen(r3->raw)
char total[ strlen(r1->raw)
+ strlen(r2->raw)
+ strlen(r3->raw)
+ 1
];
total[0] = '\0';
strcat(total, r1->raw);
strcat(total, r2->raw);
strcat(total, r3->raw);
strcat(total, r1->raw);
strcat(total, r2->raw);
strcat(total, r3->raw);
size_t traversed = 0;
parser_init(HTTP_REQUEST);
traversed = http_parser_execute(&parser, total, strlen(total));
http_parser_execute(&parser, total, strlen(total));
assert(!http_parser_has_error(&parser) );
http_parser_execute(&parser, NULL, 0);
assert(!http_parser_has_error(&parser) );
assert(! http_parser_has_error(&parser) );
assert(num_messages == 3);
message_eq(0, r1);
message_eq(1, r2);
message_eq(2, r3);
}
/* SCAN through every possible breaking to make sure the
/* SCAN through every possible breaking to make sure the
* parser can handle getting the content in any chunks that
* might come from the socket
*/
@ -619,13 +692,13 @@ test_scan (const struct message *r1, const struct message *r2, const struct mess
char buf2[80*1024] = "\0";
char buf3[80*1024] = "\0";
strcat(total, r1->raw);
strcat(total, r2->raw);
strcat(total, r3->raw);
strcat(total, r1->raw);
strcat(total, r2->raw);
strcat(total, r3->raw);
int total_len = strlen(total);
int total_ops = (total_len - 1) * (total_len - 2) / 2;
int total_ops = (total_len - 1) * (total_len - 2) / 2;
int ops = 0 ;
int i,j;
@ -659,16 +732,16 @@ test_scan (const struct message *r1, const struct message *r2, const struct mess
*/
http_parser_execute(&parser, buf1, buf1_len);
assert(!http_parser_has_error(&parser));
http_parser_execute(&parser, buf2, buf2_len);
assert(!http_parser_has_error(&parser));
http_parser_execute(&parser, buf3, buf3_len);
assert(!http_parser_has_error(&parser));
assert(! http_parser_has_error(&parser));
http_parser_execute(&parser, NULL, 0);
assert(!http_parser_has_error(&parser));
assert(3 == num_messages);
@ -687,14 +760,14 @@ main (void)
printf("sizeof(http_parser) = %d\n", sizeof(http_parser));
int request_count;
int request_count;
for (request_count = 0; requests[request_count].name; request_count++);
int response_count;
int response_count;
for (response_count = 0; responses[response_count].name; response_count++);
//// RESPONSES
//// RESPONSES
for (i = 0; i < response_count; i++) {
test_message(&responses[i]);
@ -755,7 +828,7 @@ main (void)
"Accept: */*\r\n"
"\r\n"
"HELLO";
test_error(bad_get_no_headers_no_body);
test_error(bad_get_no_headers_no_body);
/* TODO sending junk and large headers gets rejected */

7
lib/http.js

@ -333,7 +333,7 @@ function ClientRequest (method, uri, headers) {
}
this.closeOnFinish = true;
this.sendHeaderLines(method + " " + uri + " HTTP/1.1\r\n", headers);
this.sendHeaderLines(method + " " + uri + " HTTP/1.0\r\n", headers);
}
node.inherits(ClientRequest, OutgoingMessage);
@ -476,7 +476,6 @@ function connectionListener (connection) {
}
});
createIncomingMessageStream(connection, function (incoming, should_keep_alive) {
var req = incoming;
@ -507,7 +506,9 @@ exports.createClient = function (port, host) {
return;
}
//sys.debug("client flush readyState = " + client.readyState);
if (req == requests[0]) flushMessageQueue(client, [req]);
if (req == requests[0]) {
if(flushMessageQueue(client, [req])) client.close();
}
});
requests.push(req);
};

42
src/http.cc

@ -66,6 +66,21 @@ HTTPConnection::OnReceive (const void *buf, size_t len)
if (http_parser_has_error(&parser_)) ForceClose();
}
void
HTTPConnection::OnEOF ()
{
HandleScope scope;
assert(attached_);
http_parser_execute(&parser_, NULL, 0);
if (http_parser_has_error(&parser_)) {
ForceClose();
} else {
Emit("eof", 0, NULL);
}
}
int
HTTPConnection::on_message_begin (http_parser *parser)
{
@ -211,14 +226,25 @@ HTTPConnection::on_headers_complete (http_parser *parser)
Integer::New(connection->parser_.status_code));
// VERSION
char version[10];
snprintf( version
, 10
, "%d.%d"
, connection->parser_.version_major
, connection->parser_.version_minor
);
message_info->Set(HTTP_VERSION_SYMBOL, String::New(version));
Local<String> version;
switch (connection->parser_.version) {
case HTTP_VERSION_OTHER:
version = String::NewSymbol("Other");
break;
case HTTP_VERSION_09:
version = String::NewSymbol("0.9");
break;
case HTTP_VERSION_10:
version = String::NewSymbol("1.0");
break;
case HTTP_VERSION_11:
version = String::NewSymbol("1.1");
break;
}
message_info->Set(HTTP_VERSION_SYMBOL, version);
message_info->Set(SHOULD_KEEP_ALIVE_SYMBOL,
http_parser_should_keep_alive(&connection->parser_) ? True() : False());

1
src/http.h

@ -36,6 +36,7 @@ protected:
}
void OnReceive (const void *buf, size_t len);
void OnEOF ();
static int on_message_begin (http_parser *parser);
static int on_uri (http_parser *parser, const char *at, size_t length);

22
test/mjsunit/test-http-malformed-request.js

@ -7,26 +7,30 @@ http = require("/http.js");
port = 9999;
nrequests_completed = 0;
nrequests_expected = 1;
nrequests_expected = 2;
var s = http.createServer(function (req, res) {
var server = http.createServer(function (req, res) {
puts("req: " + JSON.stringify(req.uri));
res.sendHeader(200, {"Content-Type": "text/plain"});
res.sendBody("Hello World");
res.finish();
if (++nrequests_completed == nrequests_expected) s.close();
if (++nrequests_completed == nrequests_expected) server.close();
puts("nrequests_completed: " + nrequests_completed);
});
s.listen(port);
server.listen(port);
var c = tcp.createConnection(port);
c.addListener("connect", function () {
c.send("GET /hello?foo=%99bar HTTP/1.1\r\n\r\n");
c.close();
tcp.createConnection(port).addListener("connect", function () {
this.send("GET /hello?foo=%99bar HTTP/1.1\r\nConnection: close\r\n\r\n");
this.close();
});
// TODO add more!
tcp.createConnection(port).addListener("connect", function () {
this.send("GET /with_\"stupid\"_quotes?in_the=\"uri\" HTTP/1.1\r\nConnection: close\r\n\r\n");
this.close();
});
process.addListener("exit", function () {
assertEquals(nrequests_expected, nrequests_completed);

16
test/mjsunit/test-http-proxy.js

@ -5,12 +5,12 @@ var PROXY_PORT = 8869;
var BACKEND_PORT = 8870;
var backend = http.createServer(function (req, res) {
// debug("backend");
debug("backend");
res.sendHeader(200, {"content-type": "text/plain"});
res.sendBody("hello world\n");
res.finish();
});
// debug("listen backend")
debug("listen backend")
backend.listen(BACKEND_PORT);
var proxy_client = http.createClient(BACKEND_PORT);
@ -24,30 +24,30 @@ var proxy = http.createServer(function (req, res) {
});
proxy_res.addListener("complete", function() {
res.finish();
// debug("proxy res");
debug("proxy res");
});
});
});
// debug("listen proxy")
debug("listen proxy")
proxy.listen(PROXY_PORT);
var body = "";
var client = http.createClient(PROXY_PORT);
var req = client.get("/test");
// debug("client req")
debug("client req")
req.finish(function (res) {
// debug("got res");
debug("got res: " + JSON.stringify(res.headers));
assertEquals(200, res.statusCode);
res.setBodyEncoding("utf8");
res.addListener("body", function (chunk) { body += chunk; });
res.addListener("complete", function () {
proxy.close();
backend.close();
// debug("closed both");
debug("closed both");
});
});
process.addListener("exit", function () {
assertEquals(body, "hello world\n");
assertEquals("hello world\n", body);
});

16
test/mjsunit/test-http-server.js

@ -13,6 +13,12 @@ http.createServer(function (req, res) {
res.id = request_number;
req.id = request_number++;
puts("server got request " + req.id);
req.addListener("complete", function () {
puts("request complete " + req.id);
});
if (req.id == 0) {
assertEquals("GET", req.method);
assertEquals("/hello", req.uri.path);
@ -24,10 +30,11 @@ http.createServer(function (req, res) {
assertEquals("POST", req.method);
assertEquals("/quit", req.uri.path);
this.close();
//puts("server closed");
puts("server closed");
}
setTimeout(function () {
puts("send response " + req.id);
res.sendHeader(200, {"Content-Type": "text/plain"});
res.sendBody(req.uri.path);
res.finish();
@ -40,7 +47,8 @@ var c = tcp.createConnection(port);
c.setEncoding("utf8");
c.addListener("connect", function () {
c.send( "GET /hello?hello=world&foo=b==ar HTTP/1.1\r\n\r\n" );
puts("client connected. sending first request");
c.send("GET /hello?hello=world&foo=b==ar HTTP/1.1\r\n\r\n" );
requests_sent += 1;
});
@ -48,7 +56,9 @@ c.addListener("receive", function (chunk) {
server_response += chunk;
if (requests_sent == 1) {
puts("send request 2");
c.send("POST /quit HTTP/1.1\r\n\r\n");
puts("close client");
c.close();
assertEquals(c.readyState, "readOnly");
requests_sent += 1;
@ -56,10 +66,12 @@ c.addListener("receive", function (chunk) {
});
c.addListener("eof", function () {
puts("client got eof");
client_got_eof = true;
});
c.addListener("close", function () {
puts("client closed");
assertEquals(c.readyState, "closed");
});

Loading…
Cancel
Save