mirror of https://github.com/lukechilds/node.git
Ryan
16 years ago
32 changed files with 7221 additions and 4011 deletions
@ -0,0 +1,75 @@ |
|||||
|
HTTP Parser |
||||
|
=========== |
||||
|
|
||||
|
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 100 bytes of data |
||||
|
per message stream (in a web server that is per connection). |
||||
|
|
||||
|
Features: |
||||
|
|
||||
|
* No dependencies |
||||
|
* Parses both requests and responses. |
||||
|
* Handles keep-alive streams. |
||||
|
* Decodes chunked encoding. |
||||
|
* Extracts the following data from a message |
||||
|
* header fields and values |
||||
|
* content-length |
||||
|
* request method |
||||
|
* response status code |
||||
|
* transfer-encoding |
||||
|
* http version |
||||
|
* request path, query string, fragment |
||||
|
* message body |
||||
|
|
||||
|
Usage |
||||
|
----- |
||||
|
|
||||
|
One `http_parser` object is used per TCP connection. Initialize the struct |
||||
|
using `http_parser_init()` and set the callbacks. That might look something |
||||
|
like this: |
||||
|
|
||||
|
http_parser *parser = malloc(sizeof(http_parser)); |
||||
|
http_parser_init(parser, HTTP_REQUEST); |
||||
|
parser->on_path = my_path_callback; |
||||
|
parser->on_header_field = my_header_field_callback; |
||||
|
parser->data = my_socket; |
||||
|
|
||||
|
When data is received on the socket execute the parser and check for errors. |
||||
|
|
||||
|
size_t len = 80*1024; |
||||
|
char buf[len]; |
||||
|
ssize_t recved; |
||||
|
|
||||
|
recved = read(fd, buf, len); |
||||
|
if (recved != 0) // handle error |
||||
|
|
||||
|
http_parser_execute(parser, buf, recved); |
||||
|
|
||||
|
if (http_parser_has_error(parser)) { |
||||
|
// handle error. usually just close the connection |
||||
|
} |
||||
|
|
||||
|
During the `http_parser_execute()` call, the callbacks set in `http_parser` |
||||
|
will be executed. The parser maintains state and never looks behind, so |
||||
|
buffering the data is not necessary. If you need to save certain data for |
||||
|
later usage, you can do that from the callbacks. (You can also `read()` into |
||||
|
a heap allocated buffer to avoid copying memory around if this fits your |
||||
|
application.) |
||||
|
|
||||
|
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. |
||||
|
|
||||
|
It does not decode the content-encoding (gzip). Not all HTTP applications |
||||
|
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. |
||||
|
|
||||
|
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). |
File diff suppressed because it is too large
@ -0,0 +1,127 @@ |
|||||
|
/* Copyright (c) 2008 Ryan Dahl (ry@tinyclouds.org)
|
||||
|
* All rights reserved. |
||||
|
* |
||||
|
* This parser is based on code from Zed Shaw's Mongrel. |
||||
|
* Copyright (c) 2005 Zed A. Shaw |
||||
|
* |
||||
|
* 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 rights to use, copy, modify, merge, publish, |
||||
|
* 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. |
||||
|
*/ |
||||
|
#ifndef http_parser_h |
||||
|
#define http_parser_h |
||||
|
#ifdef __cplusplus |
||||
|
extern "C" { |
||||
|
#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. |
||||
|
* |
||||
|
* 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. |
||||
|
*/ |
||||
|
typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); |
||||
|
typedef int (*http_cb) (http_parser*); |
||||
|
|
||||
|
/* Request Methods */ |
||||
|
#define HTTP_COPY 0x0001 |
||||
|
#define HTTP_DELETE 0x0002 |
||||
|
#define HTTP_GET 0x0004 |
||||
|
#define HTTP_HEAD 0x0008 |
||||
|
#define HTTP_LOCK 0x0010 |
||||
|
#define HTTP_MKCOL 0x0020 |
||||
|
#define HTTP_MOVE 0x0040 |
||||
|
#define HTTP_OPTIONS 0x0080 |
||||
|
#define HTTP_POST 0x0100 |
||||
|
#define HTTP_PROPFIND 0x0200 |
||||
|
#define HTTP_PROPPATCH 0x0400 |
||||
|
#define HTTP_PUT 0x0800 |
||||
|
#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 }; |
||||
|
|
||||
|
struct http_parser { |
||||
|
/** PRIVATE **/ |
||||
|
int cs; |
||||
|
enum http_parser_type type; |
||||
|
|
||||
|
size_t chunk_size; |
||||
|
unsigned eating:1; |
||||
|
size_t body_read; |
||||
|
|
||||
|
const char *header_field_mark; |
||||
|
const char *header_value_mark; |
||||
|
const char *query_string_mark; |
||||
|
const char *path_mark; |
||||
|
const char *uri_mark; |
||||
|
const char *fragment_mark; |
||||
|
|
||||
|
/** 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 keep_alive; |
||||
|
size_t content_length; |
||||
|
|
||||
|
/** PUBLIC **/ |
||||
|
void *data; /* A pointer to get hook to the "connection" or "socket" object */ |
||||
|
|
||||
|
/* an ordered list of callbacks */ |
||||
|
|
||||
|
http_cb on_message_begin; |
||||
|
|
||||
|
/* requests only */ |
||||
|
http_data_cb on_path; |
||||
|
http_data_cb on_query_string; |
||||
|
http_data_cb on_uri; |
||||
|
http_data_cb on_fragment; |
||||
|
|
||||
|
http_data_cb on_header_field; |
||||
|
http_data_cb on_header_value; |
||||
|
http_cb on_headers_complete; |
||||
|
http_data_cb on_body; |
||||
|
http_cb on_message_complete; |
||||
|
}; |
||||
|
|
||||
|
/* Initializes an http_parser structure. The second argument specifies if
|
||||
|
* 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); |
||||
|
|
||||
|
int http_parser_has_error (http_parser *parser); |
||||
|
|
||||
|
int http_parser_should_keep_alive (http_parser *parser); |
||||
|
|
||||
|
#ifdef __cplusplus |
||||
|
} |
||||
|
#endif |
||||
|
#endif |
@ -0,0 +1,424 @@ |
|||||
|
/* Copyright (c) 2008, 2009 Ryan Dahl (ry@tinyclouds.org) |
||||
|
* |
||||
|
* Based on Zed Shaw's Mongrel. |
||||
|
* Copyright (c) 2005 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 |
||||
|
* without limitation the rights to use, copy, modify, merge, publish, |
||||
|
* 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. |
||||
|
*/ |
||||
|
#include "http_parser.h" |
||||
|
|
||||
|
#include <assert.h> |
||||
|
|
||||
|
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 |
||||
|
, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-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 |
||||
|
,-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 REMAINING (pe - p) |
||||
|
#define CALLBACK(FOR) \ |
||||
|
if (parser->FOR##_mark && parser->on_##FOR) { \ |
||||
|
callback_return_value = \ |
||||
|
parser->on_##FOR(parser, parser->FOR##_mark, p - parser->FOR##_mark); \ |
||||
|
} |
||||
|
|
||||
|
#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; |
||||
|
|
||||
|
#define END_REQUEST \ |
||||
|
do { \ |
||||
|
if (parser->on_message_complete) { \ |
||||
|
callback_return_value = \ |
||||
|
parser->on_message_complete(parser); \ |
||||
|
} \ |
||||
|
RESET_PARSER(parser); \ |
||||
|
} while (0) |
||||
|
|
||||
|
#define SKIP_BODY(nskip) \ |
||||
|
do { \ |
||||
|
tmp = (nskip); \ |
||||
|
if (parser->on_body && tmp > 0) { \ |
||||
|
callback_return_value = parser->on_body(parser, p, tmp); \ |
||||
|
} \ |
||||
|
if (callback_return_value == 0) { \ |
||||
|
p += tmp; \ |
||||
|
parser->body_read += tmp; \ |
||||
|
parser->chunk_size -= tmp; \ |
||||
|
if (0 == parser->chunk_size) { \ |
||||
|
parser->eating = FALSE; \ |
||||
|
if (parser->transfer_encoding == HTTP_IDENTITY) { \ |
||||
|
END_REQUEST; \ |
||||
|
} \ |
||||
|
} else { \ |
||||
|
parser->eating = TRUE; \ |
||||
|
} \ |
||||
|
} \ |
||||
|
} while (0) |
||||
|
|
||||
|
%%{ |
||||
|
machine http_parser; |
||||
|
|
||||
|
action mark_header_field { parser->header_field_mark = p; } |
||||
|
action mark_header_value { parser->header_value_mark = p; } |
||||
|
action mark_fragment { parser->fragment_mark = p; } |
||||
|
action mark_query_string { parser->query_string_mark = p; } |
||||
|
action mark_request_path { parser->path_mark = p; } |
||||
|
action mark_request_uri { parser->uri_mark = p; } |
||||
|
|
||||
|
action header_field { |
||||
|
CALLBACK(header_field); |
||||
|
if (callback_return_value != 0) fbreak; |
||||
|
parser->header_field_mark = NULL; |
||||
|
} |
||||
|
|
||||
|
action header_value { |
||||
|
CALLBACK(header_value); |
||||
|
if (callback_return_value != 0) fbreak; |
||||
|
parser->header_value_mark = NULL; |
||||
|
} |
||||
|
|
||||
|
action request_uri { |
||||
|
CALLBACK(uri); |
||||
|
if (callback_return_value != 0) fbreak; |
||||
|
parser->uri_mark = NULL; |
||||
|
} |
||||
|
|
||||
|
action fragment { |
||||
|
CALLBACK(fragment); |
||||
|
if (callback_return_value != 0) fbreak; |
||||
|
parser->fragment_mark = NULL; |
||||
|
} |
||||
|
|
||||
|
action query_string { |
||||
|
CALLBACK(query_string); |
||||
|
if (callback_return_value != 0) fbreak; |
||||
|
parser->query_string_mark = NULL; |
||||
|
} |
||||
|
|
||||
|
action request_path { |
||||
|
CALLBACK(path); |
||||
|
if (callback_return_value != 0) fbreak; |
||||
|
parser->path_mark = NULL; |
||||
|
} |
||||
|
|
||||
|
action headers_complete { |
||||
|
if(parser->on_headers_complete) { |
||||
|
callback_return_value = parser->on_headers_complete(parser); |
||||
|
if (callback_return_value != 0) fbreak; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
action begin_message { |
||||
|
if(parser->on_message_begin) { |
||||
|
callback_return_value = parser->on_message_begin(parser); |
||||
|
if (callback_return_value != 0) fbreak; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
action content_length { |
||||
|
parser->content_length *= 10; |
||||
|
parser->content_length += *p - '0'; |
||||
|
} |
||||
|
|
||||
|
action status_code { |
||||
|
parser->status_code *= 10; |
||||
|
parser->status_code += *p - '0'; |
||||
|
} |
||||
|
|
||||
|
action use_identity_encoding { parser->transfer_encoding = HTTP_IDENTITY; } |
||||
|
action use_chunked_encoding { parser->transfer_encoding = HTTP_CHUNKED; } |
||||
|
|
||||
|
action set_keep_alive { parser->keep_alive = TRUE; } |
||||
|
action set_not_keep_alive { parser->keep_alive = FALSE; } |
||||
|
|
||||
|
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 add_to_chunk_size { |
||||
|
parser->chunk_size *= 16; |
||||
|
parser->chunk_size += unhex[(int)*p]; |
||||
|
} |
||||
|
|
||||
|
action skip_chunk_data { |
||||
|
SKIP_BODY(MIN(parser->chunk_size, REMAINING)); |
||||
|
if (callback_return_value != 0) fbreak; |
||||
|
|
||||
|
fhold; |
||||
|
if (parser->chunk_size > REMAINING) { |
||||
|
fbreak; |
||||
|
} else { |
||||
|
fgoto chunk_end; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
action end_chunked_body { |
||||
|
END_REQUEST; |
||||
|
if (parser->type == HTTP_REQUEST) { |
||||
|
fnext Requests; |
||||
|
} else { |
||||
|
fnext Responses; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
action body_logic { |
||||
|
if (parser->transfer_encoding == HTTP_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; |
||||
|
|
||||
|
SKIP_BODY(MIN(REMAINING, parser->content_length)); |
||||
|
if (callback_return_value != 0) fbreak; |
||||
|
|
||||
|
fhold; |
||||
|
if(parser->chunk_size > REMAINING) { |
||||
|
fbreak; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
CRLF = "\r\n"; |
||||
|
|
||||
|
# character types |
||||
|
CTL = (cntrl | 127); |
||||
|
safe = ("$" | "-" | "_" | "."); |
||||
|
extra = ("!" | "*" | "'" | "(" | ")" | ","); |
||||
|
reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+"); |
||||
|
unsafe = (CTL | " " | "\"" | "#" | "%" | "<" | ">"); |
||||
|
national = any -- (alpha | digit | reserved | extra | safe | unsafe); |
||||
|
unreserved = (alpha | digit | safe | extra | national); |
||||
|
escape = ("%" xdigit xdigit); |
||||
|
uchar = (unreserved | escape); |
||||
|
pchar = (uchar | ":" | "@" | "&" | "=" | "+"); |
||||
|
tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" |
||||
|
| "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t"); |
||||
|
|
||||
|
# elements |
||||
|
token = (ascii -- (CTL | tspecials)); |
||||
|
quote = "\""; |
||||
|
# qdtext = token -- "\""; |
||||
|
# quoted_pair = "\" ascii; |
||||
|
# quoted_string = "\"" (qdtext | quoted_pair )* "\""; |
||||
|
|
||||
|
# headers |
||||
|
|
||||
|
Method = ( "COPY" %{ parser->method = HTTP_COPY; } |
||||
|
| "DELETE" %{ parser->method = HTTP_DELETE; } |
||||
|
| "GET" %{ parser->method = HTTP_GET; } |
||||
|
| "HEAD" %{ parser->method = HTTP_HEAD; } |
||||
|
| "LOCK" %{ parser->method = HTTP_LOCK; } |
||||
|
| "MKCOL" %{ parser->method = HTTP_MKCOL; } |
||||
|
| "MOVE" %{ parser->method = HTTP_MOVE; } |
||||
|
| "OPTIONS" %{ parser->method = HTTP_OPTIONS; } |
||||
|
| "POST" %{ parser->method = HTTP_POST; } |
||||
|
| "PROPFIND" %{ parser->method = HTTP_PROPFIND; } |
||||
|
| "PROPPATCH" %{ parser->method = HTTP_PROPPATCH; } |
||||
|
| "PUT" %{ parser->method = HTTP_PUT; } |
||||
|
| "TRACE" %{ parser->method = HTTP_TRACE; } |
||||
|
| "UNLOCK" %{ parser->method = HTTP_UNLOCK; } |
||||
|
); # Not allowing extension methods |
||||
|
|
||||
|
HTTP_Version = "HTTP/" digit+ $version_major "." digit+ $version_minor; |
||||
|
|
||||
|
scheme = ( alpha | digit | "+" | "-" | "." )* ; |
||||
|
absolute_uri = (scheme ":" (uchar | reserved )*); |
||||
|
path = ( pchar+ ( "/" pchar* )* ) ; |
||||
|
query = ( uchar | reserved )* >mark_query_string %query_string ; |
||||
|
param = ( pchar | "/" )* ; |
||||
|
params = ( param ( ";" param )* ) ; |
||||
|
rel_path = ( path? (";" params)? ) ; |
||||
|
absolute_path = ( "/"+ rel_path ) >mark_request_path %request_path ("?" query)?; |
||||
|
Request_URI = ( "*" | absolute_uri | absolute_path ) >mark_request_uri %request_uri; |
||||
|
Fragment = ( uchar | reserved )* >mark_fragment %fragment; |
||||
|
|
||||
|
field_name = ( token -- ":" )+; |
||||
|
Field_Name = field_name >mark_header_field %header_field; |
||||
|
|
||||
|
field_value = ((any - " ") any*)?; |
||||
|
Field_Value = field_value >mark_header_value %header_value; |
||||
|
|
||||
|
hsep = ":" " "*; |
||||
|
header = (field_name hsep field_value) :> CRLF; |
||||
|
Header = ( ("Content-Length"i hsep digit+ $content_length) |
||||
|
| ("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) |
||||
|
| (Field_Name hsep Field_Value) |
||||
|
) :> CRLF; |
||||
|
|
||||
|
Headers = (Header)* :> CRLF @headers_complete; |
||||
|
|
||||
|
Request_Line = ( Method " " Request_URI ("#" Fragment)? " " HTTP_Version CRLF ) ; |
||||
|
|
||||
|
StatusCode = (digit digit digit) $status_code; |
||||
|
ReasonPhrase = ascii+ -- ("\r" | "\n"); |
||||
|
StatusLine = HTTP_Version " " StatusCode " " ReasonPhrase CRLF; |
||||
|
|
||||
|
# chunked message |
||||
|
trailing_headers = header*; |
||||
|
#chunk_ext_val = token | quoted_string; |
||||
|
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; |
||||
|
chunk_end = CRLF; |
||||
|
chunk_body = any >skip_chunk_data; |
||||
|
chunk_begin = chunk_size chunk_extension CRLF; |
||||
|
chunk = chunk_begin chunk_body chunk_end; |
||||
|
ChunkedBody := chunk* last_chunk trailing_headers CRLF @end_chunked_body; |
||||
|
|
||||
|
Request = (Request_Line Headers) >begin_message @body_logic; |
||||
|
Response = (StatusLine Headers) >begin_message @body_logic; |
||||
|
|
||||
|
Requests := Request*; |
||||
|
Responses := Response*; |
||||
|
|
||||
|
main := any >{ |
||||
|
fhold; |
||||
|
if (parser->type == HTTP_REQUEST) { |
||||
|
fgoto Requests; |
||||
|
} else { |
||||
|
fgoto Responses; |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
}%% |
||||
|
|
||||
|
%% write data; |
||||
|
|
||||
|
void |
||||
|
http_parser_init (http_parser *parser, enum http_parser_type type) |
||||
|
{ |
||||
|
int cs = 0; |
||||
|
%% write init; |
||||
|
parser->cs = cs; |
||||
|
parser->type = type; |
||||
|
|
||||
|
parser->on_message_begin = NULL; |
||||
|
parser->on_path = NULL; |
||||
|
parser->on_query_string = NULL; |
||||
|
parser->on_uri = NULL; |
||||
|
parser->on_header_field = NULL; |
||||
|
parser->on_header_value = NULL; |
||||
|
parser->on_headers_complete = NULL; |
||||
|
parser->on_body = NULL; |
||||
|
parser->on_message_complete = NULL; |
||||
|
|
||||
|
RESET_PARSER(parser); |
||||
|
} |
||||
|
|
||||
|
/** exec **/ |
||||
|
size_t |
||||
|
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; |
||||
|
int cs = parser->cs; |
||||
|
|
||||
|
p = buffer; |
||||
|
pe = buffer+len; |
||||
|
|
||||
|
if (0 < parser->chunk_size && parser->eating) { |
||||
|
/* eat body */ |
||||
|
SKIP_BODY(MIN(len, parser->chunk_size)); |
||||
|
if (callback_return_value != 0) goto out; |
||||
|
} |
||||
|
|
||||
|
if (parser->header_field_mark) parser->header_field_mark = buffer; |
||||
|
if (parser->header_value_mark) parser->header_value_mark = buffer; |
||||
|
if (parser->fragment_mark) parser->fragment_mark = buffer; |
||||
|
if (parser->query_string_mark) parser->query_string_mark = buffer; |
||||
|
if (parser->path_mark) parser->path_mark = buffer; |
||||
|
if (parser->uri_mark) parser->uri_mark = buffer; |
||||
|
|
||||
|
%% write exec; |
||||
|
|
||||
|
parser->cs = cs; |
||||
|
|
||||
|
CALLBACK(header_field); |
||||
|
CALLBACK(header_value); |
||||
|
CALLBACK(fragment); |
||||
|
CALLBACK(query_string); |
||||
|
CALLBACK(path); |
||||
|
CALLBACK(uri); |
||||
|
|
||||
|
out: |
||||
|
assert(p <= pe && "buffer overflow after parsing execute"); |
||||
|
return(p - buffer); |
||||
|
} |
||||
|
|
||||
|
int |
||||
|
http_parser_has_error (http_parser *parser) |
||||
|
{ |
||||
|
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; |
||||
|
} |
@ -0,0 +1,748 @@ |
|||||
|
#include "http_parser.h" |
||||
|
#include <stdlib.h> |
||||
|
#include <assert.h> |
||||
|
#include <stdio.h> |
||||
|
#include <string.h> |
||||
|
#include <stdarg.h> |
||||
|
|
||||
|
#undef TRUE |
||||
|
#define TRUE 1 |
||||
|
#undef FALSE |
||||
|
#define FALSE 0 |
||||
|
|
||||
|
#define MAX_HEADERS 10 |
||||
|
#define MAX_ELEMENT_SIZE 500 |
||||
|
|
||||
|
static http_parser parser; |
||||
|
struct message { |
||||
|
const char *name; // for debugging purposes
|
||||
|
const char *raw; |
||||
|
enum http_parser_type type; |
||||
|
int method; |
||||
|
int status_code; |
||||
|
char request_path[MAX_ELEMENT_SIZE]; |
||||
|
char request_uri[MAX_ELEMENT_SIZE]; |
||||
|
char fragment[MAX_ELEMENT_SIZE]; |
||||
|
char query_string[MAX_ELEMENT_SIZE]; |
||||
|
char body[MAX_ELEMENT_SIZE]; |
||||
|
int num_headers; |
||||
|
enum { NONE=0, FIELD, VALUE } last_header_element; |
||||
|
char headers [MAX_HEADERS][2][MAX_ELEMENT_SIZE]; |
||||
|
int should_keep_alive; |
||||
|
}; |
||||
|
|
||||
|
static struct message messages[5]; |
||||
|
static int num_messages; |
||||
|
|
||||
|
/* * R E Q U E S T S * */ |
||||
|
const struct message requests[] = |
||||
|
#define CURL_GET 0 |
||||
|
{ {.name= "curl get" |
||||
|
,.type= HTTP_REQUEST |
||||
|
,.raw= "GET /test HTTP/1.1\r\n" |
||||
|
"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\r\n" |
||||
|
"Host: 0.0.0.0=5000\r\n" |
||||
|
"Accept: */*\r\n" |
||||
|
"\r\n" |
||||
|
,.should_keep_alive= TRUE |
||||
|
,.method= HTTP_GET |
||||
|
,.query_string= "" |
||||
|
,.fragment= "" |
||||
|
,.request_path= "/test" |
||||
|
,.request_uri= "/test" |
||||
|
,.num_headers= 3 |
||||
|
,.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", "*/*" } |
||||
|
} |
||||
|
,.body= "" |
||||
|
} |
||||
|
|
||||
|
#define FIREFOX_GET 1 |
||||
|
, {.name= "firefox get" |
||||
|
,.type= HTTP_REQUEST |
||||
|
,.raw= "GET /favicon.ico HTTP/1.1\r\n" |
||||
|
"Host: 0.0.0.0=5000\r\n" |
||||
|
"User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n" |
||||
|
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" |
||||
|
"Accept-Language: en-us,en;q=0.5\r\n" |
||||
|
"Accept-Encoding: gzip,deflate\r\n" |
||||
|
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" |
||||
|
"Keep-Alive: 300\r\n" |
||||
|
"Connection: keep-alive\r\n" |
||||
|
"\r\n" |
||||
|
,.should_keep_alive= TRUE |
||||
|
,.method= HTTP_GET |
||||
|
,.query_string= "" |
||||
|
,.fragment= "" |
||||
|
,.request_path= "/favicon.ico" |
||||
|
,.request_uri= "/favicon.ico" |
||||
|
,.num_headers= 8 |
||||
|
,.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" } |
||||
|
, { "Accept-Language", "en-us,en;q=0.5" } |
||||
|
, { "Accept-Encoding", "gzip,deflate" } |
||||
|
, { "Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7" } |
||||
|
, { "Keep-Alive", "300" } |
||||
|
, { "Connection", "keep-alive" } |
||||
|
} |
||||
|
,.body= "" |
||||
|
} |
||||
|
|
||||
|
#define DUMBFUCK 2 |
||||
|
, {.name= "dumbfuck" |
||||
|
,.type= HTTP_REQUEST |
||||
|
,.raw= "GET /dumbfuck HTTP/1.1\r\n" |
||||
|
"aaaaaaaaaaaaa:++++++++++\r\n" |
||||
|
"\r\n" |
||||
|
,.should_keep_alive= TRUE |
||||
|
,.method= HTTP_GET |
||||
|
,.query_string= "" |
||||
|
,.fragment= "" |
||||
|
,.request_path= "/dumbfuck" |
||||
|
,.request_uri= "/dumbfuck" |
||||
|
,.num_headers= 1 |
||||
|
,.headers= |
||||
|
{ { "aaaaaaaaaaaaa", "++++++++++" } |
||||
|
} |
||||
|
,.body= "" |
||||
|
} |
||||
|
|
||||
|
#define FRAGMENT_IN_URI 3 |
||||
|
, {.name= "fragment in uri" |
||||
|
,.type= HTTP_REQUEST |
||||
|
,.raw= "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n" |
||||
|
"\r\n" |
||||
|
,.should_keep_alive= TRUE |
||||
|
,.method= HTTP_GET |
||||
|
,.query_string= "page=1" |
||||
|
,.fragment= "posts-17408" |
||||
|
,.request_path= "/forums/1/topics/2375" |
||||
|
/* XXX request uri does not include fragment? */ |
||||
|
,.request_uri= "/forums/1/topics/2375?page=1" |
||||
|
,.num_headers= 0 |
||||
|
,.body= "" |
||||
|
} |
||||
|
|
||||
|
#define GET_NO_HEADERS_NO_BODY 4 |
||||
|
, {.name= "get no headers no body" |
||||
|
,.type= HTTP_REQUEST |
||||
|
,.raw= "GET /get_no_headers_no_body/world HTTP/1.1\r\n" |
||||
|
"\r\n" |
||||
|
,.should_keep_alive= TRUE |
||||
|
,.method= HTTP_GET |
||||
|
,.query_string= "" |
||||
|
,.fragment= "" |
||||
|
,.request_path= "/get_no_headers_no_body/world" |
||||
|
,.request_uri= "/get_no_headers_no_body/world" |
||||
|
,.num_headers= 0 |
||||
|
,.body= "" |
||||
|
} |
||||
|
|
||||
|
#define GET_ONE_HEADER_NO_BODY 5 |
||||
|
, {.name= "get one header no body" |
||||
|
,.type= HTTP_REQUEST |
||||
|
,.raw= "GET /get_one_header_no_body HTTP/1.1\r\n" |
||||
|
"Accept: */*\r\n" |
||||
|
"\r\n" |
||||
|
,.should_keep_alive= TRUE |
||||
|
,.method= HTTP_GET |
||||
|
,.query_string= "" |
||||
|
,.fragment= "" |
||||
|
,.request_path= "/get_one_header_no_body" |
||||
|
,.request_uri= "/get_one_header_no_body" |
||||
|
,.num_headers= 1 |
||||
|
,.headers= |
||||
|
{ { "Accept" , "*/*" } |
||||
|
} |
||||
|
,.body= "" |
||||
|
} |
||||
|
|
||||
|
#define GET_FUNKY_CONTENT_LENGTH 6 |
||||
|
, {.name= "get funky content length body hello" |
||||
|
,.type= HTTP_REQUEST |
||||
|
,.raw= "GET /get_funky_content_length_body_hello HTTP/1.0\r\n" |
||||
|
"conTENT-Length: 5\r\n" |
||||
|
"\r\n" |
||||
|
"HELLO" |
||||
|
,.should_keep_alive= FALSE |
||||
|
,.method= HTTP_GET |
||||
|
,.query_string= "" |
||||
|
,.fragment= "" |
||||
|
,.request_path= "/get_funky_content_length_body_hello" |
||||
|
,.request_uri= "/get_funky_content_length_body_hello" |
||||
|
,.num_headers= 1 |
||||
|
,.headers= |
||||
|
{ { "conTENT-Length" , "5" } |
||||
|
} |
||||
|
,.body= "HELLO" |
||||
|
} |
||||
|
|
||||
|
#define POST_IDENTITY_BODY_WORLD 7 |
||||
|
, {.name= "post identity body world" |
||||
|
,.type= HTTP_REQUEST |
||||
|
,.raw= "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n" |
||||
|
"Accept: */*\r\n" |
||||
|
"Transfer-Encoding: identity\r\n" |
||||
|
"Content-Length: 5\r\n" |
||||
|
"\r\n" |
||||
|
"World" |
||||
|
,.should_keep_alive= TRUE |
||||
|
,.method= HTTP_POST |
||||
|
,.query_string= "q=search" |
||||
|
,.fragment= "hey" |
||||
|
,.request_path= "/post_identity_body_world" |
||||
|
,.request_uri= "/post_identity_body_world?q=search" |
||||
|
,.num_headers= 3 |
||||
|
,.headers= |
||||
|
{ { "Accept", "*/*" } |
||||
|
, { "Transfer-Encoding", "identity" } |
||||
|
, { "Content-Length", "5" } |
||||
|
} |
||||
|
,.body= "World" |
||||
|
} |
||||
|
|
||||
|
#define POST_CHUNKED_ALL_YOUR_BASE 8 |
||||
|
, {.name= "post - chunked body: all your base are belong to us" |
||||
|
,.type= HTTP_REQUEST |
||||
|
,.raw= "POST /post_chunked_all_your_base HTTP/1.1\r\n" |
||||
|
"Transfer-Encoding: chunked\r\n" |
||||
|
"\r\n" |
||||
|
"1e\r\nall your base are belong to us\r\n" |
||||
|
"0\r\n" |
||||
|
"\r\n" |
||||
|
,.should_keep_alive= TRUE |
||||
|
,.method= HTTP_POST |
||||
|
,.query_string= "" |
||||
|
,.fragment= "" |
||||
|
,.request_path= "/post_chunked_all_your_base" |
||||
|
,.request_uri= "/post_chunked_all_your_base" |
||||
|
,.num_headers= 1 |
||||
|
,.headers= |
||||
|
{ { "Transfer-Encoding" , "chunked" } |
||||
|
} |
||||
|
,.body= "all your base are belong to us" |
||||
|
} |
||||
|
|
||||
|
#define TWO_CHUNKS_MULT_ZERO_END 9 |
||||
|
, {.name= "two chunks ; triple zero ending" |
||||
|
,.type= HTTP_REQUEST |
||||
|
,.raw= "POST /two_chunks_mult_zero_end HTTP/1.1\r\n" |
||||
|
"Transfer-Encoding: chunked\r\n" |
||||
|
"\r\n" |
||||
|
"5\r\nhello\r\n" |
||||
|
"6\r\n world\r\n" |
||||
|
"000\r\n" |
||||
|
"\r\n" |
||||
|
,.should_keep_alive= TRUE |
||||
|
,.method= HTTP_POST |
||||
|
,.query_string= "" |
||||
|
,.fragment= "" |
||||
|
,.request_path= "/two_chunks_mult_zero_end" |
||||
|
,.request_uri= "/two_chunks_mult_zero_end" |
||||
|
,.num_headers= 1 |
||||
|
,.headers= |
||||
|
{ { "Transfer-Encoding", "chunked" } |
||||
|
} |
||||
|
,.body= "hello world" |
||||
|
} |
||||
|
|
||||
|
#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" |
||||
|
"Transfer-Encoding: chunked\r\n" |
||||
|
"\r\n" |
||||
|
"5\r\nhello\r\n" |
||||
|
"6\r\n world\r\n" |
||||
|
"0\r\n" |
||||
|
"Vary: *\r\n" |
||||
|
"Content-Type: text/plain\r\n" |
||||
|
"\r\n" |
||||
|
,.should_keep_alive= TRUE |
||||
|
,.method= HTTP_POST |
||||
|
,.query_string= "" |
||||
|
,.fragment= "" |
||||
|
,.request_path= "/chunked_w_trailing_headers" |
||||
|
,.request_uri= "/chunked_w_trailing_headers" |
||||
|
,.num_headers= 1 |
||||
|
,.headers= |
||||
|
{ { "Transfer-Encoding", "chunked" } |
||||
|
} |
||||
|
,.body= "hello world" |
||||
|
} |
||||
|
|
||||
|
#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" |
||||
|
"Transfer-Encoding: chunked\r\n" |
||||
|
"\r\n" |
||||
|
"5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n" |
||||
|
"6; blahblah; blah\r\n world\r\n" |
||||
|
"0\r\n" |
||||
|
"\r\n" |
||||
|
,.should_keep_alive= TRUE |
||||
|
,.method= HTTP_POST |
||||
|
,.query_string= "" |
||||
|
,.fragment= "" |
||||
|
,.request_path= "/chunked_w_bullshit_after_length" |
||||
|
,.request_uri= "/chunked_w_bullshit_after_length" |
||||
|
,.num_headers= 1 |
||||
|
,.headers= |
||||
|
{ { "Transfer-Encoding", "chunked" } |
||||
|
} |
||||
|
,.body= "hello world" |
||||
|
} |
||||
|
|
||||
|
, {.name= NULL } /* sentinel */ |
||||
|
}; |
||||
|
|
||||
|
/* * 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" |
||||
|
"Location: http://www.google.com/\r\n" |
||||
|
"Content-Type: text/html; charset=UTF-8\r\n" |
||||
|
"Date: Sun, 26 Apr 2009 11:11:49 GMT\r\n" |
||||
|
"Expires: Tue, 26 May 2009 11:11:49 GMT\r\n" |
||||
|
"Cache-Control: public, max-age=2592000\r\n" |
||||
|
"Server: gws\r\n" |
||||
|
"Content-Length: 219\r\n" |
||||
|
"\r\n" |
||||
|
"<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n" |
||||
|
"<TITLE>301 Moved</TITLE></HEAD><BODY>\n" |
||||
|
"<H1>301 Moved</H1>\n" |
||||
|
"The document has moved\n" |
||||
|
"<A HREF=\"http://www.google.com/\">here</A>.\r\n" |
||||
|
"</BODY></HTML>\r\n" |
||||
|
,.should_keep_alive= TRUE |
||||
|
,.status_code= 301 |
||||
|
,.num_headers= 7 |
||||
|
,.headers= |
||||
|
{ { "Location", "http://www.google.com/" } |
||||
|
, { "Content-Type", "text/html; charset=UTF-8" } |
||||
|
, { "Date", "Sun, 26 Apr 2009 11:11:49 GMT" } |
||||
|
, { "Expires", "Tue, 26 May 2009 11:11:49 GMT" } |
||||
|
, { "Cache-Control", "public, max-age=2592000" } |
||||
|
, { "Server", "gws" } |
||||
|
, { "Content-Length", "219" } |
||||
|
} |
||||
|
,.body= "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n" |
||||
|
"<TITLE>301 Moved</TITLE></HEAD><BODY>\n" |
||||
|
"<H1>301 Moved</H1>\n" |
||||
|
"The document has moved\n" |
||||
|
"<A HREF=\"http://www.google.com/\">here</A>.\r\n" |
||||
|
"</BODY></HTML>\r\n" |
||||
|
} |
||||
|
|
||||
|
, {.name= "404 no headers no body" |
||||
|
,.type= HTTP_RESPONSE |
||||
|
,.raw= "HTTP/1.1 404 Not Found\r\n\r\n" |
||||
|
,.should_keep_alive= TRUE |
||||
|
,.status_code= 404 |
||||
|
,.num_headers= 0 |
||||
|
,.headers= {} |
||||
|
,.body= "" |
||||
|
} |
||||
|
|
||||
|
, {.name= NULL } /* sentinel */ |
||||
|
}; |
||||
|
|
||||
|
int |
||||
|
request_path_cb (http_parser *_, const char *p, size_t len) |
||||
|
{ |
||||
|
strncat(messages[num_messages].request_path, p, len); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
int |
||||
|
request_uri_cb (http_parser *_, const char *p, size_t len) |
||||
|
{ |
||||
|
strncat(messages[num_messages].request_uri, p, len); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
int |
||||
|
query_string_cb (http_parser *_, const char *p, size_t len) |
||||
|
{ |
||||
|
strncat(messages[num_messages].query_string, p, len); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
int |
||||
|
fragment_cb (http_parser *_, const char *p, size_t len) |
||||
|
{ |
||||
|
strncat(messages[num_messages].fragment, p, len); |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
int |
||||
|
header_field_cb (http_parser *_, const char *p, size_t len) |
||||
|
{ |
||||
|
struct message *m = &messages[num_messages]; |
||||
|
|
||||
|
if (m->last_header_element != FIELD) |
||||
|
m->num_headers++; |
||||
|
|
||||
|
strncat(m->headers[m->num_headers-1][0], p, len); |
||||
|
|
||||
|
m->last_header_element = FIELD; |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
int |
||||
|
header_value_cb (http_parser *_, const char *p, size_t len) |
||||
|
{ |
||||
|
struct message *m = &messages[num_messages]; |
||||
|
|
||||
|
strncat(m->headers[m->num_headers-1][1], p, len); |
||||
|
|
||||
|
m->last_header_element = VALUE; |
||||
|
|
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
int |
||||
|
body_cb (http_parser *_, const char *p, size_t len) |
||||
|
{ |
||||
|
strncat(messages[num_messages].body, p, len); |
||||
|
// printf("body_cb: '%s'\n", requests[num_messages].body);
|
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
int |
||||
|
message_complete_cb (http_parser *parser) |
||||
|
{ |
||||
|
messages[num_messages].method = parser->method; |
||||
|
messages[num_messages].status_code = parser->status_code; |
||||
|
|
||||
|
num_messages++; |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
int |
||||
|
message_begin_cb (http_parser *_) |
||||
|
{ |
||||
|
return 0; |
||||
|
} |
||||
|
|
||||
|
void |
||||
|
parser_init (enum http_parser_type type) |
||||
|
{ |
||||
|
num_messages = 0; |
||||
|
|
||||
|
http_parser_init(&parser, type); |
||||
|
|
||||
|
memset(&messages, 0, sizeof messages); |
||||
|
|
||||
|
parser.on_message_begin = message_begin_cb; |
||||
|
parser.on_header_field = header_field_cb; |
||||
|
parser.on_header_value = header_value_cb; |
||||
|
parser.on_path = request_path_cb; |
||||
|
parser.on_uri = request_uri_cb; |
||||
|
parser.on_fragment = fragment_cb; |
||||
|
parser.on_query_string = query_string_cb; |
||||
|
parser.on_body = body_cb; |
||||
|
parser.on_headers_complete = NULL; |
||||
|
parser.on_message_complete = message_complete_cb; |
||||
|
} |
||||
|
|
||||
|
void |
||||
|
message_eq (int index, const struct message *expected) |
||||
|
{ |
||||
|
int i; |
||||
|
struct message *m = &messages[index]; |
||||
|
|
||||
|
assert(m->method == expected->method); |
||||
|
assert(m->status_code == expected->status_code); |
||||
|
|
||||
|
assert(0 == strcmp(m->body, expected->body)); |
||||
|
assert(0 == strcmp(m->fragment, expected->fragment)); |
||||
|
assert(0 == strcmp(m->query_string, expected->query_string)); |
||||
|
assert(0 == strcmp(m->request_path, expected->request_path)); |
||||
|
assert(0 == strcmp(m->request_uri, expected->request_uri)); |
||||
|
assert(m->num_headers == expected->num_headers); |
||||
|
for (i = 0; i < m->num_headers; i++) { |
||||
|
assert(0 == strcmp(m->headers[i][0], expected->headers[i][0])); |
||||
|
assert(0 == strcmp(m->headers[i][1], expected->headers[i][1])); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
void |
||||
|
parse_messages (int message_count, const struct message *input_messages[]) |
||||
|
{ |
||||
|
// Concat the input messages
|
||||
|
size_t length = 0; |
||||
|
int i; |
||||
|
for (i = 0; i < message_count; i++) { |
||||
|
length += strlen(input_messages[i]->raw); |
||||
|
} |
||||
|
char total[length + 1]; |
||||
|
total[0] = '\0'; |
||||
|
|
||||
|
for (i = 0; i < message_count; i++) { |
||||
|
strcat(total, input_messages[i]->raw); |
||||
|
} |
||||
|
|
||||
|
// Parse the stream
|
||||
|
size_t traversed = 0; |
||||
|
parser_init(HTTP_REQUEST); |
||||
|
|
||||
|
traversed = http_parser_execute(&parser, total, length); |
||||
|
|
||||
|
assert(!http_parser_has_error(&parser)); |
||||
|
assert(num_messages == message_count); |
||||
|
|
||||
|
for (i = 0; i < message_count; i++) { |
||||
|
message_eq(i, input_messages[i]); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
|
||||
|
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)); |
||||
|
assert(!http_parser_has_error(&parser)); |
||||
|
assert(num_messages == 1); |
||||
|
|
||||
|
message_eq(0, message); |
||||
|
} |
||||
|
|
||||
|
void |
||||
|
test_error (const char *buf) |
||||
|
{ |
||||
|
size_t traversed = 0; |
||||
|
parser_init(HTTP_REQUEST); |
||||
|
|
||||
|
traversed = http_parser_execute(&parser, buf, strlen(buf)); |
||||
|
|
||||
|
assert(http_parser_has_error(&parser)); |
||||
|
} |
||||
|
|
||||
|
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) |
||||
|
+ 1 |
||||
|
]; |
||||
|
total[0] = '\0'; |
||||
|
|
||||
|
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)); |
||||
|
|
||||
|
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
|
||||
|
* parser can handle getting the content in any chunks that |
||||
|
* might come from the socket |
||||
|
*/ |
||||
|
void |
||||
|
test_scan (const struct message *r1, const struct message *r2, const struct message *r3) |
||||
|
{ |
||||
|
char total[80*1024] = "\0"; |
||||
|
char buf1[80*1024] = "\0"; |
||||
|
char buf2[80*1024] = "\0"; |
||||
|
char buf3[80*1024] = "\0"; |
||||
|
|
||||
|
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 ops = 0 ; |
||||
|
|
||||
|
int i,j; |
||||
|
for (j = 2; j < total_len; j ++ ) { |
||||
|
for (i = 1; i < j; i ++ ) { |
||||
|
|
||||
|
if (ops % 1000 == 0) { |
||||
|
printf("\b\b\b\b%3.0f%%", 100 * (float)ops /(float)total_ops); |
||||
|
fflush(stdout); |
||||
|
} |
||||
|
ops += 1; |
||||
|
|
||||
|
parser_init(HTTP_REQUEST); |
||||
|
|
||||
|
int buf1_len = i; |
||||
|
strncpy(buf1, total, buf1_len); |
||||
|
buf1[buf1_len] = 0; |
||||
|
|
||||
|
int buf2_len = j - i; |
||||
|
strncpy(buf2, total+i, buf2_len); |
||||
|
buf2[buf2_len] = 0; |
||||
|
|
||||
|
int buf3_len = total_len - j; |
||||
|
strncpy(buf3, total+j, buf3_len); |
||||
|
buf3[buf3_len] = 0; |
||||
|
|
||||
|
/*
|
||||
|
printf("buf1: %s - %d\n", buf1, buf1_len); |
||||
|
printf("buf2: %s - %d \n", buf2, buf2_len ); |
||||
|
printf("buf3: %s - %d\n\n", buf3, buf3_len); |
||||
|
*/ |
||||
|
|
||||
|
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(3 == num_messages); |
||||
|
|
||||
|
message_eq(0, r1); |
||||
|
message_eq(1, r2); |
||||
|
message_eq(2, r3); |
||||
|
} |
||||
|
} |
||||
|
printf("\b\b\b\b100%\n"); |
||||
|
} |
||||
|
|
||||
|
int |
||||
|
main (void) |
||||
|
{ |
||||
|
int i, j, k; |
||||
|
|
||||
|
printf("sizeof(http_parser) = %d\n", sizeof(http_parser)); |
||||
|
|
||||
|
int request_count; |
||||
|
for (request_count = 0; requests[request_count].name; request_count++); |
||||
|
|
||||
|
int response_count; |
||||
|
for (response_count = 0; responses[response_count].name; response_count++); |
||||
|
|
||||
|
|
||||
|
//// RESPONSES
|
||||
|
|
||||
|
for (i = 0; i < response_count; i++) { |
||||
|
test_message(&responses[i]); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
puts("responses okay"); |
||||
|
|
||||
|
|
||||
|
|
||||
|
/// REQUESTS
|
||||
|
|
||||
|
|
||||
|
test_error("hello world"); |
||||
|
test_error("GET / HTP/1.1\r\n\r\n"); |
||||
|
|
||||
|
const char *dumbfuck2 = |
||||
|
"GET / HTTP/1.1\r\n" |
||||
|
"X-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n" |
||||
|
"\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n" |
||||
|
"\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n" |
||||
|
"\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n" |
||||
|
"\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n" |
||||
|
"\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n" |
||||
|
"\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n" |
||||
|
"\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n" |
||||
|
"\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n" |
||||
|
"\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n" |
||||
|
"\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n" |
||||
|
"\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n" |
||||
|
"\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n" |
||||
|
"\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n" |
||||
|
"\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n" |
||||
|
"\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n" |
||||
|
"\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n" |
||||
|
"\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n" |
||||
|
"\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n" |
||||
|
"\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n" |
||||
|
"\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n" |
||||
|
"\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n" |
||||
|
"\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n" |
||||
|
"\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n" |
||||
|
"\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n" |
||||
|
"\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n" |
||||
|
"\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n" |
||||
|
"\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n" |
||||
|
"\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n" |
||||
|
"\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n" |
||||
|
"\tRA==\r\n" |
||||
|
"\t-----END CERTIFICATE-----\r\n" |
||||
|
"\r\n"; |
||||
|
test_error(dumbfuck2); |
||||
|
|
||||
|
// no content-length
|
||||
|
// error if there is a body without content length
|
||||
|
const char *bad_get_no_headers_no_body = "GET /bad_get_no_headers_no_body/world HTTP/1.1\r\n" |
||||
|
"Accept: */*\r\n" |
||||
|
"\r\n" |
||||
|
"HELLO"; |
||||
|
test_error(bad_get_no_headers_no_body); |
||||
|
|
||||
|
|
||||
|
/* TODO sending junk and large headers gets rejected */ |
||||
|
|
||||
|
|
||||
|
/* check to make sure our predefined requests are okay */ |
||||
|
for (i = 0; requests[i].name; i++) { |
||||
|
test_message(&requests[i]); |
||||
|
} |
||||
|
|
||||
|
for (i = 0; i < request_count; i++) { |
||||
|
for (j = 0; j < request_count; j++) { |
||||
|
for (k = 0; k < request_count; k++) { |
||||
|
//printf("%d %d %d\n", i, j, k);
|
||||
|
test_multiple3(&requests[i], &requests[j], &requests[k]); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
printf("request scan 1/3 "); |
||||
|
test_scan( &requests[GET_NO_HEADERS_NO_BODY] |
||||
|
, &requests[GET_ONE_HEADER_NO_BODY] |
||||
|
, &requests[GET_NO_HEADERS_NO_BODY] |
||||
|
); |
||||
|
|
||||
|
printf("request scan 2/3 "); |
||||
|
test_scan( &requests[GET_FUNKY_CONTENT_LENGTH] |
||||
|
, &requests[POST_IDENTITY_BODY_WORLD] |
||||
|
, &requests[POST_CHUNKED_ALL_YOUR_BASE] |
||||
|
); |
||||
|
|
||||
|
printf("request scan 3/3 "); |
||||
|
test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END] |
||||
|
, &requests[CHUNKED_W_TRAILING_HEADERS] |
||||
|
, &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH] |
||||
|
); |
||||
|
|
||||
|
puts("requests okay"); |
||||
|
|
||||
|
|
||||
|
return 0; |
||||
|
} |
@ -1,5 +0,0 @@ |
|||||
*.o |
|
||||
examples/hello_world |
|
||||
test_request_parser |
|
||||
ebb_request_parser.c |
|
||||
tags |
|
@ -1,21 +0,0 @@ |
|||||
Copyright (c) 2008 Ryan Dahl (ry@ndahl.us) |
|
||||
|
|
||||
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 rights to use, copy, modify, merge, publish, |
|
||||
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. |
|
||||
|
|
@ -1,7 +0,0 @@ |
|||||
see doc/index.html and examples/hello_world.c for explanation |
|
||||
|
|
||||
webpage: http://tinyclouds.org/libebb/ |
|
||||
git repository: http://github.com/ry/libebb/tree/master |
|
||||
|
|
||||
To build libebb please edit config.mk to reflect your system's parameters. |
|
||||
|
|
@ -1,38 +0,0 @@ |
|||||
PREFIX = $(HOME)/local/libebb |
|
||||
|
|
||||
# libev
|
|
||||
EVINC = $(HOME)/local/libev/include |
|
||||
EVLIB = $(HOME)/local/libev/lib |
|
||||
EVLIBS = -L${EVLIB} -lev |
|
||||
|
|
||||
# GnuTLS, comment if you don't want it (necessary for HTTPS)
|
|
||||
GNUTLSLIB = /usr/lib |
|
||||
GNUTLSINC = /usr/include |
|
||||
GNUTLSLIBS = -L${GNUTLSLIB} -lgnutls |
|
||||
GNUTLSFLAGS = -DHAVE_GNUTLS |
|
||||
|
|
||||
# includes and libs
|
|
||||
INCS = -I${EVINC} -I${GNUTLSINC} |
|
||||
LIBS = ${EVLIBS} ${GNUTLSLIBS} -lefence |
|
||||
|
|
||||
# flags
|
|
||||
CPPFLAGS = -DVERSION=\"$(VERSION)\" ${GNUTLSFLAGS} |
|
||||
CFLAGS = -O2 -g -Wall ${INCS} ${CPPFLAGS} -fPIC |
|
||||
LDFLAGS = -s ${LIBS} |
|
||||
LDOPT = -shared |
|
||||
SUFFIX = so |
|
||||
SONAME = -Wl,-soname,$(OUTPUT_LIB) |
|
||||
|
|
||||
# Solaris
|
|
||||
#CFLAGS = -fast ${INCS} -DVERSION=\"$(VERSION)\" -fPIC
|
|
||||
#LDFLAGS = ${LIBS}
|
|
||||
#SONAME =
|
|
||||
|
|
||||
# Darwin
|
|
||||
# LDOPT = -dynamiclib
|
|
||||
# SUFFIX = dylib
|
|
||||
# SONAME = -current_version $(VERSION) -compatibility_version $(VERSION)
|
|
||||
|
|
||||
# compiler and linker
|
|
||||
CC = cc |
|
||||
RANLIB = ranlib |
|
Before Width: | Height: | Size: 9.2 KiB |
@ -1,240 +0,0 @@ |
|||||
|
|
||||
<html> |
|
||||
<style> |
|
||||
body { |
|
||||
background: #fff; |
|
||||
color: #2e3436; |
|
||||
font-size: 12pt; |
|
||||
line-height: 16pt; |
|
||||
/* font-family: Palatino; */ |
|
||||
margin: 3em 0 3em 3em; |
|
||||
} |
|
||||
|
|
||||
code, pre { |
|
||||
} |
|
||||
|
|
||||
#contents { |
|
||||
max-width: 40em; |
|
||||
} |
|
||||
|
|
||||
ul { |
|
||||
padding-left: 0; |
|
||||
} |
|
||||
|
|
||||
li { |
|
||||
margin-top: 0.5em; |
|
||||
margin-bottom: 0.5em; |
|
||||
} |
|
||||
|
|
||||
p { |
|
||||
text-align: left; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
img { |
|
||||
float: left; |
|
||||
margin: 0 1em 1em 0; |
|
||||
} |
|
||||
|
|
||||
p { clear: both; } |
|
||||
</style> |
|
||||
<body> <div id="contents"> |
|
||||
<img src="icon.png"/> |
|
||||
<h1>libebb</h1> |
|
||||
|
|
||||
<p> |
|
||||
libebb is a lightweight HTTP server library for C. It lays the |
|
||||
foundation for writing a web server by providing the socket juggling |
|
||||
and request parsing. By implementing the HTTP/1.1 grammar provided in |
|
||||
RFC2612, libebb understands most most valid HTTP/1.1 connections |
|
||||
(persistent, pipelined, and chunked requests included) and rejects |
|
||||
invalid or malicious requests. libebb supports SSL over HTTP. |
|
||||
</p> |
|
||||
|
|
||||
<p> |
|
||||
The library embraces a minimalistic single-threaded evented design. |
|
||||
No control is removed from the user. For example, all allocations are |
|
||||
done through callbacks so that the user might implement in optimal |
|
||||
ways for their specific application. By design libebb is not |
|
||||
thread-safe and all provided callbacks must not block. libebb uses |
|
||||
the <a href="http://libev.schmorp.de/bench.html">high-performance</a> |
|
||||
libev event loop, but does not control it. The user of the library may |
|
||||
start and stop the loop at will, they may attach thier own watchers. |
|
||||
</p> |
|
||||
|
|
||||
<p> |
|
||||
libebb depends on POSIX sockets, libev, and optionally GnuTLS. |
|
||||
</p> |
|
||||
|
|
||||
<p> |
|
||||
libebb is in the early stages of development and probably contains |
|
||||
many bugs. The API is subject to radical changes. If you're |
|
||||
interested <a href="http://github.com/ry/libebb/tree/master">checkout |
|
||||
the source code</a> and <a |
|
||||
href="http://groups.google.com/group/ebbebb">join the mailing |
|
||||
list</a>. A release will be made when it proves stable. |
|
||||
</p> |
|
||||
|
|
||||
<p>libebb is released under <a |
|
||||
href="http://www.gnu.org/licenses/license-list.html#X11License">the |
|
||||
X11 license</a>.</p> |
|
||||
|
|
||||
<h2>Usage</h2> |
|
||||
|
|
||||
<p> |
|
||||
libebb is a simple API, mostly it is providing callbacks. There are |
|
||||
two types of callbacks that one will work with: |
|
||||
</p> |
|
||||
|
|
||||
<ul> |
|
||||
<li>callbacks to allocate and initialize data for libebb. These are |
|
||||
named <code>new_*</code> like <code>new_connection</code> and |
|
||||
<code>new_request</code></li> |
|
||||
<li>callbacks which happen on an event and might provide a pointer to |
|
||||
a chunk of data. These are named <code>on_*</code> like |
|
||||
<code>on_body</code> and <code>on_close</code>.</li> |
|
||||
</ul> |
|
||||
|
|
||||
<p> |
|
||||
In libebb there are three important classes: <code>ebb_server</code>, |
|
||||
<code>ebb_connection</code>, and <code>ebb_request</code>. |
|
||||
Each server has many peer connections. Each peer connection may have many |
|
||||
requests. |
|
||||
There are two additional classes <code>ebb_buf</code> and <code>ebb_request_parser</code> |
|
||||
which may or may not be useful. |
|
||||
</p> |
|
||||
|
|
||||
<h3><code>ebb_server</code></h3> |
|
||||
<p> |
|
||||
<code>ebb_server</code> represents a single web server listening on a |
|
||||
single port. The user must allocate the structure themselves, then |
|
||||
call <code>ebb_server_init()</code> and provide a libev event loop. |
|
||||
<code>ebb_server_set_secure()</code> will make the server understand |
|
||||
HTTPS connections. |
|
||||
</p> |
|
||||
|
|
||||
<p> |
|
||||
After initialized the <code>ebb_server_listen_on_port()</code> can be |
|
||||
called to open the server up to new connections. libebb does not |
|
||||
control the event loop, it is the user's responsibility to start the |
|
||||
event loop (using <code>ev_loop()</code>) after |
|
||||
<code>ebb_server_listen_on_port()</code> is called. |
|
||||
</p> |
|
||||
|
|
||||
<p> |
|
||||
To accept connections you must provide the new server with a callback |
|
||||
called <code>new_connection</code>. This callback must return an allocated |
|
||||
and initialized <code>ebb_connection</code> structure. |
|
||||
To set this callback do |
|
||||
</p> |
|
||||
|
|
||||
<pre>my_server->new_connection = my_new_connection_callback;</pre> |
|
||||
|
|
||||
<p> |
|
||||
Additional documentation can be found in <code>ebb.h</code> |
|
||||
</p> |
|
||||
|
|
||||
<h3><code>ebb_connection</code></h3> |
|
||||
|
|
||||
<p> |
|
||||
This structure contains information and callbacks for a single client |
|
||||
connection. It is allocated and initialized through the |
|
||||
<code>new_connection</code> callback in <code>ebb_server</code>. |
|
||||
To initialize a newly allocated <code>ebb_connection</code> use |
|
||||
<code>ebb_connection_init()</code>. |
|
||||
</p> |
|
||||
|
|
||||
<p> |
|
||||
After <code>ebb_connection_init()</code> is called a number of |
|
||||
callbacks can be set: <code>new_request</code>, <code>new_buf</code>, |
|
||||
<code>on_timeout</code>, and <code>on_close</code>. |
|
||||
</p> |
|
||||
|
|
||||
<p> |
|
||||
When an <code>ebb_connection</code> is returned to an |
|
||||
<code>ebb_server</code>, data is immediately data is read from the |
|
||||
socket. This data must be stored somewhere. Because libebb is |
|
||||
agnostic about allocation decisions, it passes this off to the user in |
|
||||
the form of a callback: <code>connection->new_buf</code>. This |
|
||||
callback returns a newly allocated and initialized |
|
||||
<code>ebb_buf</code> structure. How much libebb attempts to read from |
|
||||
the socket is determined by how large the returned |
|
||||
<code>ebb_buf</code> structure is. Using <code>new_buf</code> is |
|
||||
optional. By default libebb reads data into a static buffer |
|
||||
(allocated at compile time), writing over it on each read. In many |
|
||||
web server using the static buffer will be sufficent because callbacks |
|
||||
made during the parsing will buffer the data elsewhere. Providing a |
|
||||
<code>new_buf</code> callback is necessary only if you want to save |
|
||||
the raw data coming from the socket. |
|
||||
</p> |
|
||||
|
|
||||
<p> |
|
||||
The <code>new_request</code> callback is called at the beginning of a |
|
||||
request. It must return a newly allocated and initialized |
|
||||
<code>ebb_request</code> structure. Because HTTP/1.1 supports <a |
|
||||
href="http://en.wikipedia.org/wiki/HTTP_persistent_connection">peristant</a> |
|
||||
connections, there may be many requests per connection. |
|
||||
</p> |
|
||||
|
|
||||
<p> |
|
||||
You may access the file descriptor for the client socket inside the |
|
||||
<code>ebb_connection</code> structure. Writing the response, in valid |
|
||||
HTTP, is the user's responsibility. Remember, requests must be |
|
||||
returned to client in the same order that they were received. |
|
||||
</p> |
|
||||
|
|
||||
<p> |
|
||||
A convience function, <coe>ebb_connection_write</code>, is provided |
|
||||
which will write a single string to the peer. You may use |
|
||||
this function or you may write to the file descriptor directly. |
|
||||
</p> |
|
||||
|
|
||||
<p> |
|
||||
To close a peer connection use |
|
||||
<code>ebb_connnection_schedule_close()</code>. Because SSL may require |
|
||||
some additional communication to close the connection properly, the |
|
||||
file descriptor cannot be closed immediately. The |
|
||||
<code>on_close</code> callback will be made when the peer socket is |
|
||||
finally closed. |
|
||||
<em>Only once <code>on_close</code> is called may the |
|
||||
user free the <code>ebb_connection</code> structure.</em> |
|
||||
</p> |
|
||||
|
|
||||
|
|
||||
<h3><code>ebb_request</code></h3> |
|
||||
|
|
||||
<p> |
|
||||
This structure provides information about a request. For example, |
|
||||
<code>request->method == EBB_POST</code> would mean the method of |
|
||||
the request is <code>POST</code>. There are also many callbacks |
|
||||
which can be set to handle data from a request as it is parsed. |
|
||||
</p> |
|
||||
|
|
||||
<p> |
|
||||
The <code>on_uri</code> callback and all other |
|
||||
<code>ebb_element_cb</code> callbacks provide pointers to request |
|
||||
data. The annoying thing is they do not necessarily provide a |
|
||||
complete string. This is because the reads from the socket may not |
|
||||
contain an entire request and for efficency libebb does not attempt to |
|
||||
buffer the data. Theoretically, one might receive an |
|
||||
<code>on_uri</code> callback 10 times, each providing just a single |
|
||||
character of the request's URI. See <code>ebb_request_parser.h</code> for |
|
||||
a full list of callbacks that you may provide. (If you don't set them, |
|
||||
they are ignored.) |
|
||||
</p> |
|
||||
|
|
||||
<p> |
|
||||
The <code>on_complete</code> callback is called at the end of |
|
||||
each request. |
|
||||
<em>Only once <code>on_complete</code> is called may the |
|
||||
user free the <code>ebb_request</code> structure.</em> |
|
||||
</p> |
|
||||
|
|
||||
<h2>Example</h2> |
|
||||
|
|
||||
<p> |
|
||||
A simple example is provided in <code>examples/hello_world.c</code>. |
|
||||
</p> |
|
||||
|
|
||||
</div></body> |
|
||||
</html> |
|
@ -1,798 +0,0 @@ |
|||||
/* This file is part of libebb.
|
|
||||
* |
|
||||
* Copyright (c) 2008 Ryan Dahl (ry@ndahl.us) |
|
||||
* 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 rights to use, copy, modify, merge, publish, |
|
||||
* 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. |
|
||||
*/ |
|
||||
#include <assert.h> |
|
||||
#include <string.h> |
|
||||
#include <fcntl.h> |
|
||||
#include <sys/types.h> |
|
||||
#include <sys/socket.h> |
|
||||
#include <netinet/tcp.h> /* TCP_NODELAY */ |
|
||||
#include <netinet/in.h> /* inet_ntoa */ |
|
||||
#include <arpa/inet.h> /* inet_ntoa */ |
|
||||
#include <unistd.h> |
|
||||
#include <stdio.h> /* perror */ |
|
||||
#include <errno.h> /* perror */ |
|
||||
#include <stdlib.h> /* for the default methods */ |
|
||||
#include <ev.h> |
|
||||
|
|
||||
#include "ebb.h" |
|
||||
#include "ebb_request_parser.h" |
|
||||
#ifdef HAVE_GNUTLS |
|
||||
# include <gnutls/gnutls.h> |
|
||||
# include "rbtree.h" /* for session_cache */ |
|
||||
#endif |
|
||||
|
|
||||
#ifndef TRUE |
|
||||
# define TRUE 1 |
|
||||
#endif |
|
||||
#ifndef FALSE |
|
||||
# define FALSE 0 |
|
||||
#endif |
|
||||
#ifndef MIN |
|
||||
# define MIN(a,b) (a < b ? a : b) |
|
||||
#endif |
|
||||
|
|
||||
#define error(FORMAT, ...) fprintf(stderr, "error: " FORMAT "\n", ##__VA_ARGS__) |
|
||||
|
|
||||
#define CONNECTION_HAS_SOMETHING_TO_WRITE (connection->to_write != NULL) |
|
||||
|
|
||||
static void |
|
||||
set_nonblock (int fd) |
|
||||
{ |
|
||||
int flags = fcntl(fd, F_GETFL, 0); |
|
||||
int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK); |
|
||||
assert(0 <= r && "Setting socket non-block failed!"); |
|
||||
} |
|
||||
|
|
||||
static ssize_t |
|
||||
nosigpipe_push(void *data, const void *buf, size_t len) |
|
||||
{ |
|
||||
int fd = (int)data; |
|
||||
int flags = 0; |
|
||||
#ifdef MSG_NOSIGNAL |
|
||||
flags = MSG_NOSIGNAL; |
|
||||
#endif |
|
||||
return send(fd, buf, len, flags); |
|
||||
} |
|
||||
|
|
||||
static void |
|
||||
close_connection(ebb_connection *connection) |
|
||||
{ |
|
||||
#ifdef HAVE_GNUTLS |
|
||||
if(connection->server->secure) |
|
||||
ev_io_stop(connection->server->loop, &connection->handshake_watcher); |
|
||||
#endif |
|
||||
ev_io_stop(connection->server->loop, &connection->read_watcher); |
|
||||
ev_io_stop(connection->server->loop, &connection->write_watcher); |
|
||||
ev_timer_stop(connection->server->loop, &connection->timeout_watcher); |
|
||||
|
|
||||
if(0 > close(connection->fd)) |
|
||||
error("problem closing connection fd"); |
|
||||
|
|
||||
connection->open = FALSE; |
|
||||
|
|
||||
if(connection->on_close) |
|
||||
connection->on_close(connection); |
|
||||
/* No access to the connection past this point!
|
|
||||
* The user is allowed to free in the callback |
|
||||
*/ |
|
||||
} |
|
||||
|
|
||||
#ifdef HAVE_GNUTLS |
|
||||
#define GNUTLS_NEED_WRITE (gnutls_record_get_direction(connection->session) == 1) |
|
||||
#define GNUTLS_NEED_READ (gnutls_record_get_direction(connection->session) == 0) |
|
||||
|
|
||||
#define EBB_MAX_SESSION_KEY 32 |
|
||||
#define EBB_MAX_SESSION_VALUE 512 |
|
||||
|
|
||||
struct session_cache { |
|
||||
struct rbtree_node_t node; |
|
||||
|
|
||||
gnutls_datum_t key; |
|
||||
gnutls_datum_t value; |
|
||||
|
|
||||
char key_storage[EBB_MAX_SESSION_KEY]; |
|
||||
char value_storage[EBB_MAX_SESSION_VALUE]; |
|
||||
}; |
|
||||
|
|
||||
static int |
|
||||
session_cache_compare (void *left, void *right) |
|
||||
{ |
|
||||
gnutls_datum_t *left_key = left; |
|
||||
gnutls_datum_t *right_key = right; |
|
||||
if(left_key->size < right_key->size) |
|
||||
return -1; |
|
||||
else if(left_key->size > right_key->size) |
|
||||
return 1; |
|
||||
else |
|
||||
return memcmp( left_key->data |
|
||||
, right_key->data |
|
||||
, MIN(left_key->size, right_key->size) |
|
||||
); |
|
||||
} |
|
||||
|
|
||||
static int |
|
||||
session_cache_store(void *data, gnutls_datum_t key, gnutls_datum_t value) |
|
||||
{ |
|
||||
rbtree tree = data; |
|
||||
|
|
||||
if( tree == NULL |
|
||||
|| key.size > EBB_MAX_SESSION_KEY |
|
||||
|| value.size > EBB_MAX_SESSION_VALUE |
|
||||
) return -1; |
|
||||
|
|
||||
struct session_cache *cache = gnutls_malloc(sizeof(struct session_cache)); |
|
||||
|
|
||||
memcpy (cache->key_storage, key.data, key.size); |
|
||||
cache->key.size = key.size; |
|
||||
cache->key.data = (void*)cache->key_storage; |
|
||||
|
|
||||
memcpy (cache->value_storage, value.data, value.size); |
|
||||
cache->value.size = value.size; |
|
||||
cache->value.data = (void*)cache->value_storage; |
|
||||
|
|
||||
cache->node.key = &cache->key; |
|
||||
cache->node.value = &cache; |
|
||||
|
|
||||
rbtree_insert(tree, (rbtree_node)cache); |
|
||||
|
|
||||
//printf("session_cache_store\n");
|
|
||||
|
|
||||
return 0; |
|
||||
} |
|
||||
|
|
||||
static gnutls_datum_t |
|
||||
session_cache_retrieve (void *data, gnutls_datum_t key) |
|
||||
{ |
|
||||
rbtree tree = data; |
|
||||
gnutls_datum_t res = { NULL, 0 }; |
|
||||
struct session_cache *cache = rbtree_lookup(tree, &key); |
|
||||
|
|
||||
if(cache == NULL) |
|
||||
return res; |
|
||||
|
|
||||
res.size = cache->value.size; |
|
||||
res.data = gnutls_malloc (res.size); |
|
||||
if(res.data == NULL) |
|
||||
return res; |
|
||||
|
|
||||
memcpy(res.data, cache->value.data, res.size); |
|
||||
|
|
||||
//printf("session_cache_retrieve\n");
|
|
||||
|
|
||||
return res; |
|
||||
} |
|
||||
|
|
||||
static int |
|
||||
session_cache_remove (void *data, gnutls_datum_t key) |
|
||||
{ |
|
||||
rbtree tree = data; |
|
||||
|
|
||||
if(tree == NULL) |
|
||||
return -1; |
|
||||
|
|
||||
struct session_cache *cache = (struct session_cache *)rbtree_delete(tree, &key); |
|
||||
if(cache == NULL) |
|
||||
return -1; |
|
||||
|
|
||||
gnutls_free(cache); |
|
||||
|
|
||||
//printf("session_cache_remove\n");
|
|
||||
|
|
||||
return 0; |
|
||||
} |
|
||||
|
|
||||
static void |
|
||||
on_handshake(struct ev_loop *loop ,ev_io *watcher, int revents) |
|
||||
{ |
|
||||
ebb_connection *connection = watcher->data; |
|
||||
|
|
||||
//printf("on_handshake\n");
|
|
||||
|
|
||||
assert(ev_is_active(&connection->timeout_watcher)); |
|
||||
assert(!ev_is_active(&connection->read_watcher)); |
|
||||
assert(!ev_is_active(&connection->write_watcher)); |
|
||||
|
|
||||
if(EV_ERROR & revents) { |
|
||||
error("on_handshake() got error event, closing connection.n"); |
|
||||
goto error; |
|
||||
} |
|
||||
|
|
||||
int r = gnutls_handshake(connection->session); |
|
||||
if(r < 0) { |
|
||||
if(gnutls_error_is_fatal(r)) goto error; |
|
||||
if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN) |
|
||||
ev_io_set( watcher |
|
||||
, connection->fd |
|
||||
, (GNUTLS_NEED_WRITE ? EV_WRITE : EV_READ) |
|
||||
); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
ebb_connection_reset_timeout(connection); |
|
||||
ev_io_stop(loop, watcher); |
|
||||
|
|
||||
ev_io_start(loop, &connection->read_watcher); |
|
||||
if(CONNECTION_HAS_SOMETHING_TO_WRITE) |
|
||||
ev_io_start(loop, &connection->write_watcher); |
|
||||
|
|
||||
return; |
|
||||
error: |
|
||||
close_connection(connection); |
|
||||
} |
|
||||
|
|
||||
#endif /* HAVE_GNUTLS */ |
|
||||
|
|
||||
|
|
||||
/* Internal callback
|
|
||||
* called by connection->timeout_watcher |
|
||||
*/ |
|
||||
static void |
|
||||
on_timeout(struct ev_loop *loop, ev_timer *watcher, int revents) |
|
||||
{ |
|
||||
ebb_connection *connection = watcher->data; |
|
||||
|
|
||||
assert(watcher == &connection->timeout_watcher); |
|
||||
|
|
||||
//printf("on_timeout\n");
|
|
||||
|
|
||||
/* if on_timeout returns true, we don't time out */ |
|
||||
if(connection->on_timeout) { |
|
||||
int r = connection->on_timeout(connection); |
|
||||
|
|
||||
if(r == EBB_AGAIN) { |
|
||||
ebb_connection_reset_timeout(connection); |
|
||||
return; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
ebb_connection_schedule_close(connection); |
|
||||
} |
|
||||
|
|
||||
/* Internal callback
|
|
||||
* called by connection->read_watcher |
|
||||
*/ |
|
||||
static void |
|
||||
on_readable(struct ev_loop *loop, ev_io *watcher, int revents) |
|
||||
{ |
|
||||
ebb_connection *connection = watcher->data; |
|
||||
char recv_buffer[TCP_MAXWIN]; |
|
||||
ssize_t recved; |
|
||||
|
|
||||
//printf("on_readable\n");
|
|
||||
// TODO -- why is this broken?
|
|
||||
//assert(ev_is_active(&connection->timeout_watcher));
|
|
||||
assert(watcher == &connection->read_watcher); |
|
||||
|
|
||||
if(EV_ERROR & revents) { |
|
||||
error("on_readable() got error event, closing connection."); |
|
||||
goto error; |
|
||||
} |
|
||||
|
|
||||
#ifdef HAVE_GNUTLS |
|
||||
assert(!ev_is_active(&connection->handshake_watcher)); |
|
||||
|
|
||||
if(connection->server->secure) { |
|
||||
recved = gnutls_record_recv( connection->session |
|
||||
, recv_buffer |
|
||||
, TCP_MAXWIN |
|
||||
); |
|
||||
if(recved <= 0) { |
|
||||
if(gnutls_error_is_fatal(recved)) goto error; |
|
||||
if( (recved == GNUTLS_E_INTERRUPTED || recved == GNUTLS_E_AGAIN) |
|
||||
&& GNUTLS_NEED_WRITE |
|
||||
) ev_io_start(loop, &connection->write_watcher); |
|
||||
return; |
|
||||
} |
|
||||
} else { |
|
||||
#endif /* HAVE_GNUTLS */ |
|
||||
|
|
||||
recved = recv(connection->fd, recv_buffer, TCP_MAXWIN, 0); |
|
||||
if(recved <= 0) goto error; |
|
||||
|
|
||||
#ifdef HAVE_GNUTLS |
|
||||
} |
|
||||
#endif /* HAVE_GNUTLS */ |
|
||||
|
|
||||
ebb_connection_reset_timeout(connection); |
|
||||
|
|
||||
ebb_request_parser_execute(&connection->parser, recv_buffer, recved); |
|
||||
|
|
||||
/* parse error? just drop the client. screw the 400 response */ |
|
||||
if(ebb_request_parser_has_error(&connection->parser)) goto error; |
|
||||
return; |
|
||||
error: |
|
||||
ebb_connection_schedule_close(connection); |
|
||||
} |
|
||||
|
|
||||
/* Internal callback
|
|
||||
* called by connection->write_watcher |
|
||||
*/ |
|
||||
static void |
|
||||
on_writable(struct ev_loop *loop, ev_io *watcher, int revents) |
|
||||
{ |
|
||||
ebb_connection *connection = watcher->data; |
|
||||
ssize_t sent; |
|
||||
|
|
||||
//printf("on_writable\n");
|
|
||||
|
|
||||
assert(CONNECTION_HAS_SOMETHING_TO_WRITE); |
|
||||
assert(connection->written <= connection->to_write_len); |
|
||||
// TODO -- why is this broken?
|
|
||||
//assert(ev_is_active(&connection->timeout_watcher));
|
|
||||
assert(watcher == &connection->write_watcher); |
|
||||
|
|
||||
if(connection->to_write == 0) |
|
||||
goto stop_writing; |
|
||||
|
|
||||
#ifdef HAVE_GNUTLS |
|
||||
assert(!ev_is_active(&connection->handshake_watcher)); |
|
||||
|
|
||||
if(connection->server->secure) { |
|
||||
sent = gnutls_record_send( connection->session |
|
||||
, connection->to_write + connection->written |
|
||||
, connection->to_write_len - connection->written |
|
||||
); |
|
||||
if(sent < 0) { |
|
||||
if(gnutls_error_is_fatal(sent)) goto error; |
|
||||
if( (sent == GNUTLS_E_INTERRUPTED || sent == GNUTLS_E_AGAIN) |
|
||||
&& GNUTLS_NEED_READ |
|
||||
) ev_io_stop(loop, watcher); |
|
||||
return; |
|
||||
} |
|
||||
} else { |
|
||||
#endif /* HAVE_GNUTLS */ |
|
||||
|
|
||||
sent = nosigpipe_push( (void*)connection->fd |
|
||||
, connection->to_write + connection->written |
|
||||
, connection->to_write_len - connection->written |
|
||||
); |
|
||||
if(sent < 0) goto error; |
|
||||
if(sent == 0) return; |
|
||||
|
|
||||
#ifdef HAVE_GNUTLS |
|
||||
} |
|
||||
#endif /* HAVE_GNUTLS */ |
|
||||
|
|
||||
ebb_connection_reset_timeout(connection); |
|
||||
|
|
||||
connection->written += sent; |
|
||||
|
|
||||
if(connection->written == connection->to_write_len) { |
|
||||
goto stop_writing; |
|
||||
} |
|
||||
return; |
|
||||
stop_writing: |
|
||||
ev_io_stop(loop, watcher); |
|
||||
connection->to_write = NULL; |
|
||||
|
|
||||
if(connection->after_write_cb) |
|
||||
connection->after_write_cb(connection); |
|
||||
return; |
|
||||
error: |
|
||||
error("close connection on write."); |
|
||||
ebb_connection_schedule_close(connection); |
|
||||
} |
|
||||
|
|
||||
#ifdef HAVE_GNUTLS |
|
||||
|
|
||||
static void |
|
||||
on_goodbye_tls(struct ev_loop *loop, ev_io *watcher, int revents) |
|
||||
{ |
|
||||
ebb_connection *connection = watcher->data; |
|
||||
assert(watcher == &connection->goodbye_tls_watcher); |
|
||||
|
|
||||
if(EV_ERROR & revents) { |
|
||||
error("on_goodbye() got error event, closing connection."); |
|
||||
goto die; |
|
||||
} |
|
||||
|
|
||||
int r = gnutls_bye(connection->session, GNUTLS_SHUT_RDWR); |
|
||||
if(r < 0) { |
|
||||
if(gnutls_error_is_fatal(r)) goto die; |
|
||||
if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN) |
|
||||
ev_io_set( watcher |
|
||||
, connection->fd |
|
||||
, (GNUTLS_NEED_WRITE ? EV_WRITE : EV_READ) |
|
||||
); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
die: |
|
||||
ev_io_stop(loop, watcher); |
|
||||
if(connection->session) |
|
||||
gnutls_deinit(connection->session); |
|
||||
close_connection(connection); |
|
||||
} |
|
||||
#endif /* HAVE_GNUTLS*/ |
|
||||
|
|
||||
static void |
|
||||
on_goodbye(struct ev_loop *loop, ev_timer *watcher, int revents) |
|
||||
{ |
|
||||
ebb_connection *connection = watcher->data; |
|
||||
assert(watcher == &connection->goodbye_watcher); |
|
||||
|
|
||||
close_connection(connection); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
static ebb_request* |
|
||||
new_request_wrapper(void *data) |
|
||||
{ |
|
||||
ebb_connection *connection = data; |
|
||||
if(connection->new_request) |
|
||||
return connection->new_request(connection); |
|
||||
return NULL; |
|
||||
} |
|
||||
|
|
||||
/* Internal callback
|
|
||||
* Called by server->connection_watcher. |
|
||||
*/ |
|
||||
static void |
|
||||
on_connection(struct ev_loop *loop, ev_io *watcher, int revents) |
|
||||
{ |
|
||||
ebb_server *server = watcher->data; |
|
||||
|
|
||||
//printf("on connection!\n");
|
|
||||
|
|
||||
assert(server->listening); |
|
||||
assert(server->loop == loop); |
|
||||
assert(&server->connection_watcher == watcher); |
|
||||
|
|
||||
if(EV_ERROR & revents) { |
|
||||
error("on_connection() got error event, closing server."); |
|
||||
ebb_server_unlisten(server); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
struct sockaddr_in addr; // connector's address information
|
|
||||
socklen_t addr_len = sizeof(addr); |
|
||||
int fd = accept( server->fd |
|
||||
, (struct sockaddr*) & addr |
|
||||
, & addr_len |
|
||||
); |
|
||||
if(fd < 0) { |
|
||||
perror("accept()"); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
ebb_connection *connection = NULL; |
|
||||
if(server->new_connection) |
|
||||
connection = server->new_connection(server, &addr); |
|
||||
if(connection == NULL) { |
|
||||
close(fd); |
|
||||
return; |
|
||||
} |
|
||||
|
|
||||
set_nonblock(fd); |
|
||||
connection->fd = fd; |
|
||||
connection->open = TRUE; |
|
||||
connection->server = server; |
|
||||
memcpy(&connection->sockaddr, &addr, addr_len); |
|
||||
if(server->port[0] != '\0') |
|
||||
connection->ip = inet_ntoa(connection->sockaddr.sin_addr); |
|
||||
|
|
||||
#ifdef SO_NOSIGPIPE |
|
||||
int arg = 1; |
|
||||
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &arg, sizeof(int)); |
|
||||
#endif |
|
||||
|
|
||||
#ifdef HAVE_GNUTLS |
|
||||
if(server->secure) { |
|
||||
gnutls_init(&connection->session, GNUTLS_SERVER); |
|
||||
gnutls_transport_set_lowat(connection->session, 0); |
|
||||
gnutls_set_default_priority(connection->session); |
|
||||
gnutls_credentials_set(connection->session, GNUTLS_CRD_CERTIFICATE, connection->server->credentials); |
|
||||
|
|
||||
gnutls_transport_set_ptr(connection->session, (gnutls_transport_ptr) fd); |
|
||||
gnutls_transport_set_push_function(connection->session, nosigpipe_push); |
|
||||
|
|
||||
gnutls_db_set_ptr (connection->session, &server->session_cache); |
|
||||
gnutls_db_set_store_function (connection->session, session_cache_store); |
|
||||
gnutls_db_set_retrieve_function (connection->session, session_cache_retrieve); |
|
||||
gnutls_db_set_remove_function (connection->session, session_cache_remove); |
|
||||
} |
|
||||
|
|
||||
ev_io_set(&connection->handshake_watcher, connection->fd, EV_READ | EV_WRITE); |
|
||||
#endif /* HAVE_GNUTLS */ |
|
||||
|
|
||||
/* Note: not starting the write watcher until there is data to be written */ |
|
||||
ev_io_set(&connection->write_watcher, connection->fd, EV_WRITE); |
|
||||
ev_io_set(&connection->read_watcher, connection->fd, EV_READ); |
|
||||
/* XXX: seperate error watcher? */ |
|
||||
|
|
||||
ev_timer_again(loop, &connection->timeout_watcher); |
|
||||
|
|
||||
#ifdef HAVE_GNUTLS |
|
||||
if(server->secure) { |
|
||||
ev_io_start(loop, &connection->handshake_watcher); |
|
||||
return; |
|
||||
} |
|
||||
#endif |
|
||||
|
|
||||
ev_io_start(loop, &connection->read_watcher); |
|
||||
} |
|
||||
|
|
||||
/**
|
|
||||
* Begin the server listening on a file descriptor. This DOES NOT start the |
|
||||
* event loop. Start the event loop after making this call. |
|
||||
*/ |
|
||||
int |
|
||||
ebb_server_listen_on_fd(ebb_server *server, const int fd) |
|
||||
{ |
|
||||
assert(server->listening == FALSE); |
|
||||
|
|
||||
if (listen(fd, EBB_MAX_CONNECTIONS) < 0) { |
|
||||
perror("listen()"); |
|
||||
return -1; |
|
||||
} |
|
||||
|
|
||||
set_nonblock(fd); /* XXX superfluous? */ |
|
||||
|
|
||||
server->fd = fd; |
|
||||
server->listening = TRUE; |
|
||||
|
|
||||
ev_io_set (&server->connection_watcher, server->fd, EV_READ); |
|
||||
ev_io_start (server->loop, &server->connection_watcher); |
|
||||
|
|
||||
return server->fd; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
/**
|
|
||||
* Begin the server listening on a file descriptor This DOES NOT start the |
|
||||
* event loop. Start the event loop after making this call. |
|
||||
*/ |
|
||||
int |
|
||||
ebb_server_listen_on_port(ebb_server *server, const int port) |
|
||||
{ |
|
||||
int fd = -1; |
|
||||
struct linger ling = {0, 0}; |
|
||||
struct sockaddr_in addr; |
|
||||
int flags = 1; |
|
||||
|
|
||||
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { |
|
||||
perror("socket()"); |
|
||||
goto error; |
|
||||
} |
|
||||
|
|
||||
flags = 1; |
|
||||
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void *)&flags, sizeof(flags)); |
|
||||
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, (void *)&flags, sizeof(flags)); |
|
||||
setsockopt(fd, SOL_SOCKET, SO_LINGER, (void *)&ling, sizeof(ling)); |
|
||||
|
|
||||
/* XXX: Sending single byte chunks in a response body? Perhaps there is a
|
|
||||
* need to enable the Nagel algorithm dynamically. For now disabling. |
|
||||
*/ |
|
||||
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (void *)&flags, sizeof(flags)); |
|
||||
|
|
||||
/* the memset call clears nonstandard fields in some impementations that
|
|
||||
* otherwise mess things up. |
|
||||
*/ |
|
||||
memset(&addr, 0, sizeof(addr)); |
|
||||
|
|
||||
addr.sin_family = AF_INET; |
|
||||
addr.sin_port = htons(port); |
|
||||
addr.sin_addr.s_addr = htonl(INADDR_ANY); |
|
||||
|
|
||||
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { |
|
||||
perror("bind()"); |
|
||||
goto error; |
|
||||
} |
|
||||
|
|
||||
int ret = ebb_server_listen_on_fd(server, fd); |
|
||||
if (ret >= 0) { |
|
||||
sprintf(server->port, "%d", port); |
|
||||
} |
|
||||
return ret; |
|
||||
error: |
|
||||
if(fd > 0) close(fd); |
|
||||
return -1; |
|
||||
} |
|
||||
|
|
||||
/**
|
|
||||
* Stops the server. Will not accept new connections. Does not drop |
|
||||
* existing connections. |
|
||||
*/ |
|
||||
void |
|
||||
ebb_server_unlisten(ebb_server *server) |
|
||||
{ |
|
||||
if(server->listening) { |
|
||||
ev_io_stop(server->loop, &server->connection_watcher); |
|
||||
close(server->fd); |
|
||||
server->port[0] = '\0'; |
|
||||
server->listening = FALSE; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/**
|
|
||||
* Initialize an ebb_server structure. After calling ebb_server_init set |
|
||||
* the callback server->new_connection and, optionally, callback data |
|
||||
* server->data. The new connection MUST be initialized with |
|
||||
* ebb_connection_init before returning it to the server. |
|
||||
* |
|
||||
* @param server the server to initialize |
|
||||
* @param loop a libev loop |
|
||||
*/ |
|
||||
void |
|
||||
ebb_server_init(ebb_server *server, struct ev_loop *loop) |
|
||||
{ |
|
||||
server->loop = loop; |
|
||||
server->listening = FALSE; |
|
||||
server->port[0] = '\0'; |
|
||||
server->fd = -1; |
|
||||
server->connection_watcher.data = server; |
|
||||
ev_init (&server->connection_watcher, on_connection); |
|
||||
server->secure = FALSE; |
|
||||
|
|
||||
#ifdef HAVE_GNUTLS |
|
||||
rbtree_init(&server->session_cache, session_cache_compare); |
|
||||
server->credentials = NULL; |
|
||||
#endif |
|
||||
|
|
||||
server->new_connection = NULL; |
|
||||
server->data = NULL; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
#ifdef HAVE_GNUTLS |
|
||||
/* similar to server_init.
|
|
||||
* |
|
||||
* the user of secure server might want to set additional callbacks from |
|
||||
* GNUTLS. In particular |
|
||||
* gnutls_global_set_mem_functions() |
|
||||
* gnutls_global_set_log_function() |
|
||||
* Also see the note above ebb_connection_init() about setting gnutls cache |
|
||||
* access functions |
|
||||
* |
|
||||
* cert_file: the filename of a PEM certificate file |
|
||||
* |
|
||||
* key_file: the filename of a private key. Currently only PKCS-1 encoded |
|
||||
* RSA and DSA private keys are accepted. |
|
||||
*/ |
|
||||
int |
|
||||
ebb_server_set_secure (ebb_server *server, const char *cert_file, const char *key_file) |
|
||||
{ |
|
||||
server->secure = TRUE; |
|
||||
gnutls_global_init(); |
|
||||
gnutls_certificate_allocate_credentials(&server->credentials); |
|
||||
/* todo gnutls_certificate_free_credentials */ |
|
||||
int r = gnutls_certificate_set_x509_key_file( server->credentials |
|
||||
, cert_file |
|
||||
, key_file |
|
||||
, GNUTLS_X509_FMT_PEM |
|
||||
); |
|
||||
if(r < 0) { |
|
||||
error("loading certificates"); |
|
||||
return -1; |
|
||||
} |
|
||||
return 1; |
|
||||
} |
|
||||
#endif /* HAVE_GNUTLS */ |
|
||||
|
|
||||
/**
|
|
||||
* Initialize an ebb_connection structure. After calling this function you |
|
||||
* must setup callbacks for the different actions the server can take. See |
|
||||
* server.h for which callbacks are availible. |
|
||||
* |
|
||||
* This should be called immediately after allocating space for a new |
|
||||
* ebb_connection structure. Most likely, this will only be called within |
|
||||
* the ebb_server->new_connection callback which you supply. |
|
||||
* |
|
||||
* If using SSL do consider setting |
|
||||
* gnutls_db_set_retrieve_function (connection->session, _); |
|
||||
* gnutls_db_set_remove_function (connection->session, _); |
|
||||
* gnutls_db_set_store_function (connection->session, _); |
|
||||
* gnutls_db_set_ptr (connection->session, _); |
|
||||
* To provide a better means of storing SSL session caches. libebb provides |
|
||||
* only a simple default implementation. |
|
||||
* |
|
||||
* @param connection the connection to initialize |
|
||||
* @param timeout the timeout in seconds |
|
||||
*/ |
|
||||
void |
|
||||
ebb_connection_init(ebb_connection *connection) |
|
||||
{ |
|
||||
connection->fd = -1; |
|
||||
connection->server = NULL; |
|
||||
connection->ip = NULL; |
|
||||
connection->open = FALSE; |
|
||||
|
|
||||
ebb_request_parser_init( &connection->parser ); |
|
||||
connection->parser.data = connection; |
|
||||
connection->parser.new_request = new_request_wrapper; |
|
||||
|
|
||||
ev_init (&connection->write_watcher, on_writable); |
|
||||
connection->write_watcher.data = connection; |
|
||||
connection->to_write = NULL; |
|
||||
|
|
||||
ev_init(&connection->read_watcher, on_readable); |
|
||||
connection->read_watcher.data = connection; |
|
||||
|
|
||||
#ifdef HAVE_GNUTLS |
|
||||
connection->handshake_watcher.data = connection; |
|
||||
ev_init(&connection->handshake_watcher, on_handshake); |
|
||||
|
|
||||
ev_init(&connection->goodbye_tls_watcher, on_goodbye_tls); |
|
||||
connection->goodbye_tls_watcher.data = connection; |
|
||||
|
|
||||
connection->session = NULL; |
|
||||
#endif /* HAVE_GNUTLS */ |
|
||||
|
|
||||
ev_timer_init(&connection->goodbye_watcher, on_goodbye, 0., 0.); |
|
||||
connection->goodbye_watcher.data = connection; |
|
||||
|
|
||||
ev_timer_init(&connection->timeout_watcher, on_timeout, 0., EBB_DEFAULT_TIMEOUT); |
|
||||
connection->timeout_watcher.data = connection; |
|
||||
|
|
||||
connection->new_request = NULL; |
|
||||
connection->on_timeout = NULL; |
|
||||
connection->on_close = NULL; |
|
||||
connection->data = NULL; |
|
||||
} |
|
||||
|
|
||||
void |
|
||||
ebb_connection_schedule_close (ebb_connection *connection) |
|
||||
{ |
|
||||
#ifdef HAVE_GNUTLS |
|
||||
if(connection->server->secure) { |
|
||||
ev_io_set(&connection->goodbye_tls_watcher, connection->fd, EV_READ | EV_WRITE); |
|
||||
ev_io_start(connection->server->loop, &connection->goodbye_tls_watcher); |
|
||||
return; |
|
||||
} |
|
||||
#endif |
|
||||
ev_timer_start(connection->server->loop, &connection->goodbye_watcher); |
|
||||
} |
|
||||
|
|
||||
/*
|
|
||||
* Resets the timeout to stay alive for another connection->timeout seconds |
|
||||
*/ |
|
||||
void |
|
||||
ebb_connection_reset_timeout(ebb_connection *connection) |
|
||||
{ |
|
||||
ev_timer_again(connection->server->loop, &connection->timeout_watcher); |
|
||||
} |
|
||||
|
|
||||
/**
|
|
||||
* Writes a string to the socket. This is actually sets a watcher |
|
||||
* which may take multiple iterations to write the entire string. |
|
||||
* |
|
||||
* This can only be called once at a time. If you call it again |
|
||||
* while the connection is writing another buffer the ebb_connection_write |
|
||||
* will return FALSE and ignore the request. |
|
||||
*/ |
|
||||
int |
|
||||
ebb_connection_write (ebb_connection *connection, const char *buf, size_t len, ebb_after_write_cb cb) |
|
||||
{ |
|
||||
if(ev_is_active(&connection->write_watcher)) |
|
||||
return FALSE; |
|
||||
assert(!CONNECTION_HAS_SOMETHING_TO_WRITE); |
|
||||
connection->to_write = buf; |
|
||||
connection->to_write_len = len; |
|
||||
connection->written = 0; |
|
||||
connection->after_write_cb = cb; |
|
||||
ev_io_start(connection->server->loop, &connection->write_watcher); |
|
||||
return TRUE; |
|
||||
} |
|
||||
|
|
@ -1,120 +0,0 @@ |
|||||
/* This file is part of libebb.
|
|
||||
* |
|
||||
* Copyright (c) 2008 Ryan Dahl (ry@ndahl.us) |
|
||||
* 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 rights to use, copy, modify, merge, publish, |
|
||||
* 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. |
|
||||
*/ |
|
||||
#ifndef EBB_H |
|
||||
#define EBB_H |
|
||||
|
|
||||
#include <sys/socket.h> |
|
||||
#include <netinet/in.h> |
|
||||
#include <ev.h> |
|
||||
#ifdef HAVE_GNUTLS |
|
||||
# include <gnutls/gnutls.h> |
|
||||
# include "rbtree.h" /* for ebb_server.session_cache */ |
|
||||
#endif |
|
||||
#include "ebb_request_parser.h" |
|
||||
|
|
||||
#define EBB_MAX_CONNECTIONS 1024 |
|
||||
#define EBB_DEFAULT_TIMEOUT 30.0 |
|
||||
|
|
||||
#define EBB_AGAIN 0 |
|
||||
#define EBB_STOP 1 |
|
||||
|
|
||||
typedef struct ebb_server ebb_server; |
|
||||
typedef struct ebb_connection ebb_connection; |
|
||||
typedef void (*ebb_after_write_cb) (ebb_connection *connection); |
|
||||
typedef void (*ebb_connection_cb)(ebb_connection *connection, void *data); |
|
||||
|
|
||||
struct ebb_server { |
|
||||
int fd; /* ro */ |
|
||||
struct sockaddr_in sockaddr; /* ro */ |
|
||||
socklen_t socklen; /* ro */ |
|
||||
char port[6]; /* ro */ |
|
||||
struct ev_loop *loop; /* ro */ |
|
||||
unsigned listening:1; /* ro */ |
|
||||
unsigned secure:1; /* ro */ |
|
||||
#ifdef HAVE_GNUTLS |
|
||||
gnutls_certificate_credentials_t credentials; /* private */ |
|
||||
struct rbtree_t session_cache; /* private */ |
|
||||
#endif |
|
||||
ev_io connection_watcher; /* private */ |
|
||||
|
|
||||
/* Public */ |
|
||||
|
|
||||
/* Allocates and initializes an ebb_connection. NULL by default. */ |
|
||||
ebb_connection* (*new_connection) (ebb_server*, struct sockaddr_in*); |
|
||||
|
|
||||
void *data; |
|
||||
}; |
|
||||
|
|
||||
struct ebb_connection { |
|
||||
int fd; /* ro */ |
|
||||
struct sockaddr_in sockaddr; /* ro */ |
|
||||
socklen_t socklen; /* ro */ |
|
||||
ebb_server *server; /* ro */ |
|
||||
char *ip; /* ro */ |
|
||||
unsigned open:1; /* ro */ |
|
||||
|
|
||||
const char *to_write; /* ro */ |
|
||||
size_t to_write_len; /* ro */ |
|
||||
size_t written; /* ro */ |
|
||||
ebb_after_write_cb after_write_cb; /* ro */ |
|
||||
|
|
||||
ebb_request_parser parser; /* private */ |
|
||||
ev_io write_watcher; /* private */ |
|
||||
ev_io read_watcher; /* private */ |
|
||||
ev_timer timeout_watcher; /* private */ |
|
||||
ev_timer goodbye_watcher; /* private */ |
|
||||
#ifdef HAVE_GNUTLS |
|
||||
ev_io handshake_watcher; /* private */ |
|
||||
gnutls_session_t session; /* private */ |
|
||||
ev_io goodbye_tls_watcher; /* private */ |
|
||||
#endif |
|
||||
|
|
||||
/* Public */ |
|
||||
|
|
||||
ebb_request* (*new_request) (ebb_connection*); |
|
||||
|
|
||||
/* Returns EBB_STOP or EBB_AGAIN. NULL by default. */ |
|
||||
int (*on_timeout) (ebb_connection*); |
|
||||
|
|
||||
void (*on_close) (ebb_connection*); |
|
||||
|
|
||||
void *data; |
|
||||
}; |
|
||||
|
|
||||
void ebb_server_init (ebb_server *server, struct ev_loop *loop); |
|
||||
#ifdef HAVE_GNUTLS |
|
||||
int ebb_server_set_secure (ebb_server *server, const char *cert_file, |
|
||||
const char *key_file); |
|
||||
#endif |
|
||||
int ebb_server_listen_on_port (ebb_server *server, const int port); |
|
||||
int ebb_server_listen_on_fd (ebb_server *server, const int sfd); |
|
||||
void ebb_server_unlisten (ebb_server *server); |
|
||||
|
|
||||
void ebb_connection_init (ebb_connection *); |
|
||||
void ebb_connection_schedule_close (ebb_connection *); |
|
||||
void ebb_connection_reset_timeout (ebb_connection *); |
|
||||
int ebb_connection_write (ebb_connection *, const char *buf, size_t len, ebb_after_write_cb); |
|
||||
|
|
||||
#endif |
|
@ -1,117 +0,0 @@ |
|||||
/* This file is part of the libebb web server library
|
|
||||
* |
|
||||
* Copyright (c) 2008 Ryan Dahl (ry@ndahl.us) |
|
||||
* All rights reserved. |
|
||||
* |
|
||||
* This parser is based on code from Zed Shaw's Mongrel. |
|
||||
* Copyright (c) 2005 Zed A. Shaw |
|
||||
* |
|
||||
* 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 rights to use, copy, modify, merge, publish, |
|
||||
* 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. |
|
||||
*/ |
|
||||
#ifndef ebb_request_parser_h |
|
||||
#define ebb_request_parser_h |
|
||||
#ifdef __cplusplus |
|
||||
extern "C" { |
|
||||
#endif |
|
||||
|
|
||||
|
|
||||
#include <sys/types.h> |
|
||||
|
|
||||
typedef struct ebb_request ebb_request; |
|
||||
typedef struct ebb_request_parser ebb_request_parser; |
|
||||
typedef void (*ebb_header_cb)(ebb_request*, const char *at, size_t length, int header_index); |
|
||||
typedef void (*ebb_element_cb)(ebb_request*, const char *at, size_t length); |
|
||||
|
|
||||
#define EBB_MAX_MULTIPART_BOUNDARY_LEN 20 |
|
||||
|
|
||||
/* HTTP Methods */ |
|
||||
#define EBB_COPY 0x00000001 |
|
||||
#define EBB_DELETE 0x00000002 |
|
||||
#define EBB_GET 0x00000004 |
|
||||
#define EBB_HEAD 0x00000008 |
|
||||
#define EBB_LOCK 0x00000010 |
|
||||
#define EBB_MKCOL 0x00000020 |
|
||||
#define EBB_MOVE 0x00000040 |
|
||||
#define EBB_OPTIONS 0x00000080 |
|
||||
#define EBB_POST 0x00000100 |
|
||||
#define EBB_PROPFIND 0x00000200 |
|
||||
#define EBB_PROPPATCH 0x00000400 |
|
||||
#define EBB_PUT 0x00000800 |
|
||||
#define EBB_TRACE 0x00001000 |
|
||||
#define EBB_UNLOCK 0x00002000 |
|
||||
|
|
||||
/* Transfer Encodings */ |
|
||||
#define EBB_IDENTITY 0x00000001 |
|
||||
#define EBB_CHUNKED 0x00000002 |
|
||||
|
|
||||
struct ebb_request { |
|
||||
int method; |
|
||||
int transfer_encoding; /* ro */ |
|
||||
int expect_continue; /* ro */ |
|
||||
unsigned int version_major; /* ro */ |
|
||||
unsigned int version_minor; /* ro */ |
|
||||
int number_of_headers; /* ro */ |
|
||||
int keep_alive; /* private - use ebb_request_should_keep_alive */ |
|
||||
size_t content_length; /* ro - 0 if unknown */ |
|
||||
size_t body_read; /* ro */ |
|
||||
|
|
||||
/* Public - ordered list of callbacks */ |
|
||||
ebb_element_cb on_path; |
|
||||
ebb_element_cb on_query_string; |
|
||||
ebb_element_cb on_uri; |
|
||||
ebb_element_cb on_fragment; |
|
||||
ebb_header_cb on_header_field; |
|
||||
ebb_header_cb on_header_value; |
|
||||
void (*on_headers_complete)(ebb_request *); |
|
||||
ebb_element_cb on_body; |
|
||||
void (*on_complete)(ebb_request *); |
|
||||
void *data; |
|
||||
}; |
|
||||
|
|
||||
struct ebb_request_parser { |
|
||||
int cs; /* private */ |
|
||||
size_t chunk_size; /* private */ |
|
||||
unsigned eating:1; /* private */ |
|
||||
ebb_request *current_request; /* ro */ |
|
||||
const char *header_field_mark; |
|
||||
const char *header_value_mark; |
|
||||
const char *query_string_mark; |
|
||||
const char *path_mark; |
|
||||
const char *uri_mark; |
|
||||
const char *fragment_mark; |
|
||||
|
|
||||
/* Public */ |
|
||||
ebb_request* (*new_request)(void*); |
|
||||
void *data; |
|
||||
}; |
|
||||
|
|
||||
void ebb_request_parser_init(ebb_request_parser *parser); |
|
||||
size_t ebb_request_parser_execute(ebb_request_parser *parser, const char *data, size_t len); |
|
||||
int ebb_request_parser_has_error(ebb_request_parser *parser); |
|
||||
int ebb_request_parser_is_finished(ebb_request_parser *parser); |
|
||||
void ebb_request_init(ebb_request *); |
|
||||
int ebb_request_should_keep_alive(ebb_request *request); |
|
||||
#define ebb_request_has_body(request) \ |
|
||||
(request->transfer_encoding == EBB_CHUNKED || request->content_length > 0 ) |
|
||||
|
|
||||
#ifdef __cplusplus |
|
||||
} |
|
||||
#endif |
|
||||
#endif |
|
@ -1,413 +0,0 @@ |
|||||
/* This file is part of the libebb web server library |
|
||||
* |
|
||||
* Copyright (c) 2008 Ryan Dahl (ry@ndahl.us) |
|
||||
* All rights reserved. |
|
||||
* |
|
||||
* This parser is based on code from Zed Shaw's Mongrel. |
|
||||
* Copyright (c) 2005 Zed A. Shaw |
|
||||
* |
|
||||
* 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 rights to use, copy, modify, merge, publish, |
|
||||
* 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. |
|
||||
*/ |
|
||||
#include "ebb_request_parser.h" |
|
||||
|
|
||||
#include <stdio.h> |
|
||||
#include <assert.h> |
|
||||
|
|
||||
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 |
|
||||
, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-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 |
|
||||
,-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 REMAINING (pe - p) |
|
||||
#define CURRENT (parser->current_request) |
|
||||
#define CONTENT_LENGTH (parser->current_request->content_length) |
|
||||
#define CALLBACK(FOR) \ |
|
||||
if(parser->FOR##_mark && CURRENT->on_##FOR) { \ |
|
||||
CURRENT->on_##FOR( CURRENT \ |
|
||||
, parser->FOR##_mark \ |
|
||||
, p - parser->FOR##_mark \ |
|
||||
); \ |
|
||||
} |
|
||||
#define HEADER_CALLBACK(FOR) \ |
|
||||
if(parser->FOR##_mark && CURRENT->on_##FOR) { \ |
|
||||
CURRENT->on_##FOR( CURRENT \ |
|
||||
, parser->FOR##_mark \ |
|
||||
, p - parser->FOR##_mark \ |
|
||||
, CURRENT->number_of_headers \ |
|
||||
); \ |
|
||||
} |
|
||||
#define END_REQUEST \ |
|
||||
if(CURRENT->on_complete) \ |
|
||||
CURRENT->on_complete(CURRENT); \ |
|
||||
CURRENT = NULL; |
|
||||
|
|
||||
|
|
||||
%%{ |
|
||||
machine ebb_request_parser; |
|
||||
|
|
||||
action mark_header_field { parser->header_field_mark = p; } |
|
||||
action mark_header_value { parser->header_value_mark = p; } |
|
||||
action mark_fragment { parser->fragment_mark = p; } |
|
||||
action mark_query_string { parser->query_string_mark = p; } |
|
||||
action mark_request_path { parser->path_mark = p; } |
|
||||
action mark_request_uri { parser->uri_mark = p; } |
|
||||
|
|
||||
action write_field { |
|
||||
HEADER_CALLBACK(header_field); |
|
||||
parser->header_field_mark = NULL; |
|
||||
} |
|
||||
|
|
||||
action write_value { |
|
||||
HEADER_CALLBACK(header_value); |
|
||||
parser->header_value_mark = NULL; |
|
||||
} |
|
||||
|
|
||||
action request_uri { |
|
||||
CALLBACK(uri); |
|
||||
parser->uri_mark = NULL; |
|
||||
} |
|
||||
|
|
||||
action fragment { |
|
||||
CALLBACK(fragment); |
|
||||
parser->fragment_mark = NULL; |
|
||||
} |
|
||||
|
|
||||
action query_string { |
|
||||
CALLBACK(query_string); |
|
||||
parser->query_string_mark = NULL; |
|
||||
} |
|
||||
|
|
||||
action request_path { |
|
||||
CALLBACK(path); |
|
||||
parser->path_mark = NULL; |
|
||||
} |
|
||||
|
|
||||
action content_length { |
|
||||
CURRENT->content_length *= 10; |
|
||||
CURRENT->content_length += *p - '0'; |
|
||||
} |
|
||||
|
|
||||
action use_identity_encoding { CURRENT->transfer_encoding = EBB_IDENTITY; } |
|
||||
action use_chunked_encoding { CURRENT->transfer_encoding = EBB_CHUNKED; } |
|
||||
|
|
||||
action set_keep_alive { CURRENT->keep_alive = TRUE; } |
|
||||
action set_not_keep_alive { CURRENT->keep_alive = FALSE; } |
|
||||
|
|
||||
action expect_continue { |
|
||||
CURRENT->expect_continue = TRUE; |
|
||||
} |
|
||||
|
|
||||
action trailer { |
|
||||
/* not implemenetd yet. (do requests even have trailing headers?) */ |
|
||||
} |
|
||||
|
|
||||
action version_major { |
|
||||
CURRENT->version_major *= 10; |
|
||||
CURRENT->version_major += *p - '0'; |
|
||||
} |
|
||||
|
|
||||
action version_minor { |
|
||||
CURRENT->version_minor *= 10; |
|
||||
CURRENT->version_minor += *p - '0'; |
|
||||
} |
|
||||
|
|
||||
action end_header_line { |
|
||||
CURRENT->number_of_headers++; |
|
||||
} |
|
||||
|
|
||||
action end_headers { |
|
||||
if(CURRENT->on_headers_complete) |
|
||||
CURRENT->on_headers_complete(CURRENT); |
|
||||
} |
|
||||
|
|
||||
action add_to_chunk_size { |
|
||||
parser->chunk_size *= 16; |
|
||||
parser->chunk_size += unhex[(int)*p]; |
|
||||
} |
|
||||
|
|
||||
action skip_chunk_data { |
|
||||
skip_body(&p, parser, MIN(parser->chunk_size, REMAINING)); |
|
||||
fhold; |
|
||||
if(parser->chunk_size > REMAINING) { |
|
||||
fbreak; |
|
||||
} else { |
|
||||
fgoto chunk_end; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
action end_chunked_body { |
|
||||
END_REQUEST; |
|
||||
fnext main; |
|
||||
} |
|
||||
|
|
||||
action start_req { |
|
||||
assert(CURRENT == NULL); |
|
||||
CURRENT = parser->new_request(parser->data); |
|
||||
} |
|
||||
|
|
||||
action body_logic { |
|
||||
if(CURRENT->transfer_encoding == EBB_CHUNKED) { |
|
||||
fnext ChunkedBody; |
|
||||
} else { |
|
||||
/* this is pretty stupid. i'd prefer to combine this with skip_chunk_data */ |
|
||||
parser->chunk_size = CURRENT->content_length; |
|
||||
p += 1; |
|
||||
skip_body(&p, parser, MIN(REMAINING, CURRENT->content_length)); |
|
||||
fhold; |
|
||||
if(parser->chunk_size > REMAINING) { |
|
||||
fbreak; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
# |
|
||||
## |
|
||||
### |
|
||||
#### HTTP/1.1 STATE MACHINE |
|
||||
### |
|
||||
## RequestHeaders and character types are from |
|
||||
# Zed Shaw's beautiful Mongrel parser. |
|
||||
|
|
||||
CRLF = "\r\n"; |
|
||||
|
|
||||
# character types |
|
||||
CTL = (cntrl | 127); |
|
||||
safe = ("$" | "-" | "_" | "."); |
|
||||
extra = ("!" | "*" | "'" | "(" | ")" | ","); |
|
||||
reserved = (";" | "/" | "?" | ":" | "@" | "&" | "=" | "+"); |
|
||||
unsafe = (CTL | " " | "\"" | "#" | "%" | "<" | ">"); |
|
||||
national = any -- (alpha | digit | reserved | extra | safe | unsafe); |
|
||||
unreserved = (alpha | digit | safe | extra | national); |
|
||||
escape = ("%" xdigit xdigit); |
|
||||
uchar = (unreserved | escape); |
|
||||
pchar = (uchar | ":" | "@" | "&" | "=" | "+"); |
|
||||
tspecials = ("(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\\" | "\"" | "/" | "[" | "]" | "?" | "=" | "{" | "}" | " " | "\t"); |
|
||||
|
|
||||
# elements |
|
||||
token = (ascii -- (CTL | tspecials)); |
|
||||
quote = "\""; |
|
||||
# qdtext = token -- "\""; |
|
||||
# quoted_pair = "\" ascii; |
|
||||
# quoted_string = "\"" (qdtext | quoted_pair )* "\""; |
|
||||
|
|
||||
# headers |
|
||||
|
|
||||
Method = ( "COPY" %{ CURRENT->method = EBB_COPY; } |
|
||||
| "DELETE" %{ CURRENT->method = EBB_DELETE; } |
|
||||
| "GET" %{ CURRENT->method = EBB_GET; } |
|
||||
| "HEAD" %{ CURRENT->method = EBB_HEAD; } |
|
||||
| "LOCK" %{ CURRENT->method = EBB_LOCK; } |
|
||||
| "MKCOL" %{ CURRENT->method = EBB_MKCOL; } |
|
||||
| "MOVE" %{ CURRENT->method = EBB_MOVE; } |
|
||||
| "OPTIONS" %{ CURRENT->method = EBB_OPTIONS; } |
|
||||
| "POST" %{ CURRENT->method = EBB_POST; } |
|
||||
| "PROPFIND" %{ CURRENT->method = EBB_PROPFIND; } |
|
||||
| "PROPPATCH" %{ CURRENT->method = EBB_PROPPATCH; } |
|
||||
| "PUT" %{ CURRENT->method = EBB_PUT; } |
|
||||
| "TRACE" %{ CURRENT->method = EBB_TRACE; } |
|
||||
| "UNLOCK" %{ CURRENT->method = EBB_UNLOCK; } |
|
||||
); # Not allowing extension methods |
|
||||
|
|
||||
HTTP_Version = "HTTP/" digit+ $version_major "." digit+ $version_minor; |
|
||||
|
|
||||
scheme = ( alpha | digit | "+" | "-" | "." )* ; |
|
||||
absolute_uri = (scheme ":" (uchar | reserved )*); |
|
||||
path = ( pchar+ ( "/" pchar* )* ) ; |
|
||||
query = ( uchar | reserved )* >mark_query_string %query_string ; |
|
||||
param = ( pchar | "/" )* ; |
|
||||
params = ( param ( ";" param )* ) ; |
|
||||
rel_path = ( path? (";" params)? ) ; |
|
||||
absolute_path = ( "/"+ rel_path ) >mark_request_path %request_path ("?" query)?; |
|
||||
Request_URI = ( "*" | absolute_uri | absolute_path ) >mark_request_uri %request_uri; |
|
||||
Fragment = ( uchar | reserved )* >mark_fragment %fragment; |
|
||||
|
|
||||
field_name = ( token -- ":" )+; |
|
||||
Field_Name = field_name >mark_header_field %write_field; |
|
||||
|
|
||||
field_value = ((any - " ") any*)?; |
|
||||
Field_Value = field_value >mark_header_value %write_value; |
|
||||
|
|
||||
hsep = ":" " "*; |
|
||||
header = (field_name hsep field_value) :> CRLF; |
|
||||
Header = ( ("Content-Length"i hsep digit+ $content_length) |
|
||||
| ("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) |
|
||||
# | ("Expect"i hsep "100-continue"i %expect_continue) |
|
||||
# | ("Trailer"i hsep field_value %trailer) |
|
||||
| (Field_Name hsep Field_Value) |
|
||||
) :> CRLF; |
|
||||
|
|
||||
Request_Line = ( Method " " Request_URI ("#" Fragment)? " " HTTP_Version CRLF ) ; |
|
||||
RequestHeader = Request_Line (Header %end_header_line)* :> CRLF @end_headers; |
|
||||
|
|
||||
# chunked message |
|
||||
trailing_headers = header*; |
|
||||
#chunk_ext_val = token | quoted_string; |
|
||||
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; |
|
||||
chunk_end = CRLF; |
|
||||
chunk_body = any >skip_chunk_data; |
|
||||
chunk_begin = chunk_size chunk_extension CRLF; |
|
||||
chunk = chunk_begin chunk_body chunk_end; |
|
||||
ChunkedBody := chunk* last_chunk trailing_headers CRLF @end_chunked_body; |
|
||||
|
|
||||
Request = RequestHeader >start_req @body_logic; |
|
||||
|
|
||||
main := Request*; # sequence of requests (for keep-alive) |
|
||||
}%% |
|
||||
|
|
||||
%% write data; |
|
||||
|
|
||||
static void |
|
||||
skip_body(const char **p, ebb_request_parser *parser, size_t nskip) { |
|
||||
if(CURRENT->on_body && nskip > 0) { |
|
||||
CURRENT->on_body(CURRENT, *p, nskip); |
|
||||
} |
|
||||
CURRENT->body_read += nskip; |
|
||||
parser->chunk_size -= nskip; |
|
||||
*p += nskip; |
|
||||
if(0 == parser->chunk_size) { |
|
||||
parser->eating = FALSE; |
|
||||
if(CURRENT->transfer_encoding == EBB_IDENTITY) { |
|
||||
END_REQUEST; |
|
||||
} |
|
||||
} else { |
|
||||
parser->eating = TRUE; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void ebb_request_parser_init(ebb_request_parser *parser) |
|
||||
{ |
|
||||
int cs = 0; |
|
||||
%% write init; |
|
||||
parser->cs = cs; |
|
||||
|
|
||||
parser->chunk_size = 0; |
|
||||
parser->eating = 0; |
|
||||
|
|
||||
parser->current_request = NULL; |
|
||||
|
|
||||
parser->header_field_mark = parser->header_value_mark = |
|
||||
parser->query_string_mark = parser->path_mark = |
|
||||
parser->uri_mark = parser->fragment_mark = NULL; |
|
||||
|
|
||||
parser->new_request = NULL; |
|
||||
} |
|
||||
|
|
||||
|
|
||||
/** exec **/ |
|
||||
size_t ebb_request_parser_execute(ebb_request_parser *parser, const char *buffer, size_t len) |
|
||||
{ |
|
||||
const char *p, *pe; |
|
||||
int cs = parser->cs; |
|
||||
|
|
||||
assert(parser->new_request && "undefined callback"); |
|
||||
|
|
||||
p = buffer; |
|
||||
pe = buffer+len; |
|
||||
|
|
||||
if(0 < parser->chunk_size && parser->eating) { |
|
||||
/* eat body */ |
|
||||
size_t eat = MIN(len, parser->chunk_size); |
|
||||
skip_body(&p, parser, eat); |
|
||||
} |
|
||||
|
|
||||
if(parser->header_field_mark) parser->header_field_mark = buffer; |
|
||||
if(parser->header_value_mark) parser->header_value_mark = buffer; |
|
||||
if(parser->fragment_mark) parser->fragment_mark = buffer; |
|
||||
if(parser->query_string_mark) parser->query_string_mark = buffer; |
|
||||
if(parser->path_mark) parser->path_mark = buffer; |
|
||||
if(parser->uri_mark) parser->uri_mark = buffer; |
|
||||
|
|
||||
%% write exec; |
|
||||
|
|
||||
parser->cs = cs; |
|
||||
|
|
||||
HEADER_CALLBACK(header_field); |
|
||||
HEADER_CALLBACK(header_value); |
|
||||
CALLBACK(fragment); |
|
||||
CALLBACK(query_string); |
|
||||
CALLBACK(path); |
|
||||
CALLBACK(uri); |
|
||||
|
|
||||
assert(p <= pe && "buffer overflow after parsing execute"); |
|
||||
|
|
||||
return(p - buffer); |
|
||||
} |
|
||||
|
|
||||
int ebb_request_parser_has_error(ebb_request_parser *parser) |
|
||||
{ |
|
||||
return parser->cs == ebb_request_parser_error; |
|
||||
} |
|
||||
|
|
||||
int ebb_request_parser_is_finished(ebb_request_parser *parser) |
|
||||
{ |
|
||||
return parser->cs == ebb_request_parser_first_final; |
|
||||
} |
|
||||
|
|
||||
void ebb_request_init(ebb_request *request) |
|
||||
{ |
|
||||
request->expect_continue = FALSE; |
|
||||
request->body_read = 0; |
|
||||
request->content_length = 0; |
|
||||
request->version_major = 0; |
|
||||
request->version_minor = 0; |
|
||||
request->number_of_headers = 0; |
|
||||
request->transfer_encoding = EBB_IDENTITY; |
|
||||
request->keep_alive = -1; |
|
||||
|
|
||||
request->on_complete = NULL; |
|
||||
request->on_headers_complete = NULL; |
|
||||
request->on_body = NULL; |
|
||||
request->on_header_field = NULL; |
|
||||
request->on_header_value = NULL; |
|
||||
request->on_uri = NULL; |
|
||||
request->on_fragment = NULL; |
|
||||
request->on_path = NULL; |
|
||||
request->on_query_string = NULL; |
|
||||
} |
|
||||
|
|
||||
int ebb_request_should_keep_alive(ebb_request *request) |
|
||||
{ |
|
||||
if(request->keep_alive == -1) |
|
||||
if(request->version_major == 1) |
|
||||
return (request->version_minor != 0); |
|
||||
else if(request->version_major == 0) |
|
||||
return FALSE; |
|
||||
else |
|
||||
return TRUE; |
|
||||
else |
|
||||
return request->keep_alive; |
|
||||
} |
|
@ -1,12 +0,0 @@ |
|||||
-----BEGIN CERTIFICATE----- |
|
||||
MIIBzDCCATegAwIBAgIESIuNVTALBgkqhkiG9w0BAQUwADAeFw0wODA3MjYyMDQ3 |
|
||||
MTlaFw0xMTA0MjIyMDQ3MjVaMAAwgZwwCwYJKoZIhvcNAQEBA4GMADCBiAKBgOm9 |
|
||||
l/FoXbTIcEusk/QlS5YrlR04+oWIbSdZIf3GJBEWEUPljDxAX96qHsTcaVnGK+EP |
|
||||
keU4cIZvdY+hzbqa5cc1j2/9IeJNejL8gpQ/ocyMM69yq5Ib2F8K4mGWm1xr30hU |
|
||||
bYpY5D0MrZ1b0HtYFVc8KVAr0ADGG+pye0P9c3B/AgMBAAGjWjBYMAwGA1UdEwEB |
|
||||
/wQCMAAwFAYDVR0RBA0wC4IJbG9jYWxob3N0MBMGA1UdJQQMMAoGCCsGAQUFBwMB |
|
||||
MB0GA1UdDgQWBBTYgVB7kJnnm+jgX9DgrapzGfUmxjALBgkqhkiG9w0BAQUDgYEA |
|
||||
GkadA2H8CAzU3w4oCGZu9Ry9Tj/9Agw1XMFKvoJuG7VLPk7+B25JvNFVsmpROLxO |
|
||||
0TJ6mIU2hz5/rLvEfTBGQ+DYtbsjIxCz1fD7R5c1kKBtA0d0u8mY8pTlPNlxFPSW |
|
||||
3ymx5DB2zyDa/HuX6m6/VmzMYmA0vp7Dp1cl+pA9Nhs= |
|
||||
-----END CERTIFICATE----- |
|
@ -1,15 +0,0 @@ |
|||||
-----BEGIN RSA PRIVATE KEY----- |
|
||||
MIICXQIBAAKBgQDpvZfxaF20yHBLrJP0JUuWK5UdOPqFiG0nWSH9xiQRFhFD5Yw8 |
|
||||
QF/eqh7E3GlZxivhD5HlOHCGb3WPoc26muXHNY9v/SHiTXoy/IKUP6HMjDOvcquS |
|
||||
G9hfCuJhlptca99IVG2KWOQ9DK2dW9B7WBVXPClQK9AAxhvqcntD/XNwfwIDAQAB |
|
||||
AoGAJqo3LTbfcV1KvinhG5zjwQaalwfq4RXtQHoNFmalZrIozvt01C6t7S5lApmX |
|
||||
T8NpVMR3lNxeOM7NOqJAXuLqqVVqk81YEYuMx6E4gB/Ifl7jVZk1jstmLILhh59D |
|
||||
pXrlpzvvm5X2hVsI7lp/YGAvtdLS1iVy37bGgmQWfCeeZiECQQDtZLfcJb4oE1yR |
|
||||
ZfLOcPDlBCw02wGMNFpAjwbspf/du3Yn3ONWHVfhSCCcCe262h9PLblL97LoB+gF |
|
||||
OHOlM9JvAkEA/A+U3/p9pwL4L742pxZP62/rmk6p5mZFIykk2jIUTpdilXBWBlcT |
|
||||
OjgjnpquZpwnaClmvgFpkzdhIPF7Nq4K8QJBAITVKagOmnOUOeTF1fI78h9DkXTV |
|
||||
4uzP0nxzS52ZWS16Gqg9ihuCecz97flB+Prn2EMWw6tFY58/5U0ehF85OxMCQQDF |
|
||||
08TYdVSg+6emcPeb89sNwW18UjjuZ13j1qrhxWRCunXZK62YlEa27tCl7mjqh6w2 |
|
||||
CChm/9zIejJ1FJHLvJVBAkBj63ZbwggMYkxuj60jIBbNrEtDx9y7zM0sXkiJqcKp |
|
||||
5uGtJNafG+yZrLAHE6/b4aqUOtGsCGsiZpT9ms7CoaVr |
|
||||
-----END RSA PRIVATE KEY----- |
|
@ -1,101 +0,0 @@ |
|||||
#include <stdio.h> |
|
||||
#include <stdlib.h> |
|
||||
#include <string.h> |
|
||||
#include <unistd.h> |
|
||||
#include <assert.h> |
|
||||
|
|
||||
#include <ev.h> |
|
||||
#include "ebb.h" |
|
||||
|
|
||||
#define MSG ("HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 12\r\n\r\nhello world\n") |
|
||||
static int c = 0; |
|
||||
|
|
||||
struct hello_connection { |
|
||||
unsigned int responses_to_write; |
|
||||
}; |
|
||||
|
|
||||
void on_close(ebb_connection *connection) |
|
||||
{ |
|
||||
free(connection->data); |
|
||||
free(connection); |
|
||||
} |
|
||||
|
|
||||
static void continue_responding(ebb_connection *connection) |
|
||||
{ |
|
||||
int r; |
|
||||
struct hello_connection *connection_data = connection->data; |
|
||||
//printf("response complete \n");
|
|
||||
if(--connection_data->responses_to_write > 0) { |
|
||||
/* write another response */ |
|
||||
r = ebb_connection_write(connection, MSG, sizeof MSG, continue_responding); |
|
||||
assert(r); |
|
||||
} else { |
|
||||
ebb_connection_schedule_close(connection); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
static void request_complete(ebb_request *request) |
|
||||
{ |
|
||||
//printf("request complete \n");
|
|
||||
ebb_connection *connection = request->data; |
|
||||
struct hello_connection *connection_data = connection->data; |
|
||||
|
|
||||
if(ebb_request_should_keep_alive(request)) |
|
||||
connection_data->responses_to_write++; |
|
||||
else |
|
||||
connection_data->responses_to_write = 1; |
|
||||
|
|
||||
ebb_connection_write(connection, MSG, sizeof MSG, continue_responding); |
|
||||
free(request); |
|
||||
} |
|
||||
|
|
||||
static ebb_request* new_request(ebb_connection *connection) |
|
||||
{ |
|
||||
//printf("request %d\n", ++c);
|
|
||||
ebb_request *request = malloc(sizeof(ebb_request)); |
|
||||
ebb_request_init(request); |
|
||||
request->data = connection; |
|
||||
request->on_complete = request_complete; |
|
||||
return request; |
|
||||
} |
|
||||
|
|
||||
ebb_connection* new_connection(ebb_server *server, struct sockaddr_in *addr) |
|
||||
{ |
|
||||
struct hello_connection *connection_data = malloc(sizeof(struct hello_connection)); |
|
||||
if(connection_data == NULL) |
|
||||
return NULL; |
|
||||
connection_data->responses_to_write = 0; |
|
||||
|
|
||||
ebb_connection *connection = malloc(sizeof(ebb_connection)); |
|
||||
if(connection == NULL) { |
|
||||
free(connection_data); |
|
||||
return NULL; |
|
||||
} |
|
||||
|
|
||||
ebb_connection_init(connection); |
|
||||
connection->data = connection_data; |
|
||||
connection->new_request = new_request; |
|
||||
connection->on_close = on_close; |
|
||||
|
|
||||
printf("connection: %d\n", c++); |
|
||||
return connection; |
|
||||
} |
|
||||
|
|
||||
int main(int argc, char **_) |
|
||||
{ |
|
||||
struct ev_loop *loop = ev_default_loop(0); |
|
||||
ebb_server server; |
|
||||
|
|
||||
ebb_server_init(&server, loop); |
|
||||
if(argc > 1) { |
|
||||
printf("using SSL\n"); |
|
||||
ebb_server_set_secure(&server, "ca-cert.pem", "ca-key.pem"); |
|
||||
} |
|
||||
server.new_connection = new_connection; |
|
||||
|
|
||||
printf("hello_world listening on port 5000\n"); |
|
||||
ebb_server_listen_on_port(&server, 5000); |
|
||||
ev_loop(loop, 0); |
|
||||
|
|
||||
return 0; |
|
||||
} |
|
@ -1,412 +0,0 @@ |
|||||
/* Copyright (c) 2008 Derrick Coetzee
|
|
||||
* http://en.literateprograms.org/Red-black_tree_(C)?oldid=7982
|
|
||||
* |
|
||||
* 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 rights to use, copy, modify, merge, publish, |
|
||||
* 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. |
|
||||
*/ |
|
||||
|
|
||||
#include "rbtree.h" |
|
||||
#include <assert.h> |
|
||||
|
|
||||
#ifndef NULL |
|
||||
# define NULL ((void*)0) |
|
||||
#endif |
|
||||
|
|
||||
|
|
||||
typedef rbtree_node node; |
|
||||
typedef enum rbtree_node_color color; |
|
||||
|
|
||||
|
|
||||
static node grandparent(node n); |
|
||||
static node sibling(node n); |
|
||||
static node uncle(node n); |
|
||||
static void verify_properties(rbtree t); |
|
||||
static void verify_property_1(node root); |
|
||||
/* static void verify_property_2(node root); */ |
|
||||
static color node_color(node n); |
|
||||
static void verify_property_4(node root); |
|
||||
/* static void verify_property_5(node root); */ |
|
||||
static void verify_property_5_helper(node n, int black_count, int* black_count_path); |
|
||||
|
|
||||
static node lookup_node(rbtree t, void* key); |
|
||||
static void rotate_left(rbtree t, node n); |
|
||||
static void rotate_right(rbtree t, node n); |
|
||||
|
|
||||
static void replace_node(rbtree t, node oldn, node newn); |
|
||||
static void insert_case1(rbtree t, node n); |
|
||||
static void insert_case2(rbtree t, node n); |
|
||||
static void insert_case3(rbtree t, node n); |
|
||||
static void insert_case4(rbtree t, node n); |
|
||||
static void insert_case5(rbtree t, node n); |
|
||||
static node maximum_node(node root); |
|
||||
static void delete_case1(rbtree t, node n); |
|
||||
static void delete_case2(rbtree t, node n); |
|
||||
static void delete_case3(rbtree t, node n); |
|
||||
static void delete_case4(rbtree t, node n); |
|
||||
static void delete_case5(rbtree t, node n); |
|
||||
static void delete_case6(rbtree t, node n); |
|
||||
|
|
||||
node grandparent(node n) { |
|
||||
assert (n != NULL); |
|
||||
assert (n->parent != NULL); /* Not the root node */ |
|
||||
assert (n->parent->parent != NULL); /* Not child of root */ |
|
||||
return n->parent->parent; |
|
||||
} |
|
||||
|
|
||||
node sibling(node n) { |
|
||||
assert (n != NULL); |
|
||||
assert (n->parent != NULL); /* Root node has no sibling */ |
|
||||
if (n == n->parent->left) |
|
||||
return n->parent->right; |
|
||||
else |
|
||||
return n->parent->left; |
|
||||
} |
|
||||
|
|
||||
node uncle(node n) { |
|
||||
assert (n != NULL); |
|
||||
assert (n->parent != NULL); /* Root node has no uncle */ |
|
||||
assert (n->parent->parent != NULL); /* Children of root have no uncle */ |
|
||||
return sibling(n->parent); |
|
||||
} |
|
||||
|
|
||||
void verify_properties(rbtree t) { |
|
||||
#ifdef VERIFY_RBTREE |
|
||||
verify_property_1(t->root); |
|
||||
verify_property_2(t->root); |
|
||||
/* Property 3 is implicit */ |
|
||||
verify_property_4(t->root); |
|
||||
verify_property_5(t->root); |
|
||||
#endif |
|
||||
} |
|
||||
|
|
||||
void verify_property_1(node n) { |
|
||||
assert(node_color(n) == RED || node_color(n) == BLACK); |
|
||||
if (n == NULL) return; |
|
||||
verify_property_1(n->left); |
|
||||
verify_property_1(n->right); |
|
||||
} |
|
||||
|
|
||||
/*
|
|
||||
void verify_property_2(node root) { |
|
||||
assert(node_color(root) == BLACK); |
|
||||
} |
|
||||
*/ |
|
||||
|
|
||||
color node_color(node n) { |
|
||||
return n == NULL ? BLACK : n->color; |
|
||||
} |
|
||||
|
|
||||
void verify_property_4(node n) { |
|
||||
if (node_color(n) == RED) { |
|
||||
assert (node_color(n->left) == BLACK); |
|
||||
assert (node_color(n->right) == BLACK); |
|
||||
assert (node_color(n->parent) == BLACK); |
|
||||
} |
|
||||
if (n == NULL) return; |
|
||||
verify_property_4(n->left); |
|
||||
verify_property_4(n->right); |
|
||||
} |
|
||||
|
|
||||
/*
|
|
||||
void verify_property_5(node root) { |
|
||||
int black_count_path = -1; |
|
||||
verify_property_5_helper(root, 0, &black_count_path); |
|
||||
} |
|
||||
*/ |
|
||||
|
|
||||
void verify_property_5_helper(node n, int black_count, int* path_black_count) { |
|
||||
if (node_color(n) == BLACK) { |
|
||||
black_count++; |
|
||||
} |
|
||||
if (n == NULL) { |
|
||||
if (*path_black_count == -1) { |
|
||||
*path_black_count = black_count; |
|
||||
} else { |
|
||||
assert (black_count == *path_black_count); |
|
||||
} |
|
||||
return; |
|
||||
} |
|
||||
verify_property_5_helper(n->left, black_count, path_black_count); |
|
||||
verify_property_5_helper(n->right, black_count, path_black_count); |
|
||||
} |
|
||||
|
|
||||
void rbtree_init(rbtree t, rbtree_compare_func compare) { |
|
||||
t->root = NULL; |
|
||||
t->compare = compare; |
|
||||
verify_properties(t); |
|
||||
} |
|
||||
|
|
||||
node lookup_node(rbtree t, void* key) { |
|
||||
node n = t->root; |
|
||||
while (n != NULL) { |
|
||||
int comp_result = t->compare(key, n->key); |
|
||||
if (comp_result == 0) { |
|
||||
return n; |
|
||||
} else if (comp_result < 0) { |
|
||||
n = n->left; |
|
||||
} else { |
|
||||
assert(comp_result > 0); |
|
||||
n = n->right; |
|
||||
} |
|
||||
} |
|
||||
return n; |
|
||||
} |
|
||||
|
|
||||
void* rbtree_lookup(rbtree t, void* key) { |
|
||||
node n = lookup_node(t, key); |
|
||||
return n == NULL ? NULL : n->value; |
|
||||
} |
|
||||
|
|
||||
void rotate_left(rbtree t, node n) { |
|
||||
node r = n->right; |
|
||||
replace_node(t, n, r); |
|
||||
n->right = r->left; |
|
||||
if (r->left != NULL) { |
|
||||
r->left->parent = n; |
|
||||
} |
|
||||
r->left = n; |
|
||||
n->parent = r; |
|
||||
} |
|
||||
|
|
||||
void rotate_right(rbtree t, node n) { |
|
||||
node L = n->left; |
|
||||
replace_node(t, n, L); |
|
||||
n->left = L->right; |
|
||||
if (L->right != NULL) { |
|
||||
L->right->parent = n; |
|
||||
} |
|
||||
L->right = n; |
|
||||
n->parent = L; |
|
||||
} |
|
||||
|
|
||||
void replace_node(rbtree t, node oldn, node newn) { |
|
||||
if (oldn->parent == NULL) { |
|
||||
t->root = newn; |
|
||||
} else { |
|
||||
if (oldn == oldn->parent->left) |
|
||||
oldn->parent->left = newn; |
|
||||
else |
|
||||
oldn->parent->right = newn; |
|
||||
} |
|
||||
if (newn != NULL) { |
|
||||
newn->parent = oldn->parent; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void rbtree_insert(rbtree t, rbtree_node inserted_node) { |
|
||||
inserted_node->color = RED; |
|
||||
inserted_node->left = NULL; |
|
||||
inserted_node->right = NULL; |
|
||||
inserted_node->parent = NULL; |
|
||||
|
|
||||
if (t->root == NULL) { |
|
||||
t->root = inserted_node; |
|
||||
} else { |
|
||||
node n = t->root; |
|
||||
while (1) { |
|
||||
int comp_result = t->compare(inserted_node->key, n->key); |
|
||||
if (comp_result == 0) { |
|
||||
n->value = inserted_node->value; |
|
||||
return; |
|
||||
} else if (comp_result < 0) { |
|
||||
if (n->left == NULL) { |
|
||||
n->left = inserted_node; |
|
||||
break; |
|
||||
} else { |
|
||||
n = n->left; |
|
||||
} |
|
||||
} else { |
|
||||
assert (comp_result > 0); |
|
||||
if (n->right == NULL) { |
|
||||
n->right = inserted_node; |
|
||||
break; |
|
||||
} else { |
|
||||
n = n->right; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
inserted_node->parent = n; |
|
||||
} |
|
||||
insert_case1(t, inserted_node); |
|
||||
verify_properties(t); |
|
||||
} |
|
||||
|
|
||||
void insert_case1(rbtree t, node n) { |
|
||||
if (n->parent == NULL) |
|
||||
n->color = BLACK; |
|
||||
else |
|
||||
insert_case2(t, n); |
|
||||
} |
|
||||
|
|
||||
void insert_case2(rbtree t, node n) { |
|
||||
if (node_color(n->parent) == BLACK) |
|
||||
return; /* Tree is still valid */ |
|
||||
else |
|
||||
insert_case3(t, n); |
|
||||
} |
|
||||
|
|
||||
void insert_case3(rbtree t, node n) { |
|
||||
if (node_color(uncle(n)) == RED) { |
|
||||
n->parent->color = BLACK; |
|
||||
uncle(n)->color = BLACK; |
|
||||
grandparent(n)->color = RED; |
|
||||
insert_case1(t, grandparent(n)); |
|
||||
} else { |
|
||||
insert_case4(t, n); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void insert_case4(rbtree t, node n) { |
|
||||
if (n == n->parent->right && n->parent == grandparent(n)->left) { |
|
||||
rotate_left(t, n->parent); |
|
||||
n = n->left; |
|
||||
} else if (n == n->parent->left && n->parent == grandparent(n)->right) { |
|
||||
rotate_right(t, n->parent); |
|
||||
n = n->right; |
|
||||
} |
|
||||
insert_case5(t, n); |
|
||||
} |
|
||||
|
|
||||
void insert_case5(rbtree t, node n) { |
|
||||
n->parent->color = BLACK; |
|
||||
grandparent(n)->color = RED; |
|
||||
if (n == n->parent->left && n->parent == grandparent(n)->left) { |
|
||||
rotate_right(t, grandparent(n)); |
|
||||
} else { |
|
||||
assert (n == n->parent->right && n->parent == grandparent(n)->right); |
|
||||
rotate_left(t, grandparent(n)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
rbtree_node rbtree_delete(rbtree t, void* key) { |
|
||||
node child; |
|
||||
node n = lookup_node(t, key); |
|
||||
if (n == NULL) return NULL; /* Key not found, do nothing */ |
|
||||
if (n->left != NULL && n->right != NULL) { |
|
||||
/* Copy key/value from predecessor and then delete it instead */ |
|
||||
node pred = maximum_node(n->left); |
|
||||
n->key = pred->key; |
|
||||
n->value = pred->value; |
|
||||
n = pred; |
|
||||
} |
|
||||
|
|
||||
assert(n->left == NULL || n->right == NULL); |
|
||||
child = n->right == NULL ? n->left : n->right; |
|
||||
if (node_color(n) == BLACK) { |
|
||||
n->color = node_color(child); |
|
||||
delete_case1(t, n); |
|
||||
} |
|
||||
replace_node(t, n, child); |
|
||||
|
|
||||
verify_properties(t); |
|
||||
return n; |
|
||||
} |
|
||||
|
|
||||
static node maximum_node(node n) { |
|
||||
assert (n != NULL); |
|
||||
while (n->right != NULL) { |
|
||||
n = n->right; |
|
||||
} |
|
||||
return n; |
|
||||
} |
|
||||
|
|
||||
void delete_case1(rbtree t, node n) { |
|
||||
if (n->parent == NULL) |
|
||||
return; |
|
||||
else |
|
||||
delete_case2(t, n); |
|
||||
} |
|
||||
|
|
||||
void delete_case2(rbtree t, node n) { |
|
||||
if (node_color(sibling(n)) == RED) { |
|
||||
n->parent->color = RED; |
|
||||
sibling(n)->color = BLACK; |
|
||||
if (n == n->parent->left) |
|
||||
rotate_left(t, n->parent); |
|
||||
else |
|
||||
rotate_right(t, n->parent); |
|
||||
} |
|
||||
delete_case3(t, n); |
|
||||
} |
|
||||
|
|
||||
void delete_case3(rbtree t, node n) { |
|
||||
if (node_color(n->parent) == BLACK && |
|
||||
node_color(sibling(n)) == BLACK && |
|
||||
node_color(sibling(n)->left) == BLACK && |
|
||||
node_color(sibling(n)->right) == BLACK) |
|
||||
{ |
|
||||
sibling(n)->color = RED; |
|
||||
delete_case1(t, n->parent); |
|
||||
} |
|
||||
else |
|
||||
delete_case4(t, n); |
|
||||
} |
|
||||
|
|
||||
void delete_case4(rbtree t, node n) { |
|
||||
if (node_color(n->parent) == RED && |
|
||||
node_color(sibling(n)) == BLACK && |
|
||||
node_color(sibling(n)->left) == BLACK && |
|
||||
node_color(sibling(n)->right) == BLACK) |
|
||||
{ |
|
||||
sibling(n)->color = RED; |
|
||||
n->parent->color = BLACK; |
|
||||
} |
|
||||
else |
|
||||
delete_case5(t, n); |
|
||||
} |
|
||||
|
|
||||
void delete_case5(rbtree t, node n) { |
|
||||
if (n == n->parent->left && |
|
||||
node_color(sibling(n)) == BLACK && |
|
||||
node_color(sibling(n)->left) == RED && |
|
||||
node_color(sibling(n)->right) == BLACK) |
|
||||
{ |
|
||||
sibling(n)->color = RED; |
|
||||
sibling(n)->left->color = BLACK; |
|
||||
rotate_right(t, sibling(n)); |
|
||||
} |
|
||||
else if (n == n->parent->right && |
|
||||
node_color(sibling(n)) == BLACK && |
|
||||
node_color(sibling(n)->right) == RED && |
|
||||
node_color(sibling(n)->left) == BLACK) |
|
||||
{ |
|
||||
sibling(n)->color = RED; |
|
||||
sibling(n)->right->color = BLACK; |
|
||||
rotate_left(t, sibling(n)); |
|
||||
} |
|
||||
delete_case6(t, n); |
|
||||
} |
|
||||
|
|
||||
void delete_case6(rbtree t, node n) { |
|
||||
sibling(n)->color = node_color(n->parent); |
|
||||
n->parent->color = BLACK; |
|
||||
if (n == n->parent->left) { |
|
||||
assert (node_color(sibling(n)->right) == RED); |
|
||||
sibling(n)->right->color = BLACK; |
|
||||
rotate_left(t, n->parent); |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
assert (node_color(sibling(n)->left) == RED); |
|
||||
sibling(n)->left->color = BLACK; |
|
||||
rotate_right(t, n->parent); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
|
|
@ -1,54 +0,0 @@ |
|||||
/* Copyright (c) 2008 Derrick Coetzee
|
|
||||
* http://en.literateprograms.org/Red-black_tree_(C)?oldid=7982
|
|
||||
* Small changes by Ryah Dahl |
|
||||
* |
|
||||
* 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 rights to use, copy, modify, merge, publish, |
|
||||
* 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. |
|
||||
*/ |
|
||||
|
|
||||
#ifndef _RBTREE_H_ |
|
||||
#define _RBTREE_H_ |
|
||||
|
|
||||
enum rbtree_node_color { RED, BLACK }; |
|
||||
|
|
||||
typedef int (*rbtree_compare_func)(void* left_key, void* right_key); |
|
||||
|
|
||||
typedef struct rbtree_node_t { |
|
||||
struct rbtree_node_t* left; /* private */ |
|
||||
struct rbtree_node_t* right; /* private */ |
|
||||
struct rbtree_node_t* parent; /* private */ |
|
||||
enum rbtree_node_color color; /* private */ |
|
||||
void* key; /* public */ |
|
||||
void* value; /* public */ |
|
||||
} *rbtree_node; |
|
||||
|
|
||||
typedef struct rbtree_t { |
|
||||
rbtree_node root; /* private */ |
|
||||
rbtree_compare_func compare; /* private */ |
|
||||
} *rbtree; |
|
||||
|
|
||||
|
|
||||
void rbtree_init(rbtree t, rbtree_compare_func); |
|
||||
void* rbtree_lookup(rbtree t, void* key); |
|
||||
void rbtree_insert(rbtree t, rbtree_node); |
|
||||
/* you must free the returned node */ |
|
||||
rbtree_node rbtree_delete(rbtree t, void* key); |
|
||||
|
|
||||
#endif |
|
||||
|
|
@ -1,60 +0,0 @@ |
|||||
require 'test/unit' |
|
||||
require 'socket' |
|
||||
|
|
||||
REQ = "GET /hello/%d HTTP/1.1\r\n\r\n" |
|
||||
HOST = '0.0.0.0' |
|
||||
PORT = 5000 |
|
||||
|
|
||||
class TCPSocket |
|
||||
def full_send(string) |
|
||||
written = 0 |
|
||||
while(written < string.length) |
|
||||
sent = write(string) |
|
||||
string = string.slice(sent, 10000) |
|
||||
end |
|
||||
written |
|
||||
end |
|
||||
|
|
||||
def full_read |
|
||||
response = "" |
|
||||
while chunk = read(10000) |
|
||||
response += chunk |
|
||||
end |
|
||||
response |
|
||||
end |
|
||||
|
|
||||
end |
|
||||
|
|
||||
class EbbTest < Test::Unit::TestCase |
|
||||
|
|
||||
def setup |
|
||||
@socket = TCPSocket.new(HOST, PORT) |
|
||||
end |
|
||||
|
|
||||
def test_bad_req |
|
||||
@socket.full_send("hello") |
|
||||
assert_equal "", @socket.full_read |
|
||||
#assert @socket.closed? |
|
||||
end |
|
||||
|
|
||||
def test_single |
|
||||
written = 0 |
|
||||
req = REQ % 1 |
|
||||
@socket.full_send(req) |
|
||||
response = @socket.full_read() |
|
||||
count = 0 |
|
||||
response.scan("hello world") { count += 1 } |
|
||||
|
|
||||
assert_equal 1, count |
|
||||
end |
|
||||
|
|
||||
def test_pipeline |
|
||||
written = 0 |
|
||||
req = (REQ % 1) + (REQ % 2) + (REQ % 3) + (REQ % 4) |
|
||||
@socket.full_send(req) |
|
||||
response = @socket.full_read() |
|
||||
count = 0 |
|
||||
response.scan("hello world") { count += 1 } |
|
||||
assert_equal 4, count |
|
||||
end |
|
||||
end |
|
@ -1,108 +0,0 @@ |
|||||
/* Copyright (c) 2008 Derrick Coetzee
|
|
||||
* http://en.literateprograms.org/Red-black_tree_(C)?oldid=7982
|
|
||||
* |
|
||||
* 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 rights to use, copy, modify, merge, publish, |
|
||||
* 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. |
|
||||
*/ |
|
||||
|
|
||||
#include "rbtree.h" |
|
||||
#include <stdio.h> |
|
||||
#include <assert.h> |
|
||||
#include <stdlib.h> /* rand(), malloc(), free() */ |
|
||||
|
|
||||
static int compare_int(void* left, void* right); |
|
||||
static void print_tree(rbtree t); |
|
||||
static void print_tree_helper(rbtree_node n, int indent); |
|
||||
|
|
||||
int compare_int(void* leftp, void* rightp) { |
|
||||
int left = (int)leftp; |
|
||||
int right = (int)rightp; |
|
||||
if (left < right) |
|
||||
return -1; |
|
||||
else if (left > right) |
|
||||
return 1; |
|
||||
else { |
|
||||
assert (left == right); |
|
||||
return 0; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
#define INDENT_STEP 4 |
|
||||
|
|
||||
void print_tree_helper(rbtree_node n, int indent); |
|
||||
|
|
||||
void print_tree(rbtree t) { |
|
||||
print_tree_helper(t->root, 0); |
|
||||
puts(""); |
|
||||
} |
|
||||
|
|
||||
void print_tree_helper(rbtree_node n, int indent) { |
|
||||
int i; |
|
||||
if (n == NULL) { |
|
||||
fputs("<empty tree>", stdout); |
|
||||
return; |
|
||||
} |
|
||||
if (n->right != NULL) { |
|
||||
print_tree_helper(n->right, indent + INDENT_STEP); |
|
||||
} |
|
||||
for(i=0; i<indent; i++) |
|
||||
fputs(" ", stdout); |
|
||||
if (n->color == BLACK) |
|
||||
printf("%d\n", (int)n->key); |
|
||||
else |
|
||||
printf("<%d>\n", (int)n->key); |
|
||||
if (n->left != NULL) { |
|
||||
print_tree_helper(n->left, indent + INDENT_STEP); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
int main() { |
|
||||
int i; |
|
||||
struct rbtree_t t; |
|
||||
rbtree_init(&t, compare_int); |
|
||||
print_tree(&t); |
|
||||
|
|
||||
for(i=0; i<5000; i++) { |
|
||||
int x = rand() % 10000; |
|
||||
int y = rand() % 10000; |
|
||||
#ifdef TRACE |
|
||||
print_tree(&t); |
|
||||
printf("Inserting %d -> %d\n\n", x, y); |
|
||||
#endif |
|
||||
rbtree_node node = (rbtree_node) malloc(sizeof(struct rbtree_node_t)); |
|
||||
node->key = (void*)x; |
|
||||
node->value = (void*)y; |
|
||||
rbtree_insert(&t, node); |
|
||||
assert(rbtree_lookup(&t, (void*)x) == (void*)y); |
|
||||
} |
|
||||
for(i=0; i<60000; i++) { |
|
||||
int x = rand() % 10000; |
|
||||
#ifdef TRACE |
|
||||
print_tree(&t); |
|
||||
printf("Deleting key %d\n\n", x); |
|
||||
#endif |
|
||||
rbtree_node n = rbtree_delete(&t, (void*)x); |
|
||||
if(n != NULL) { |
|
||||
free(n); |
|
||||
} |
|
||||
} |
|
||||
printf("Okay\n"); |
|
||||
return 0; |
|
||||
} |
|
||||
|
|
@ -1,746 +0,0 @@ |
|||||
/* unit tests for request parser
|
|
||||
* Copyright 2008 ryah dahl, ry@ndahl.us |
|
||||
* |
|
||||
* This software may be distributed under the "MIT" license included in the |
|
||||
* README |
|
||||
*/ |
|
||||
#include "ebb_request_parser.h" |
|
||||
#include <stdlib.h> |
|
||||
#include <assert.h> |
|
||||
#include <stdio.h> |
|
||||
#include <string.h> |
|
||||
|
|
||||
#define TRUE 1 |
|
||||
#define FALSE 0 |
|
||||
|
|
||||
#define MAX_HEADERS 500 |
|
||||
#define MAX_ELEMENT_SIZE 500 |
|
||||
|
|
||||
static ebb_request_parser parser; |
|
||||
struct request_data { |
|
||||
const char *raw; |
|
||||
int request_method; |
|
||||
char request_path[MAX_ELEMENT_SIZE]; |
|
||||
char request_uri[MAX_ELEMENT_SIZE]; |
|
||||
char fragment[MAX_ELEMENT_SIZE]; |
|
||||
char query_string[MAX_ELEMENT_SIZE]; |
|
||||
char body[MAX_ELEMENT_SIZE]; |
|
||||
int num_headers; |
|
||||
char header_fields[MAX_HEADERS][MAX_ELEMENT_SIZE]; |
|
||||
char header_values[MAX_HEADERS][MAX_ELEMENT_SIZE]; |
|
||||
int should_keep_alive; |
|
||||
ebb_request request; |
|
||||
}; |
|
||||
static struct request_data requests[5]; |
|
||||
static int num_requests; |
|
||||
|
|
||||
const struct request_data curl_get = |
|
||||
{ raw: "GET /test HTTP/1.1\r\n" |
|
||||
"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\r\n" |
|
||||
"Host: 0.0.0.0:5000\r\n" |
|
||||
"Accept: */*\r\n" |
|
||||
"\r\n" |
|
||||
, should_keep_alive: TRUE |
|
||||
, request_method: EBB_GET |
|
||||
, query_string: "" |
|
||||
, fragment: "" |
|
||||
, request_path: "/test" |
|
||||
, request_uri: "/test" |
|
||||
, num_headers: 3 |
|
||||
, header_fields: { "User-Agent", "Host", "Accept" } |
|
||||
, header_values: { "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1", "0.0.0.0:5000", "*/*" } |
|
||||
, body: "" |
|
||||
}; |
|
||||
|
|
||||
const struct request_data firefox_get = |
|
||||
{ raw: "GET /favicon.ico HTTP/1.1\r\n" |
|
||||
"Host: 0.0.0.0:5000\r\n" |
|
||||
"User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n" |
|
||||
"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" |
|
||||
"Accept-Language: en-us,en;q=0.5\r\n" |
|
||||
"Accept-Encoding: gzip,deflate\r\n" |
|
||||
"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" |
|
||||
"Keep-Alive: 300\r\n" |
|
||||
"Connection: keep-alive\r\n" |
|
||||
"\r\n" |
|
||||
, should_keep_alive: TRUE |
|
||||
, request_method: EBB_GET |
|
||||
, query_string: "" |
|
||||
, fragment: "" |
|
||||
, request_path: "/favicon.ico" |
|
||||
, request_uri: "/favicon.ico" |
|
||||
, num_headers: 8 |
|
||||
, header_fields: |
|
||||
{ "Host" |
|
||||
, "User-Agent" |
|
||||
, "Accept" |
|
||||
, "Accept-Language" |
|
||||
, "Accept-Encoding" |
|
||||
, "Accept-Charset" |
|
||||
, "Keep-Alive" |
|
||||
, "Connection" |
|
||||
} |
|
||||
, header_values: |
|
||||
{ "0.0.0.0:5000" |
|
||||
, "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0" |
|
||||
, "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" |
|
||||
, "en-us,en;q=0.5" |
|
||||
, "gzip,deflate" |
|
||||
, "ISO-8859-1,utf-8;q=0.7,*;q=0.7" |
|
||||
, "300" |
|
||||
, "keep-alive" |
|
||||
} |
|
||||
, body: "" |
|
||||
}; |
|
||||
|
|
||||
const struct request_data dumbfuck = |
|
||||
{ raw: "GET /dumbfuck HTTP/1.1\r\n" |
|
||||
"aaaaaaaaaaaaa:++++++++++\r\n" |
|
||||
"\r\n" |
|
||||
, should_keep_alive: TRUE |
|
||||
, request_method: EBB_GET |
|
||||
, query_string: "" |
|
||||
, fragment: "" |
|
||||
, request_path: "/dumbfuck" |
|
||||
, request_uri: "/dumbfuck" |
|
||||
, num_headers: 1 |
|
||||
, header_fields: { "aaaaaaaaaaaaa" } |
|
||||
, header_values: { "++++++++++" } |
|
||||
, body: "" |
|
||||
}; |
|
||||
|
|
||||
const struct request_data fragment_in_uri = |
|
||||
{ raw: "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n" |
|
||||
"\r\n" |
|
||||
, should_keep_alive: TRUE |
|
||||
, request_method: EBB_GET |
|
||||
, query_string: "page=1" |
|
||||
, fragment: "posts-17408" |
|
||||
, request_path: "/forums/1/topics/2375" |
|
||||
/* XXX request uri does not include fragment? */ |
|
||||
, request_uri: "/forums/1/topics/2375?page=1" |
|
||||
, num_headers: 0 |
|
||||
, body: "" |
|
||||
}; |
|
||||
|
|
||||
|
|
||||
// get - no headers - no body
|
|
||||
const struct request_data get_no_headers_no_body = |
|
||||
{ raw: "GET /get_no_headers_no_body/world HTTP/1.1\r\n" |
|
||||
"\r\n" |
|
||||
, should_keep_alive: TRUE |
|
||||
, request_method: EBB_GET |
|
||||
, query_string: "" |
|
||||
, fragment: "" |
|
||||
, request_path: "/get_no_headers_no_body/world" |
|
||||
, request_uri: "/get_no_headers_no_body/world" |
|
||||
, num_headers: 0 |
|
||||
, body: "" |
|
||||
}; |
|
||||
|
|
||||
// get - one header - no body
|
|
||||
const struct request_data get_one_header_no_body = |
|
||||
{ raw: "GET /get_one_header_no_body HTTP/1.1\r\n" |
|
||||
"Accept: */*\r\n" |
|
||||
"\r\n" |
|
||||
, should_keep_alive: TRUE |
|
||||
, request_method: EBB_GET |
|
||||
, query_string: "" |
|
||||
, fragment: "" |
|
||||
, request_path: "/get_one_header_no_body" |
|
||||
, request_uri: "/get_one_header_no_body" |
|
||||
, num_headers: 1 |
|
||||
, header_fields: { "Accept" } |
|
||||
, header_values: { "*/*" } |
|
||||
, body: "" |
|
||||
}; |
|
||||
|
|
||||
// get - no headers - body "HELLO"
|
|
||||
const struct request_data get_funky_content_length_body_hello = |
|
||||
{ raw: "GET /get_funky_content_length_body_hello HTTP/1.0\r\n" |
|
||||
"conTENT-Length: 5\r\n" |
|
||||
"\r\n" |
|
||||
"HELLO" |
|
||||
, should_keep_alive: FALSE |
|
||||
, request_method: EBB_GET |
|
||||
, query_string: "" |
|
||||
, fragment: "" |
|
||||
, request_path: "/get_funky_content_length_body_hello" |
|
||||
, request_uri: "/get_funky_content_length_body_hello" |
|
||||
, num_headers: 1 |
|
||||
, header_fields: { "conTENT-Length" } |
|
||||
, header_values: { "5" } |
|
||||
, body: "HELLO" |
|
||||
}; |
|
||||
|
|
||||
// post - one header - body "World"
|
|
||||
const struct request_data post_identity_body_world = |
|
||||
{ raw: "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n" |
|
||||
"Accept: */*\r\n" |
|
||||
"Transfer-Encoding: identity\r\n" |
|
||||
"Content-Length: 5\r\n" |
|
||||
"\r\n" |
|
||||
"World" |
|
||||
, should_keep_alive: TRUE |
|
||||
, request_method: EBB_POST |
|
||||
, query_string: "q=search" |
|
||||
, fragment: "hey" |
|
||||
, request_path: "/post_identity_body_world" |
|
||||
, request_uri: "/post_identity_body_world?q=search" |
|
||||
, num_headers: 3 |
|
||||
, header_fields: { "Accept", "Transfer-Encoding", "Content-Length" } |
|
||||
, header_values: { "*/*", "identity", "5" } |
|
||||
, body: "World" |
|
||||
}; |
|
||||
|
|
||||
// post - no headers - chunked body "all your base are belong to us"
|
|
||||
const struct request_data post_chunked_all_your_base = |
|
||||
{ raw: "POST /post_chunked_all_your_base HTTP/1.1\r\n" |
|
||||
"Transfer-Encoding: chunked\r\n" |
|
||||
"\r\n" |
|
||||
"1e\r\nall your base are belong to us\r\n" |
|
||||
"0\r\n" |
|
||||
"\r\n" |
|
||||
, should_keep_alive: TRUE |
|
||||
, request_method: EBB_POST |
|
||||
, query_string: "" |
|
||||
, fragment: "" |
|
||||
, request_path: "/post_chunked_all_your_base" |
|
||||
, request_uri: "/post_chunked_all_your_base" |
|
||||
, num_headers: 1 |
|
||||
, header_fields: { "Transfer-Encoding" } |
|
||||
, header_values: { "chunked" } |
|
||||
, body: "all your base are belong to us" |
|
||||
}; |
|
||||
|
|
||||
// two chunks ; triple zero ending
|
|
||||
const struct request_data two_chunks_mult_zero_end = |
|
||||
{ raw: "POST /two_chunks_mult_zero_end HTTP/1.1\r\n" |
|
||||
"Transfer-Encoding: chunked\r\n" |
|
||||
"\r\n" |
|
||||
"5\r\nhello\r\n" |
|
||||
"6\r\n world\r\n" |
|
||||
"000\r\n" |
|
||||
"\r\n" |
|
||||
, should_keep_alive: TRUE |
|
||||
, request_method: EBB_POST |
|
||||
, query_string: "" |
|
||||
, fragment: "" |
|
||||
, request_path: "/two_chunks_mult_zero_end" |
|
||||
, request_uri: "/two_chunks_mult_zero_end" |
|
||||
, num_headers: 1 |
|
||||
, header_fields: { "Transfer-Encoding" } |
|
||||
, header_values: { "chunked" } |
|
||||
, body: "hello world" |
|
||||
}; |
|
||||
|
|
||||
|
|
||||
// chunked with trailing headers. blech.
|
|
||||
const struct request_data chunked_w_trailing_headers = |
|
||||
{ raw: "POST /chunked_w_trailing_headers HTTP/1.1\r\n" |
|
||||
"Transfer-Encoding: chunked\r\n" |
|
||||
"\r\n" |
|
||||
"5\r\nhello\r\n" |
|
||||
"6\r\n world\r\n" |
|
||||
"0\r\n" |
|
||||
"Vary: *\r\n" |
|
||||
"Content-Type: text/plain\r\n" |
|
||||
"\r\n" |
|
||||
, should_keep_alive: TRUE |
|
||||
, request_method: EBB_POST |
|
||||
, query_string: "" |
|
||||
, fragment: "" |
|
||||
, request_path: "/chunked_w_trailing_headers" |
|
||||
, request_uri: "/chunked_w_trailing_headers" |
|
||||
, num_headers: 1 |
|
||||
, header_fields: { "Transfer-Encoding" } |
|
||||
, header_values: { "chunked" } |
|
||||
, body: "hello world" |
|
||||
}; |
|
||||
|
|
||||
// with bullshit after the length
|
|
||||
const struct request_data chunked_w_bullshit_after_length = |
|
||||
{ raw: "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n" |
|
||||
"Transfer-Encoding: chunked\r\n" |
|
||||
"\r\n" |
|
||||
"5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n" |
|
||||
"6; blahblah; blah\r\n world\r\n" |
|
||||
"0\r\n" |
|
||||
"\r\n" |
|
||||
, should_keep_alive: TRUE |
|
||||
, request_method: EBB_POST |
|
||||
, query_string: "" |
|
||||
, fragment: "" |
|
||||
, request_path: "/chunked_w_bullshit_after_length" |
|
||||
, request_uri: "/chunked_w_bullshit_after_length" |
|
||||
, num_headers: 1 |
|
||||
, header_fields: { "Transfer-Encoding" } |
|
||||
, header_values: { "chunked" } |
|
||||
, body: "hello world" |
|
||||
}; |
|
||||
|
|
||||
const struct request_data *fixtures[] = |
|
||||
{ &curl_get |
|
||||
, &firefox_get |
|
||||
, &dumbfuck |
|
||||
, &fragment_in_uri |
|
||||
, &get_no_headers_no_body |
|
||||
, &get_one_header_no_body |
|
||||
, &get_funky_content_length_body_hello |
|
||||
, &post_identity_body_world |
|
||||
, &post_chunked_all_your_base |
|
||||
, &two_chunks_mult_zero_end |
|
||||
, &chunked_w_trailing_headers |
|
||||
, &chunked_w_bullshit_after_length |
|
||||
, NULL |
|
||||
}; |
|
||||
|
|
||||
|
|
||||
int request_data_eq |
|
||||
( struct request_data *r1 |
|
||||
, const struct request_data *r2 |
|
||||
) |
|
||||
{ |
|
||||
if(ebb_request_should_keep_alive(&r1->request) != r2->should_keep_alive) { |
|
||||
printf("requests disagree on keep-alive"); |
|
||||
return FALSE; |
|
||||
} |
|
||||
|
|
||||
if(0 != strcmp(r1->body, r2->body)) { |
|
||||
printf("body '%s' != '%s'\n", r1->body, r2->body); |
|
||||
return FALSE; |
|
||||
} |
|
||||
if(0 != strcmp(r1->fragment, r2->fragment)) { |
|
||||
printf("fragment '%s' != '%s'\n", r1->fragment, r2->fragment); |
|
||||
return FALSE; |
|
||||
} |
|
||||
if(0 != strcmp(r1->query_string, r2->query_string)) { |
|
||||
printf("query_string '%s' != '%s'\n", r1->query_string, r2->query_string); |
|
||||
return FALSE; |
|
||||
} |
|
||||
if(r1->request.method != r2->request_method) { |
|
||||
printf("request_method '%d' != '%d'\n", r1->request.method, r2->request_method); |
|
||||
return FALSE; |
|
||||
} |
|
||||
if(0 != strcmp(r1->request_path, r2->request_path)) { |
|
||||
printf("request_path '%s' != '%s'\n", r1->request_path, r2->request_path); |
|
||||
return FALSE; |
|
||||
} |
|
||||
if(0 != strcmp(r1->request_uri, r2->request_uri)) { |
|
||||
printf("request_uri '%s' != '%s'\n", r1->request_uri, r2->request_uri); |
|
||||
return FALSE; |
|
||||
} |
|
||||
if(r1->num_headers != r2->num_headers) { |
|
||||
printf("num_headers '%d' != '%d'\n", r1->num_headers, r2->num_headers); |
|
||||
return FALSE; |
|
||||
} |
|
||||
int i; |
|
||||
for(i = 0; i < r1->num_headers; i++) { |
|
||||
if(0 != strcmp(r1->header_fields[i], r2->header_fields[i])) { |
|
||||
printf("header field '%s' != '%s'\n", r1->header_fields[i], r2->header_fields[i]); |
|
||||
return FALSE; |
|
||||
} |
|
||||
if(0 != strcmp(r1->header_values[i], r2->header_values[i])) { |
|
||||
printf("header field '%s' != '%s'\n", r1->header_values[i], r2->header_values[i]); |
|
||||
return FALSE; |
|
||||
} |
|
||||
} |
|
||||
return TRUE; |
|
||||
} |
|
||||
|
|
||||
int request_eq |
|
||||
( int index |
|
||||
, const struct request_data *expected |
|
||||
) |
|
||||
{ |
|
||||
return request_data_eq(&requests[index], expected); |
|
||||
} |
|
||||
|
|
||||
void request_complete(ebb_request *info) |
|
||||
{ |
|
||||
num_requests++; |
|
||||
} |
|
||||
|
|
||||
void request_path_cb(ebb_request *request, const char *p, size_t len) |
|
||||
{ |
|
||||
strncat(requests[num_requests].request_path, p, len); |
|
||||
} |
|
||||
|
|
||||
void request_uri_cb(ebb_request *request, const char *p, size_t len) |
|
||||
{ |
|
||||
strncat(requests[num_requests].request_uri, p, len); |
|
||||
} |
|
||||
|
|
||||
void query_string_cb(ebb_request *request, const char *p, size_t len) |
|
||||
{ |
|
||||
strncat(requests[num_requests].query_string, p, len); |
|
||||
} |
|
||||
|
|
||||
void fragment_cb(ebb_request *request, const char *p, size_t len) |
|
||||
{ |
|
||||
strncat(requests[num_requests].fragment, p, len); |
|
||||
} |
|
||||
|
|
||||
void header_field_cb(ebb_request *request, const char *p, size_t len, int header_index) |
|
||||
{ |
|
||||
strncat(requests[num_requests].header_fields[header_index], p, len); |
|
||||
} |
|
||||
|
|
||||
void header_value_cb(ebb_request *request, const char *p, size_t len, int header_index) |
|
||||
{ |
|
||||
strncat(requests[num_requests].header_values[header_index], p, len); |
|
||||
requests[num_requests].num_headers = header_index + 1; |
|
||||
} |
|
||||
|
|
||||
void body_handler(ebb_request *request, const char *p, size_t len) |
|
||||
{ |
|
||||
strncat(requests[num_requests].body, p, len); |
|
||||
// printf("body_handler: '%s'\n", requests[num_requests].body);
|
|
||||
} |
|
||||
|
|
||||
ebb_request* new_request () |
|
||||
{ |
|
||||
requests[num_requests].num_headers = 0; |
|
||||
requests[num_requests].request_method = -1; |
|
||||
requests[num_requests].request_path[0] = 0; |
|
||||
requests[num_requests].request_uri[0] = 0; |
|
||||
requests[num_requests].fragment[0] = 0; |
|
||||
requests[num_requests].query_string[0] = 0; |
|
||||
requests[num_requests].body[0] = 0; |
|
||||
int i; |
|
||||
for(i = 0; i < MAX_HEADERS; i++) { |
|
||||
requests[num_requests].header_fields[i][0] = 0; |
|
||||
requests[num_requests].header_values[i][0] = 0; |
|
||||
} |
|
||||
|
|
||||
ebb_request *r = &requests[num_requests].request; |
|
||||
ebb_request_init(r); |
|
||||
|
|
||||
r->on_complete = request_complete; |
|
||||
r->on_header_field = header_field_cb; |
|
||||
r->on_header_value = header_value_cb; |
|
||||
r->on_path = request_path_cb; |
|
||||
r->on_uri = request_uri_cb; |
|
||||
r->on_fragment = fragment_cb; |
|
||||
r->on_query_string = query_string_cb; |
|
||||
r->on_body = body_handler; |
|
||||
r->on_headers_complete = NULL; |
|
||||
|
|
||||
r->data = &requests[num_requests]; |
|
||||
// printf("new request %d\n", num_requests);
|
|
||||
return r; |
|
||||
} |
|
||||
|
|
||||
void parser_init() |
|
||||
{ |
|
||||
num_requests = 0; |
|
||||
|
|
||||
ebb_request_parser_init(&parser); |
|
||||
|
|
||||
parser.new_request = new_request; |
|
||||
} |
|
||||
|
|
||||
int test_request |
|
||||
( const struct request_data *request_data |
|
||||
) |
|
||||
{ |
|
||||
size_t traversed = 0; |
|
||||
parser_init(); |
|
||||
|
|
||||
traversed = ebb_request_parser_execute( &parser |
|
||||
, request_data->raw |
|
||||
, strlen(request_data->raw) |
|
||||
); |
|
||||
if( ebb_request_parser_has_error(&parser) ) |
|
||||
return FALSE; |
|
||||
if(! ebb_request_parser_is_finished(&parser) ) |
|
||||
return FALSE; |
|
||||
if(num_requests != 1) |
|
||||
return FALSE; |
|
||||
|
|
||||
return request_eq(0, request_data); |
|
||||
} |
|
||||
|
|
||||
int test_error |
|
||||
( const char *buf |
|
||||
) |
|
||||
{ |
|
||||
size_t traversed = 0; |
|
||||
parser_init(); |
|
||||
|
|
||||
traversed = ebb_request_parser_execute(&parser, buf, strlen(buf)); |
|
||||
|
|
||||
return ebb_request_parser_has_error(&parser); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
int test_multiple3 |
|
||||
( const struct request_data *r1 |
|
||||
, const struct request_data *r2 |
|
||||
, const struct request_data *r3 |
|
||||
) |
|
||||
{ |
|
||||
char total[80*1024] = "\0"; |
|
||||
|
|
||||
strcat(total, r1->raw); |
|
||||
strcat(total, r2->raw); |
|
||||
strcat(total, r3->raw); |
|
||||
|
|
||||
size_t traversed = 0; |
|
||||
parser_init(); |
|
||||
|
|
||||
traversed = ebb_request_parser_execute(&parser, total, strlen(total)); |
|
||||
|
|
||||
|
|
||||
if( ebb_request_parser_has_error(&parser) ) |
|
||||
return FALSE; |
|
||||
if(! ebb_request_parser_is_finished(&parser) ) |
|
||||
return FALSE; |
|
||||
if(num_requests != 3) |
|
||||
return FALSE; |
|
||||
|
|
||||
if(!request_eq(0, r1)){ |
|
||||
printf("request 1 error.\n"); |
|
||||
return FALSE; |
|
||||
} |
|
||||
if(!request_eq(1, r2)){ |
|
||||
printf("request 2 error.\n"); |
|
||||
return FALSE; |
|
||||
} |
|
||||
if(!request_eq(2, r3)){ |
|
||||
printf("request 3 error.\n"); |
|
||||
return FALSE; |
|
||||
} |
|
||||
|
|
||||
return TRUE; |
|
||||
} |
|
||||
|
|
||||
/**
|
|
||||
* SCAN through every possible breaking to make sure the |
|
||||
* parser can handle getting the content in any chunks that |
|
||||
* might come from the socket |
|
||||
*/ |
|
||||
int test_scan2 |
|
||||
( const struct request_data *r1 |
|
||||
, const struct request_data *r2 |
|
||||
, const struct request_data *r3 |
|
||||
) |
|
||||
{ |
|
||||
char total[80*1024] = "\0"; |
|
||||
char buf1[80*1024] = "\0"; |
|
||||
char buf2[80*1024] = "\0"; |
|
||||
|
|
||||
strcat(total, r1->raw); |
|
||||
strcat(total, r2->raw); |
|
||||
strcat(total, r3->raw); |
|
||||
|
|
||||
int total_len = strlen(total); |
|
||||
|
|
||||
//printf("total_len = %d\n", total_len);
|
|
||||
int i; |
|
||||
for(i = 1; i < total_len - 1; i ++ ) { |
|
||||
|
|
||||
parser_init(); |
|
||||
|
|
||||
int buf1_len = i; |
|
||||
strncpy(buf1, total, buf1_len); |
|
||||
buf1[buf1_len] = 0; |
|
||||
|
|
||||
int buf2_len = total_len - i; |
|
||||
strncpy(buf2, total+i, buf2_len); |
|
||||
buf2[buf2_len] = 0; |
|
||||
|
|
||||
ebb_request_parser_execute(&parser, buf1, buf1_len); |
|
||||
|
|
||||
if( ebb_request_parser_has_error(&parser) ) { |
|
||||
return FALSE; |
|
||||
} |
|
||||
/*
|
|
||||
if(ebb_request_parser_is_finished(&parser)) |
|
||||
return FALSE; |
|
||||
*/ |
|
||||
|
|
||||
ebb_request_parser_execute(&parser, buf2, buf2_len); |
|
||||
|
|
||||
if( ebb_request_parser_has_error(&parser)) |
|
||||
return FALSE; |
|
||||
if(!ebb_request_parser_is_finished(&parser)) |
|
||||
return FALSE; |
|
||||
|
|
||||
if(3 != num_requests) { |
|
||||
printf("scan error: got %d requests in iteration %d\n", num_requests, i); |
|
||||
return FALSE; |
|
||||
} |
|
||||
|
|
||||
if(!request_eq(0, r1)) { |
|
||||
printf("not maching r1\n"); |
|
||||
return FALSE; |
|
||||
} |
|
||||
if(!request_eq(1, r2)) { |
|
||||
printf("not maching r2\n"); |
|
||||
return FALSE; |
|
||||
} |
|
||||
if(!request_eq(2, r3)) { |
|
||||
printf("not maching r3\n"); |
|
||||
return FALSE; |
|
||||
} |
|
||||
} |
|
||||
return TRUE; |
|
||||
} |
|
||||
|
|
||||
int test_scan3 |
|
||||
( const struct request_data *r1 |
|
||||
, const struct request_data *r2 |
|
||||
, const struct request_data *r3 |
|
||||
) |
|
||||
{ |
|
||||
char total[80*1024] = "\0"; |
|
||||
char buf1[80*1024] = "\0"; |
|
||||
char buf2[80*1024] = "\0"; |
|
||||
char buf3[80*1024] = "\0"; |
|
||||
|
|
||||
strcat(total, r1->raw); |
|
||||
strcat(total, r2->raw); |
|
||||
strcat(total, r3->raw); |
|
||||
|
|
||||
int total_len = strlen(total); |
|
||||
|
|
||||
//printf("total_len = %d\n", total_len);
|
|
||||
int i,j; |
|
||||
for(j = 2; j < total_len - 1; j ++ ) { |
|
||||
for(i = 1; i < j; i ++ ) { |
|
||||
|
|
||||
parser_init(); |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
int buf1_len = i; |
|
||||
strncpy(buf1, total, buf1_len); |
|
||||
buf1[buf1_len] = 0; |
|
||||
|
|
||||
int buf2_len = j - i; |
|
||||
strncpy(buf2, total+i, buf2_len); |
|
||||
buf2[buf2_len] = 0; |
|
||||
|
|
||||
int buf3_len = total_len - j; |
|
||||
strncpy(buf3, total+j, buf3_len); |
|
||||
buf3[buf3_len] = 0; |
|
||||
|
|
||||
/*
|
|
||||
printf("buf1: %s - %d\n", buf1, buf1_len); |
|
||||
printf("buf2: %s - %d \n", buf2, buf2_len ); |
|
||||
printf("buf3: %s - %d\n\n", buf3, buf3_len); |
|
||||
*/ |
|
||||
|
|
||||
ebb_request_parser_execute(&parser, buf1, buf1_len); |
|
||||
|
|
||||
if( ebb_request_parser_has_error(&parser) ) { |
|
||||
return FALSE; |
|
||||
} |
|
||||
|
|
||||
ebb_request_parser_execute(&parser, buf2, buf2_len); |
|
||||
|
|
||||
if( ebb_request_parser_has_error(&parser) ) { |
|
||||
return FALSE; |
|
||||
} |
|
||||
|
|
||||
ebb_request_parser_execute(&parser, buf3, buf3_len); |
|
||||
|
|
||||
if( ebb_request_parser_has_error(&parser)) |
|
||||
return FALSE; |
|
||||
if(!ebb_request_parser_is_finished(&parser)) |
|
||||
return FALSE; |
|
||||
|
|
||||
if(3 != num_requests) { |
|
||||
printf("scan error: only got %d requests in iteration %d\n", num_requests, i); |
|
||||
return FALSE; |
|
||||
} |
|
||||
|
|
||||
if(!request_eq(0, r1)) { |
|
||||
printf("not maching r1\n"); |
|
||||
return FALSE; |
|
||||
} |
|
||||
if(!request_eq(1, r2)) { |
|
||||
printf("not maching r2\n"); |
|
||||
return FALSE; |
|
||||
} |
|
||||
if(!request_eq(2, r3)) { |
|
||||
printf("not maching r3\n"); |
|
||||
return FALSE; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
return TRUE; |
|
||||
} |
|
||||
|
|
||||
int main() |
|
||||
{ |
|
||||
|
|
||||
assert(test_error("hello world")); |
|
||||
assert(test_error("GET / HTP/1.1\r\n\r\n")); |
|
||||
|
|
||||
assert(test_request(&curl_get)); |
|
||||
assert(test_request(&firefox_get)); |
|
||||
|
|
||||
// Zed's header tests
|
|
||||
|
|
||||
assert(test_request(&dumbfuck)); |
|
||||
|
|
||||
const char *dumbfuck2 = "GET / HTTP/1.1\r\nX-SSL-Bullshit: -----BEGIN CERTIFICATE-----\r\n\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgEBBAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n\tRA==\r\n\t-----END CERTIFICATE-----\r\n\r\n"; |
|
||||
assert(test_error(dumbfuck2)); |
|
||||
|
|
||||
assert(test_request(&fragment_in_uri)); |
|
||||
|
|
||||
/* TODO sending junk and large headers gets rejected */ |
|
||||
|
|
||||
|
|
||||
/* check to make sure our predefined requests are okay */ |
|
||||
|
|
||||
assert(test_request(&get_no_headers_no_body)); |
|
||||
assert(test_request(&get_one_header_no_body)); |
|
||||
assert(test_request(&get_no_headers_no_body)); |
|
||||
|
|
||||
// no content-length
|
|
||||
const char *bad_get_no_headers_no_body = "GET /bad_get_no_headers_no_body/world HTTP/1.1\r\nAccept: */*\r\nHELLO\r\n"; |
|
||||
assert(test_error(bad_get_no_headers_no_body)); // error if there is a body without content length
|
|
||||
|
|
||||
assert(test_request(&get_funky_content_length_body_hello)); |
|
||||
assert(test_request(&post_identity_body_world)); |
|
||||
assert(test_request(&post_chunked_all_your_base)); |
|
||||
assert(test_request(&two_chunks_mult_zero_end)); |
|
||||
assert(test_request(&chunked_w_trailing_headers)); |
|
||||
|
|
||||
assert(test_request(&chunked_w_bullshit_after_length)); |
|
||||
assert(1 == requests[0].request.version_major); |
|
||||
assert(1 == requests[0].request.version_minor); |
|
||||
|
|
||||
// three requests - no bodies
|
|
||||
assert( test_multiple3( &get_no_headers_no_body |
|
||||
, &get_one_header_no_body |
|
||||
, &get_no_headers_no_body |
|
||||
)); |
|
||||
|
|
||||
// three requests - one body
|
|
||||
assert( test_multiple3(&get_no_headers_no_body, &get_funky_content_length_body_hello, &get_no_headers_no_body)); |
|
||||
|
|
||||
// three requests with bodies -- last is chunked
|
|
||||
assert( test_multiple3(&get_funky_content_length_body_hello, &post_identity_body_world, &post_chunked_all_your_base)); |
|
||||
|
|
||||
// three chunked requests
|
|
||||
assert( test_multiple3(&two_chunks_mult_zero_end, &post_chunked_all_your_base, &chunked_w_trailing_headers)); |
|
||||
|
|
||||
|
|
||||
assert(test_scan2(&get_no_headers_no_body, &get_one_header_no_body, &get_no_headers_no_body)); |
|
||||
assert(test_scan2(&get_funky_content_length_body_hello, &post_identity_body_world, &post_chunked_all_your_base)); |
|
||||
assert(test_scan2(&two_chunks_mult_zero_end, &chunked_w_trailing_headers, &chunked_w_bullshit_after_length)); |
|
||||
|
|
||||
assert(test_scan3(&get_no_headers_no_body, &get_one_header_no_body, &get_no_headers_no_body)); |
|
||||
assert(test_scan3(&get_funky_content_length_body_hello, &post_identity_body_world, &post_chunked_all_your_base)); |
|
||||
assert(test_scan3(&two_chunks_mult_zero_end, &chunked_w_trailing_headers, &chunked_w_bullshit_after_length)); |
|
||||
|
|
||||
|
|
||||
printf("okay\n"); |
|
||||
return 0; |
|
||||
} |
|
||||
|
|
@ -1,35 +0,0 @@ |
|||||
#! /usr/bin/env python |
|
||||
# encoding: utf-8 |
|
||||
|
|
||||
"Ragel: '.rl' files are converted into .c files using 'ragel': {.rl -> .c -> .o}" |
|
||||
|
|
||||
import TaskGen, Task, Runner |
|
||||
|
|
||||
|
|
||||
def rageltaskfun(task): |
|
||||
env = task.env |
|
||||
ragelbin = env.get_flat('RAGEL') |
|
||||
if ragelbin: |
|
||||
if task.inputs[0].srcpath(env) == '../src/config_parser.rl': |
|
||||
cmd = '%s -o %s -C -T0 %s' % (ragelbin, task.outputs[0].bldpath(env), task.inputs[0].srcpath(env)) |
|
||||
else: |
|
||||
cmd = '%s -o %s -C -T1 %s' % (ragelbin, task.outputs[0].bldpath(env), task.inputs[0].srcpath(env)) |
|
||||
else: |
|
||||
src = task.inputs[0].srcpath(env) |
|
||||
src = src[:src.rfind('.')] + '.c' |
|
||||
cmd = 'cp %s %s' % (src, task.outputs[0].bldpath(env)) |
|
||||
return task.generator.bld.exec_command(cmd) |
|
||||
|
|
||||
rageltask = Task.task_type_from_func('ragel', rageltaskfun, vars = ['RAGEL'], color = 'BLUE', ext_in = '.rl', ext_out = '.c', before = 'c') |
|
||||
|
|
||||
@TaskGen.extension('.rl') |
|
||||
@TaskGen.before('apply_core') |
|
||||
def ragel(self, node): |
|
||||
out = node.change_ext('.c') |
|
||||
self.allnodes.append(out) |
|
||||
tsk = self.create_task('ragel') |
|
||||
tsk.set_inputs(node) |
|
||||
tsk.set_outputs(out) |
|
||||
|
|
||||
def detect(conf): |
|
||||
dang = conf.find_program('ragel', var='RAGEL') |
|
@ -1,691 +1,61 @@ |
|||||
#include "node.h" |
#include "node.h" |
||||
#include "http.h" |
#include "http.h" |
||||
|
#include <http_parser.h> |
||||
#include <oi_socket.h> |
|
||||
#include <ebb_request_parser.h> |
|
||||
|
|
||||
#include <string> |
|
||||
#include <list> |
|
||||
|
|
||||
#include <assert.h> |
#include <assert.h> |
||||
|
#include <stdio.h> |
||||
|
|
||||
using namespace v8; |
using namespace v8; |
||||
|
using namespace node; |
||||
using namespace std; |
using namespace std; |
||||
|
|
||||
static Persistent<ObjectTemplate> request_template; |
|
||||
|
|
||||
// globals
|
|
||||
static Persistent<String> path_str; |
|
||||
static Persistent<String> uri_str; |
|
||||
static Persistent<String> query_string_str; |
|
||||
static Persistent<String> fragment_str; |
|
||||
static Persistent<String> method_str; |
|
||||
static Persistent<String> http_version_str; |
|
||||
static Persistent<String> headers_str; |
|
||||
|
|
||||
static Persistent<String> on_request_str; |
|
||||
static Persistent<String> on_body_str; |
|
||||
static Persistent<String> respond_str; |
|
||||
|
|
||||
static Persistent<String> copy_str; |
|
||||
static Persistent<String> delete_str; |
|
||||
static Persistent<String> get_str; |
|
||||
static Persistent<String> head_str; |
|
||||
static Persistent<String> lock_str; |
|
||||
static Persistent<String> mkcol_str; |
|
||||
static Persistent<String> move_str; |
|
||||
static Persistent<String> options_str; |
|
||||
static Persistent<String> post_str; |
|
||||
static Persistent<String> propfind_str; |
|
||||
static Persistent<String> proppatch_str; |
|
||||
static Persistent<String> put_str; |
|
||||
static Persistent<String> trace_str; |
|
||||
static Persistent<String> unlock_str; |
|
||||
|
|
||||
#define INVALID_STATE_ERR 1 |
|
||||
|
|
||||
class HttpServer { |
|
||||
public: |
|
||||
HttpServer (Handle<Object> _js_server); |
|
||||
~HttpServer (); |
|
||||
|
|
||||
int Start(struct addrinfo *servinfo); |
|
||||
void Stop(); |
|
||||
|
|
||||
Handle<Value> Callback() |
|
||||
{ |
|
||||
HandleScope scope; |
|
||||
Handle<Value> value = js_server->Get(on_request_str); |
|
||||
return scope.Close(value); |
|
||||
} |
|
||||
|
|
||||
private: |
|
||||
oi_server server; |
|
||||
Persistent<Object> js_server; |
|
||||
}; |
|
||||
|
|
||||
class HttpRequest; |
|
||||
|
|
||||
class Connection { |
|
||||
public: |
|
||||
Connection(); |
|
||||
~Connection(); |
|
||||
|
|
||||
void Parse(const void *buf, size_t count); |
|
||||
void Write(); |
|
||||
void Close(); |
|
||||
void AddRequest (HttpRequest *request); |
|
||||
|
|
||||
oi_socket socket; |
|
||||
Persistent<Function> js_onrequest; |
|
||||
|
|
||||
private: |
|
||||
ebb_request_parser parser; |
|
||||
list<HttpRequest*> requests; |
|
||||
list<HttpRequest*> finished_requests; |
|
||||
friend class HttpServer; |
|
||||
}; |
|
||||
|
|
||||
class HttpRequest { |
|
||||
public: |
|
||||
HttpRequest (Connection &c); |
|
||||
/* Deleted from C++ as soon as possible.
|
|
||||
* Javascript object might linger. This is okay |
|
||||
*/ |
|
||||
~HttpRequest(); |
|
||||
|
|
||||
void MakeBodyCallback (const char *base, size_t length); |
|
||||
Local<Object> CreateJSObject (); |
|
||||
void Respond (Handle<Value> data); |
|
||||
|
|
||||
string path; |
|
||||
string query_string; |
|
||||
string fragment; |
|
||||
string uri; |
|
||||
|
|
||||
list<string> header_fields; |
|
||||
list<string> header_values; |
|
||||
|
|
||||
Connection &connection; |
|
||||
ebb_request parser_info; |
|
||||
|
|
||||
list<oi_buf*> output; |
|
||||
bool done; |
|
||||
Persistent<Object> js_object; |
|
||||
}; |
|
||||
|
|
||||
static Handle<Value> |
|
||||
GetMethodString (int method) |
|
||||
{ |
|
||||
switch(method) { |
|
||||
case EBB_COPY: return copy_str; |
|
||||
case EBB_DELETE: return delete_str; |
|
||||
case EBB_GET: return get_str; |
|
||||
case EBB_HEAD: return head_str; |
|
||||
case EBB_LOCK: return lock_str; |
|
||||
case EBB_MKCOL: return mkcol_str; |
|
||||
case EBB_MOVE: return move_str; |
|
||||
case EBB_OPTIONS: return options_str; |
|
||||
case EBB_POST: return post_str; |
|
||||
case EBB_PROPFIND: return propfind_str; |
|
||||
case EBB_PROPPATCH: return proppatch_str; |
|
||||
case EBB_PUT: return put_str; |
|
||||
case EBB_TRACE: return trace_str; |
|
||||
case EBB_UNLOCK: return unlock_str; |
|
||||
} |
|
||||
return Null(); |
|
||||
} |
|
||||
|
|
||||
static Handle<Value> |
|
||||
RespondCallback (const Arguments& args) |
|
||||
{ |
|
||||
HandleScope scope; |
|
||||
|
|
||||
Handle<Value> v = args.Holder()->GetInternalField(0); |
|
||||
if(v->IsUndefined()) { |
|
||||
// check that args.Holder()->GetInternalField(0)
|
|
||||
// is not NULL if so raise INVALID_STATE_ERR
|
|
||||
printf("null request external\n"); |
|
||||
ThrowException(Integer::New(INVALID_STATE_ERR)); |
|
||||
return Undefined(); |
|
||||
} |
|
||||
Handle<External> field = Handle<External>::Cast(v); |
|
||||
HttpRequest* request = static_cast<HttpRequest*>(field->Value()); |
|
||||
request->Respond(args[0]); |
|
||||
return Undefined(); |
|
||||
} |
|
||||
|
|
||||
void |
void |
||||
HttpRequest::Respond (Handle<Value> data) |
HTTPClient::Initialize (Handle<Object> target) |
||||
{ |
{ |
||||
if(data == Null()) { |
|
||||
done = true; |
|
||||
} else { |
|
||||
Handle<String> s = data->ToString(); |
|
||||
|
|
||||
size_t l1 = s->Utf8Length(), l2; |
|
||||
oi_buf *buf = oi_buf_new2(l1); |
|
||||
l2 = s->WriteUtf8(buf->base, l1); |
|
||||
assert(l1 == l2); |
|
||||
|
|
||||
output.push_back(buf); |
|
||||
} |
|
||||
connection.Write(); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
static void |
|
||||
on_path (ebb_request *req, const char *buf, size_t len) |
|
||||
{ |
|
||||
HttpRequest *request = static_cast<HttpRequest*> (req->data); |
|
||||
request->path.append(buf, len); |
|
||||
} |
|
||||
|
|
||||
static void |
|
||||
on_uri (ebb_request *req, const char *buf, size_t len) |
|
||||
{ |
|
||||
HttpRequest *request = static_cast<HttpRequest*> (req->data); |
|
||||
request->uri.append(buf, len); |
|
||||
} |
|
||||
|
|
||||
static void |
|
||||
on_query_string (ebb_request *req, const char *buf, size_t len) |
|
||||
{ |
|
||||
HttpRequest *request = static_cast<HttpRequest*> (req->data); |
|
||||
request->query_string.append(buf, len); |
|
||||
} |
|
||||
|
|
||||
static void |
|
||||
on_fragment (ebb_request *req, const char *buf, size_t len) |
|
||||
{ |
|
||||
HttpRequest *request = static_cast<HttpRequest*> (req->data); |
|
||||
request->fragment.append(buf, len); |
|
||||
} |
|
||||
|
|
||||
static const char upcase[] = |
|
||||
"\0______________________________" |
|
||||
"_________________0123456789_____" |
|
||||
"__ABCDEFGHIJKLMNOPQRSTUVWXYZ____" |
|
||||
"__ABCDEFGHIJKLMNOPQRSTUVWXYZ____" |
|
||||
"________________________________" |
|
||||
"________________________________" |
|
||||
"________________________________" |
|
||||
"________________________________"; |
|
||||
|
|
||||
static void |
|
||||
on_header_field (ebb_request *req, const char *buf, size_t len, int header_index) |
|
||||
{ |
|
||||
HttpRequest *request = static_cast<HttpRequest*> (req->data); |
|
||||
|
|
||||
char upbuf[len]; |
|
||||
|
|
||||
for(int i = 0; i < len; i++) |
|
||||
upbuf[i] = upcase[buf[i]]; |
|
||||
|
|
||||
if( request->header_fields.size() == header_index - 1) { |
|
||||
request->header_fields.back().append(upbuf, len); |
|
||||
} else { |
|
||||
request->header_fields.push_back( string(upbuf, len) ); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
static void |
|
||||
on_header_value (ebb_request *req, const char *buf, size_t len, int header_index) |
|
||||
{ |
|
||||
HttpRequest *request = static_cast<HttpRequest*> (req->data); |
|
||||
|
|
||||
if( request->header_values.size() == header_index - 1) { |
|
||||
request->header_values.back().append(buf, len); |
|
||||
} else { |
|
||||
request->header_values.push_back( string(buf, len) ); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
static void |
|
||||
on_headers_complete (ebb_request *req) |
|
||||
{ |
|
||||
HttpRequest *request = static_cast<HttpRequest*> (req->data); |
|
||||
|
|
||||
HandleScope scope; |
HandleScope scope; |
||||
|
|
||||
Handle<Object> js_request = request->CreateJSObject(); |
Local<FunctionTemplate> t = FunctionTemplate::New(HTTPClient::v8New); |
||||
|
t->InstanceTemplate()->SetInternalFieldCount(1); |
||||
// Set up an exception handler before calling the Process function
|
target->Set(String::NewSymbol("HTTPClient"), t->GetFunction()); |
||||
TryCatch try_catch; |
|
||||
|
|
||||
// Invoke the process function, giving the global object as 'this'
|
|
||||
// and one argument, the request.
|
|
||||
const int argc = 1; |
|
||||
Handle<Value> argv[argc] = { js_request }; |
|
||||
Handle<Value> r = request->connection.js_onrequest->Call(Context::GetCurrent()->Global(), argc, argv); |
|
||||
|
|
||||
if(try_catch.HasCaught()) |
|
||||
node::fatal_exception(try_catch); |
|
||||
} |
|
||||
|
|
||||
static void |
|
||||
on_request_complete (ebb_request *req) |
|
||||
{ |
|
||||
HttpRequest *request = static_cast<HttpRequest*> (req->data); |
|
||||
request->MakeBodyCallback(NULL, 0); // EOF
|
|
||||
} |
|
||||
|
|
||||
static void |
|
||||
on_body (ebb_request *req, const char *base, size_t length) |
|
||||
{ |
|
||||
HttpRequest *request = static_cast<HttpRequest*> (req->data); |
|
||||
|
|
||||
if(length) |
|
||||
request->MakeBodyCallback(base, length); |
|
||||
} |
|
||||
|
|
||||
static ebb_request * on_request |
|
||||
( void *data |
|
||||
) |
|
||||
{ |
|
||||
Connection *connection = static_cast<Connection*> (data); |
|
||||
|
|
||||
HttpRequest *request = new HttpRequest(*connection); |
|
||||
connection->AddRequest(request); |
|
||||
|
|
||||
return &request->parser_info; |
NODE_SET_METHOD(t->InstanceTemplate(), "connect", Connection::v8Connect); |
||||
|
NODE_SET_METHOD(t->InstanceTemplate(), "close", Connection::v8Close); |
||||
|
NODE_SET_METHOD(t->InstanceTemplate(), "send", Connection::v8Send); |
||||
|
NODE_SET_METHOD(t->InstanceTemplate(), "sendEOF", Connection::v8SendEOF); |
||||
} |
} |
||||
|
|
||||
static void on_read |
Handle<Value> |
||||
( oi_socket *socket |
HTTPClient::v8New (const Arguments& args) |
||||
, const void *buf |
|
||||
, size_t count |
|
||||
) |
|
||||
{ |
|
||||
Connection *connection = static_cast<Connection*> (socket->data); |
|
||||
if(count == 0) { |
|
||||
connection->Close(); |
|
||||
} else { |
|
||||
//write(1, buf, count);
|
|
||||
connection->Parse(buf, count); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
static void on_close |
|
||||
( oi_socket *socket |
|
||||
) |
|
||||
{ |
|
||||
Connection *connection = static_cast<Connection*> (socket->data); |
|
||||
delete connection; |
|
||||
} |
|
||||
|
|
||||
HttpRequest::~HttpRequest () |
|
||||
{ |
|
||||
HandleScope scope; // needed?
|
|
||||
// delete a reference c++ HttpRequest
|
|
||||
js_object->SetInternalField(0, Undefined()); |
|
||||
js_object->Delete(respond_str); |
|
||||
// dispose of Persistent handle so that
|
|
||||
// it can be GC'd normally.
|
|
||||
js_object.Dispose(); |
|
||||
} |
|
||||
|
|
||||
HttpRequest::HttpRequest (Connection &c) : connection(c) |
|
||||
{ |
|
||||
ebb_request_init(&parser_info); |
|
||||
parser_info.on_path = on_path; |
|
||||
parser_info.on_query_string = on_query_string; |
|
||||
parser_info.on_uri = on_uri; |
|
||||
parser_info.on_fragment = on_fragment; |
|
||||
parser_info.on_header_field = on_header_field; |
|
||||
parser_info.on_header_value = on_header_value; |
|
||||
parser_info.on_headers_complete = on_headers_complete; |
|
||||
parser_info.on_body = on_body; |
|
||||
parser_info.on_complete = on_request_complete; |
|
||||
parser_info.data = this; |
|
||||
|
|
||||
done = false; |
|
||||
} |
|
||||
|
|
||||
void |
|
||||
HttpRequest::MakeBodyCallback (const char *base, size_t length) |
|
||||
{ |
|
||||
HandleScope handle_scope; |
|
||||
|
|
||||
Handle<Value> onbody_val = js_object->Get(on_body_str); |
|
||||
if (!onbody_val->IsFunction()) return; |
|
||||
Handle<Function> onbody = Handle<Function>::Cast(onbody_val); |
|
||||
|
|
||||
TryCatch try_catch; |
|
||||
const int argc = 1; |
|
||||
Handle<Value> argv[argc]; |
|
||||
|
|
||||
if(length) { |
|
||||
// TODO ByteArray?
|
|
||||
//
|
|
||||
|
|
||||
uint16_t expanded_base[length]; |
|
||||
for(int i = 0; i < length; i++) { |
|
||||
expanded_base[i] = base[i]; |
|
||||
} |
|
||||
|
|
||||
Handle<String> chunk = String::New(expanded_base, length); |
|
||||
argv[0] = chunk; |
|
||||
} else { |
|
||||
argv[0] = Null(); |
|
||||
} |
|
||||
|
|
||||
Handle<Value> result = onbody->Call(js_object, argc, argv); |
|
||||
|
|
||||
if(try_catch.HasCaught()) |
|
||||
node::fatal_exception(try_catch); |
|
||||
} |
|
||||
|
|
||||
Local<Object> |
|
||||
HttpRequest::CreateJSObject () |
|
||||
{ |
|
||||
HandleScope scope; |
|
||||
|
|
||||
if (request_template.IsEmpty()) { |
|
||||
Handle<ObjectTemplate> raw_template = ObjectTemplate::New(); |
|
||||
raw_template->SetInternalFieldCount(1); |
|
||||
raw_template->Set(respond_str, FunctionTemplate::New(RespondCallback)); |
|
||||
|
|
||||
request_template = Persistent<ObjectTemplate>::New(raw_template); |
|
||||
} |
|
||||
|
|
||||
// Create an empty http request wrapper.
|
|
||||
Handle<Object> result = request_template->NewInstance(); |
|
||||
|
|
||||
// Wrap the raw C++ pointer in an External so it can be referenced
|
|
||||
// from within JavaScript.
|
|
||||
Handle<External> request_ptr = External::New(this); |
|
||||
|
|
||||
// Store the request pointer in the JavaScript wrapper.
|
|
||||
result->SetInternalField(0, request_ptr); |
|
||||
|
|
||||
result->Set ( path_str |
|
||||
, String::New(path.c_str(), path.length()) |
|
||||
); |
|
||||
|
|
||||
result->Set ( uri_str |
|
||||
, String::New(uri.c_str(), uri.length()) |
|
||||
); |
|
||||
|
|
||||
result->Set ( query_string_str |
|
||||
, String::New(query_string.c_str(), query_string.length()) |
|
||||
); |
|
||||
|
|
||||
result->Set ( fragment_str |
|
||||
, String::New(fragment.c_str(), fragment.length()) |
|
||||
); |
|
||||
|
|
||||
result->Set ( method_str |
|
||||
, GetMethodString(parser_info.method) |
|
||||
); |
|
||||
|
|
||||
char version[10]; |
|
||||
snprintf ( version |
|
||||
, 10 // big enough? :)
|
|
||||
, "%d.%d" |
|
||||
, parser_info.version_major |
|
||||
, parser_info.version_minor |
|
||||
); |
|
||||
result->Set ( http_version_str |
|
||||
, String::New(version) |
|
||||
); |
|
||||
|
|
||||
|
|
||||
Handle<Object> headers = Object::New(); |
|
||||
list<string>::iterator field_iterator = header_fields.begin(); |
|
||||
list<string>::iterator value_iterator = header_values.begin(); |
|
||||
while( value_iterator != header_values.end() ) { |
|
||||
string &f = *field_iterator; |
|
||||
string &v = *value_iterator; |
|
||||
|
|
||||
headers->Set( String::NewSymbol(f.c_str(), f.length()) |
|
||||
, String::New(v.c_str(), v.length() ) |
|
||||
); |
|
||||
|
|
||||
field_iterator++; |
|
||||
value_iterator++; |
|
||||
} |
|
||||
result->Set(headers_str, headers); |
|
||||
|
|
||||
js_object = Persistent<Object>::New(result); |
|
||||
// XXX does the request's js_object need a MakeWeak callback?
|
|
||||
// i dont think so because at some point the connection closes
|
|
||||
// and we're going to delete the request.
|
|
||||
|
|
||||
return scope.Close(result); |
|
||||
} |
|
||||
|
|
||||
|
|
||||
static oi_socket* |
|
||||
on_connection (oi_server *_server, struct sockaddr *addr, socklen_t len) |
|
||||
{ |
{ |
||||
HandleScope scope; |
HandleScope scope; |
||||
|
|
||||
HttpServer *server = static_cast<HttpServer*> (_server->data); |
if (args[0]->IsFunction() == false) |
||||
|
return ThrowException(String::New("Must pass a class as the first argument.")); |
||||
Handle<Value> callback_v = server->Callback(); |
|
||||
|
|
||||
if(callback_v == Undefined()) |
|
||||
return NULL; |
|
||||
|
|
||||
Connection *connection = new Connection(); |
|
||||
|
|
||||
Handle<Function> f = Handle<Function>::Cast(callback_v); |
|
||||
connection->js_onrequest = Persistent<Function>::New(f); |
|
||||
|
|
||||
return &connection->socket; |
|
||||
} |
|
||||
|
|
||||
Connection::Connection () |
|
||||
{ |
|
||||
oi_socket_init (&socket, 30.0); // TODO make timeout adjustable
|
|
||||
socket.on_read = on_read; |
|
||||
socket.on_error = NULL; |
|
||||
socket.on_close = on_close; |
|
||||
socket.on_timeout = NULL; |
|
||||
socket.on_drain = NULL; |
|
||||
socket.data = this; |
|
||||
|
|
||||
ebb_request_parser_init (&parser); |
|
||||
parser.new_request = on_request; |
|
||||
parser.data = this; |
|
||||
} |
|
||||
|
|
||||
Connection::~Connection () |
|
||||
{ |
|
||||
list<HttpRequest*>::iterator it = requests.begin(); |
|
||||
|
|
||||
// delete all the requests
|
|
||||
|
|
||||
for(it = requests.begin(); it != requests.end(); it++) |
|
||||
delete *it; |
|
||||
|
|
||||
for(it = finished_requests.begin(); it != finished_requests.end(); it++) |
|
||||
delete *it; |
|
||||
} |
|
||||
|
|
||||
void |
|
||||
Connection::Parse(const void *buf, size_t count) |
|
||||
{ |
|
||||
// FIXME change ebb_request_parser to have void* arg
|
|
||||
ebb_request_parser_execute ( &parser |
|
||||
, static_cast<const char*> (buf) |
|
||||
, count |
|
||||
); |
|
||||
|
|
||||
if(ebb_request_parser_has_error(&parser)) { |
|
||||
fprintf(stderr, "parse error closing connection\n"); |
|
||||
oi_socket_close(&socket); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void |
Handle<Function> protocol = Handle<Function>::Cast(args[0]); |
||||
Connection::AddRequest(HttpRequest *request) |
|
||||
{ |
|
||||
requests.push_back(request); |
|
||||
} |
|
||||
|
|
||||
void |
int argc = args.Length(); |
||||
Connection::Write ( ) |
Handle<Value> argv[argc]; |
||||
{ |
|
||||
if(requests.size() == 0) |
|
||||
return; |
|
||||
|
|
||||
HttpRequest *request = requests.front(); |
|
||||
|
|
||||
while(request->output.size() > 0) { |
argv[0] = args.This(); |
||||
oi_buf *buf = request->output.front(); |
for (int i = 1; i < args.Length(); i++) { |
||||
oi_socket_write(&socket, buf); |
argv[i] = args[i]; |
||||
request->output.pop_front(); |
|
||||
} |
} |
||||
|
|
||||
if(request->done) { |
Local<Object> protocol_instance = protocol->NewInstance(argc, argv); |
||||
if(!ebb_request_should_keep_alive(&request->parser_info)) { |
|
||||
socket.on_drain = oi_socket_close; |
|
||||
} |
|
||||
|
|
||||
requests.pop_front(); |
|
||||
finished_requests.push_back(request); |
|
||||
|
|
||||
Write(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
void |
new HTTPClient(args.This(), protocol_instance); |
||||
Connection::Close ( ) |
|
||||
{ |
|
||||
oi_socket_close(&socket); |
|
||||
} |
|
||||
|
|
||||
static void |
|
||||
server_destroy (Persistent<Value> _, void *data) |
|
||||
{ |
|
||||
HttpServer *server = static_cast<HttpServer *> (data); |
|
||||
delete server; |
|
||||
} |
|
||||
|
|
||||
HttpServer::HttpServer (Handle<Object> _js_server) |
|
||||
{ |
|
||||
oi_server_init(&server, 1024); |
|
||||
server.on_connection = on_connection; |
|
||||
server.data = this; |
|
||||
HandleScope scope; |
|
||||
js_server = Persistent<Object>::New (_js_server); |
|
||||
// are we ever going to need this external?
|
|
||||
js_server->SetInternalField (0, External::New(this)); |
|
||||
js_server.MakeWeak (this, server_destroy); |
|
||||
} |
|
||||
|
|
||||
HttpServer::~HttpServer () |
|
||||
{ |
|
||||
Stop(); |
|
||||
js_server.Dispose(); |
|
||||
js_server.Clear(); // necessary?
|
|
||||
} |
|
||||
|
|
||||
int |
return args.This(); |
||||
HttpServer::Start(struct addrinfo *servinfo) |
|
||||
{ |
|
||||
int r = oi_server_listen(&server, servinfo); |
|
||||
if(r == 0) |
|
||||
oi_server_attach(EV_DEFAULT_UC_ &server); |
|
||||
return r; |
|
||||
} |
} |
||||
|
|
||||
void |
void |
||||
HttpServer::Stop() |
HTTPClient::OnReceive (const void *buf, size_t len) |
||||
{ |
{ |
||||
oi_server_close (&server); |
printf("http client got data!\n"); |
||||
oi_server_detach (&server); |
|
||||
} |
} |
||||
|
|
||||
/* This constructor takes 2 arguments: host, port. */ |
HTTPClient::HTTPClient (Handle<Object> handle, Handle<Object> protocol) |
||||
static Handle<Value> |
: Connection(handle, protocol) |
||||
newHTTPHttpServer (const Arguments& args) |
|
||||
{ |
{ |
||||
if (args.Length() < 3) |
|
||||
return Undefined(); |
|
||||
|
|
||||
HandleScope scope; |
|
||||
|
|
||||
char *host = NULL; |
|
||||
String::AsciiValue host_v(args[0]->ToString()); |
|
||||
if(args[0]->IsString()) { |
|
||||
host = *host_v; |
|
||||
} |
|
||||
String::AsciiValue port(args[1]->ToString()); |
|
||||
|
|
||||
Handle<Function> onrequest = Handle<Function>::Cast(args[2]); |
|
||||
args.This()->Set(on_request_str, onrequest); |
|
||||
|
|
||||
// get addrinfo for localhost, PORT
|
|
||||
struct addrinfo *servinfo; |
|
||||
struct addrinfo hints; |
|
||||
memset(&hints, 0, sizeof hints); |
|
||||
hints.ai_family = AF_UNSPEC; |
|
||||
hints.ai_socktype = SOCK_STREAM; |
|
||||
hints.ai_flags = AI_PASSIVE; |
|
||||
// FIXME BLOCKING
|
|
||||
int r = getaddrinfo(host, *port, &hints, &servinfo); |
|
||||
if (r != 0) |
|
||||
return Undefined(); // XXX raise error?
|
|
||||
|
|
||||
//
|
|
||||
//
|
|
||||
//
|
|
||||
// TODO host is ignored for now assumed localhost
|
|
||||
//
|
|
||||
//
|
|
||||
//
|
|
||||
//
|
|
||||
|
|
||||
HttpServer *server = new HttpServer(args.This()); |
|
||||
if(server == NULL) |
|
||||
return Undefined(); // XXX raise error?
|
|
||||
|
|
||||
r = server->Start(servinfo); |
|
||||
if (r != 0) |
|
||||
return Undefined(); // XXX raise error?
|
|
||||
|
|
||||
return args.This(); |
|
||||
} |
|
||||
|
|
||||
void |
|
||||
node::Init_http (Handle<Object> target) |
|
||||
{ |
|
||||
HandleScope scope; |
|
||||
|
|
||||
Local<FunctionTemplate> server_t = FunctionTemplate::New(newHTTPHttpServer); |
|
||||
server_t->InstanceTemplate()->SetInternalFieldCount(1); |
|
||||
|
|
||||
server_t->Set("INVALID_STATE_ERR", Integer::New(INVALID_STATE_ERR)); |
|
||||
|
|
||||
target->Set(String::New("HTTPServer"), server_t->GetFunction()); |
|
||||
|
|
||||
path_str = Persistent<String>::New( String::NewSymbol("path") ); |
|
||||
uri_str = Persistent<String>::New( String::NewSymbol("uri") ); |
|
||||
query_string_str = Persistent<String>::New( String::NewSymbol("query_string") ); |
|
||||
fragment_str = Persistent<String>::New( String::NewSymbol("fragment") ); |
|
||||
method_str = Persistent<String>::New( String::NewSymbol("method") ); |
|
||||
http_version_str = Persistent<String>::New( String::NewSymbol("http_version") ); |
|
||||
headers_str = Persistent<String>::New( String::NewSymbol("headers") ); |
|
||||
|
|
||||
on_request_str = Persistent<String>::New( String::NewSymbol("onrequest") ); |
|
||||
on_body_str = Persistent<String>::New( String::NewSymbol("onbody") ); |
|
||||
respond_str = Persistent<String>::New( String::NewSymbol("respond") ); |
|
||||
|
|
||||
copy_str = Persistent<String>::New( String::New("COPY") ); |
|
||||
delete_str = Persistent<String>::New( String::New("DELETE") ); |
|
||||
get_str = Persistent<String>::New( String::New("GET") ); |
|
||||
head_str = Persistent<String>::New( String::New("HEAD") ); |
|
||||
lock_str = Persistent<String>::New( String::New("LOCK") ); |
|
||||
mkcol_str = Persistent<String>::New( String::New("MKCOL") ); |
|
||||
move_str = Persistent<String>::New( String::New("MOVE") ); |
|
||||
options_str = Persistent<String>::New( String::New("OPTIONS") ); |
|
||||
post_str = Persistent<String>::New( String::New("POST") ); |
|
||||
propfind_str = Persistent<String>::New( String::New("PROPFIND") ); |
|
||||
proppatch_str = Persistent<String>::New( String::New("PROPPATCH") ); |
|
||||
put_str = Persistent<String>::New( String::New("PUT") ); |
|
||||
trace_str = Persistent<String>::New( String::New("TRACE") ); |
|
||||
unlock_str = Persistent<String>::New( String::New("UNLOCK") ); |
|
||||
} |
} |
||||
|
Loading…
Reference in new issue