diff --git a/configure b/configure
index db2d3c52b8..fbe2e5ad0d 100755
--- a/configure
+++ b/configure
@@ -4,19 +4,11 @@
# Fancy colors used to beautify the output a bit.
#
-if [ "$NOCOLOR" ] ; then
- NORMAL=""
- BOLD=""
- RED=""
- YELLOW=""
- GREEN=""
-else
- NORMAL='\\033[0m'
- BOLD='\\033[01;1m'
- RED='\\033[01;91m'
- YELLOW='\\033[00;33m'
- GREEN='\\033[01;92m'
-fi
+NORMAL=""
+BOLD=""
+RED=""
+YELLOW=""
+GREEN=""
EXIT_SUCCESS=0
EXIT_FAILURE=1
diff --git a/deps/libebb/.gitignore b/deps/libebb/.gitignore
new file mode 100644
index 0000000000..f65038b4e3
--- /dev/null
+++ b/deps/libebb/.gitignore
@@ -0,0 +1,5 @@
+*.o
+examples/hello_world
+test_request_parser
+ebb_request_parser.c
+tags
diff --git a/deps/libebb/LICENSE b/deps/libebb/LICENSE
new file mode 100644
index 0000000000..91b52e6a4c
--- /dev/null
+++ b/deps/libebb/LICENSE
@@ -0,0 +1,21 @@
+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.
+
diff --git a/deps/libebb/README b/deps/libebb/README
new file mode 100644
index 0000000000..d5b7ce3a4b
--- /dev/null
+++ b/deps/libebb/README
@@ -0,0 +1,7 @@
+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.
+
diff --git a/deps/libebb/config.mk b/deps/libebb/config.mk
new file mode 100644
index 0000000000..5564bddebf
--- /dev/null
+++ b/deps/libebb/config.mk
@@ -0,0 +1,38 @@
+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
diff --git a/deps/libebb/doc/icon.png b/deps/libebb/doc/icon.png
new file mode 100644
index 0000000000..702f4b5bba
Binary files /dev/null and b/deps/libebb/doc/icon.png differ
diff --git a/deps/libebb/doc/index.html b/deps/libebb/doc/index.html
new file mode 100644
index 0000000000..612c9e9fbf
--- /dev/null
+++ b/deps/libebb/doc/index.html
@@ -0,0 +1,240 @@
+
+
+
+
+
+
libebb
+
+
+ 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.
+
+
+
+ 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 high-performance
+ 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.
+
+
+
+ libebb depends on POSIX sockets, libev, and optionally GnuTLS.
+
+
+
+ libebb is in the early stages of development and probably contains
+ many bugs. The API is subject to radical changes. If you're
+ interested checkout
+ the source code and join the mailing
+ list. A release will be made when it proves stable.
+
+ libebb is a simple API, mostly it is providing callbacks. There are
+ two types of callbacks that one will work with:
+
+
+
+
callbacks to allocate and initialize data for libebb. These are
+ named new_* like new_connection and
+ new_request
+
callbacks which happen on an event and might provide a pointer to
+ a chunk of data. These are named on_* like
+ on_body and on_close.
+
+
+
+ In libebb there are three important classes: ebb_server,
+ ebb_connection, and ebb_request.
+ Each server has many peer connections. Each peer connection may have many
+ requests.
+ There are two additional classes ebb_buf and ebb_request_parser
+ which may or may not be useful.
+
+
+
ebb_server
+
+ ebb_server represents a single web server listening on a
+ single port. The user must allocate the structure themselves, then
+ call ebb_server_init() and provide a libev event loop.
+ ebb_server_set_secure() will make the server understand
+ HTTPS connections.
+
+
+
+ After initialized the ebb_server_listen_on_port() 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 ev_loop()) after
+ ebb_server_listen_on_port() is called.
+
+
+
+ To accept connections you must provide the new server with a callback
+ called new_connection. This callback must return an allocated
+ and initialized ebb_connection structure.
+ To set this callback do
+
+ Additional documentation can be found in ebb.h
+
+
+
ebb_connection
+
+
+ This structure contains information and callbacks for a single client
+ connection. It is allocated and initialized through the
+ new_connection callback in ebb_server.
+ To initialize a newly allocated ebb_connection use
+ ebb_connection_init().
+
+
+
+ After ebb_connection_init() is called a number of
+ callbacks can be set: new_request, new_buf,
+ on_timeout, and on_close.
+
+
+
+ When an ebb_connection is returned to an
+ ebb_server, 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: connection->new_buf. This
+ callback returns a newly allocated and initialized
+ ebb_buf structure. How much libebb attempts to read from
+ the socket is determined by how large the returned
+ ebb_buf structure is. Using new_buf 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
+ new_buf callback is necessary only if you want to save
+ the raw data coming from the socket.
+
+
+
+ The new_request callback is called at the beginning of a
+ request. It must return a newly allocated and initialized
+ ebb_request structure. Because HTTP/1.1 supports peristant
+ connections, there may be many requests per connection.
+
+
+
+ You may access the file descriptor for the client socket inside the
+ ebb_connection 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.
+
+
+
+ A convience function, ebb_connection_write, 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.
+
+
+
+ To close a peer connection use
+ ebb_connnection_schedule_close(). Because SSL may require
+ some additional communication to close the connection properly, the
+ file descriptor cannot be closed immediately. The
+ on_close callback will be made when the peer socket is
+ finally closed.
+ Only once on_close is called may the
+ user free the ebb_connection structure.
+
+
+
+
ebb_request
+
+
+ This structure provides information about a request. For example,
+ request->method == EBB_POST would mean the method of
+ the request is POST. There are also many callbacks
+ which can be set to handle data from a request as it is parsed.
+
+
+
+ The on_uri callback and all other
+ ebb_element_cb 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
+ on_uri callback 10 times, each providing just a single
+ character of the request's URI. See ebb_request_parser.h for
+ a full list of callbacks that you may provide. (If you don't set them,
+ they are ignored.)
+
+
+
+ The on_complete callback is called at the end of
+ each request.
+ Only once on_complete is called may the
+ user free the ebb_request structure.
+
+
+
Example
+
+
+ A simple example is provided in examples/hello_world.c.
+
+
+
+
diff --git a/deps/libebb/ebb.c b/deps/libebb/ebb.c
new file mode 100644
index 0000000000..a4c5e78ac6
--- /dev/null
+++ b/deps/libebb/ebb.c
@@ -0,0 +1,798 @@
+/* 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
+#include
+#include
+#include
+#include
+#include /* TCP_NODELAY */
+#include /* inet_ntoa */
+#include /* inet_ntoa */
+#include
+#include /* perror */
+#include /* perror */
+#include /* for the default methods */
+#include
+
+#include "ebb.h"
+#include "ebb_request_parser.h"
+#ifdef HAVE_GNUTLS
+# include
+# 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;
+}
+
diff --git a/deps/libebb/ebb.h b/deps/libebb/ebb.h
new file mode 100644
index 0000000000..e5e90b495e
--- /dev/null
+++ b/deps/libebb/ebb.h
@@ -0,0 +1,120 @@
+/* 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
+#include
+#include
+#ifdef HAVE_GNUTLS
+# include
+# 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
diff --git a/deps/libebb/ebb_request_parser.h b/deps/libebb/ebb_request_parser.h
new file mode 100644
index 0000000000..93d502f4b5
--- /dev/null
+++ b/deps/libebb/ebb_request_parser.h
@@ -0,0 +1,117 @@
+/* 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
+
+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
diff --git a/deps/libebb/ebb_request_parser.rl b/deps/libebb/ebb_request_parser.rl
new file mode 100644
index 0000000000..63e36a8a49
--- /dev/null
+++ b/deps/libebb/ebb_request_parser.rl
@@ -0,0 +1,413 @@
+/* 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
+#include
+
+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;
+}
diff --git a/deps/libebb/examples/ca-cert.pem b/deps/libebb/examples/ca-cert.pem
new file mode 100644
index 0000000000..606283b1ec
--- /dev/null
+++ b/deps/libebb/examples/ca-cert.pem
@@ -0,0 +1,12 @@
+-----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-----
diff --git a/deps/libebb/examples/ca-key.pem b/deps/libebb/examples/ca-key.pem
new file mode 100644
index 0000000000..afb953d69b
--- /dev/null
+++ b/deps/libebb/examples/ca-key.pem
@@ -0,0 +1,15 @@
+-----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-----
diff --git a/deps/libebb/examples/hello_world.c b/deps/libebb/examples/hello_world.c
new file mode 100644
index 0000000000..bec0070937
--- /dev/null
+++ b/deps/libebb/examples/hello_world.c
@@ -0,0 +1,101 @@
+#include
+#include
+#include
+#include
+#include
+
+#include
+#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;
+}
diff --git a/deps/libebb/rbtree.c b/deps/libebb/rbtree.c
new file mode 100644
index 0000000000..4a66972a26
--- /dev/null
+++ b/deps/libebb/rbtree.c
@@ -0,0 +1,412 @@
+/* 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
+
+#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);
+ }
+}
+
+
diff --git a/deps/libebb/rbtree.h b/deps/libebb/rbtree.h
new file mode 100644
index 0000000000..5962a3c118
--- /dev/null
+++ b/deps/libebb/rbtree.h
@@ -0,0 +1,54 @@
+/* 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
+
diff --git a/deps/libebb/test_examples.rb b/deps/libebb/test_examples.rb
new file mode 100644
index 0000000000..2af658cab8
--- /dev/null
+++ b/deps/libebb/test_examples.rb
@@ -0,0 +1,60 @@
+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
diff --git a/deps/libebb/test_rbtree.c b/deps/libebb/test_rbtree.c
new file mode 100644
index 0000000000..f5ee1d6cd1
--- /dev/null
+++ b/deps/libebb/test_rbtree.c
@@ -0,0 +1,108 @@
+/* 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
+#include
+#include /* 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("", stdout);
+ return;
+ }
+ if (n->right != NULL) {
+ print_tree_helper(n->right, indent + INDENT_STEP);
+ }
+ for(i=0; icolor == 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;
+}
+
diff --git a/deps/libebb/test_request_parser.c b/deps/libebb/test_request_parser.c
new file mode 100644
index 0000000000..5a5dca38f1
--- /dev/null
+++ b/deps/libebb/test_request_parser.c
@@ -0,0 +1,746 @@
+/* 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
+#include
+#include
+#include
+
+#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;
+}
+
diff --git a/deps/liboi/LICENSE b/deps/liboi/LICENSE
new file mode 100644
index 0000000000..a5a6e0191f
--- /dev/null
+++ b/deps/liboi/LICENSE
@@ -0,0 +1,23 @@
+liboi is Copyright (C) 2009 Ryan Dahl.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
diff --git a/deps/liboi/README b/deps/liboi/README
new file mode 100644
index 0000000000..0609ab4961
--- /dev/null
+++ b/deps/liboi/README
@@ -0,0 +1,36 @@
+liboi is a C library for doing evented I/O. It is intended for building
+efficent internet programs.
+
+liboi is released under the X11 license.
+
+= Feature Summary
+
+ * The library has a minimalist design
+ - Does not make internal allocations
+ - Does not wrap functionality of GnuTLS or libev. The user must use those
+ libraries in conjuction with liboi.
+ * Supports both server and client sockets.
+ * Supports evented file I/O emulation through a thread pool.
+ * SSL support
+ * Sendfile (file to socket) with emulation on platforms that do not support
+ it.
+
+= Building
+
+ 1 Edit config.mk. You almost certainly will need to set the EVDIR and
+ GNUTLSDIR variables.
+ 2 Run "make"
+
+= Documentation
+
+ 1 make doc
+ 2 man ./oi.3
+
+= Website
+
+http://github.com/ry/liboi
+
+= Author
+
+Ryan Dahl (ry@tinyclouds.org)
+
diff --git a/deps/liboi/config.mk b/deps/liboi/config.mk
new file mode 100644
index 0000000000..311f8cb535
--- /dev/null
+++ b/deps/liboi/config.mk
@@ -0,0 +1,33 @@
+# Define EVDIR=/foo/bar if your libev header and library files are in
+# /foo/bar/include and /foo/bar/lib directories.
+EVDIR=$(HOME)/local/libev
+
+# Define GNUTLSDIR=/foo/bar if your gnutls header and library files are in
+# /foo/bar/include and /foo/bar/lib directories.
+#GNUTLSDIR=/usr
+#
+#
+# Define NO_PREAD if you have a problem with pread() system call (e.g.
+# cygwin.dll before v1.5.22).
+#
+#
+# Define NO_SENDFILE if you have a problem with the sendfile() system call
+#
+#
+
+uname_S := $(shell sh -c 'uname -s 2>/dev/null || echo not')
+uname_M := $(shell sh -c 'uname -m 2>/dev/null || echo not')
+uname_O := $(shell sh -c 'uname -o 2>/dev/null || echo not')
+uname_R := $(shell sh -c 'uname -r 2>/dev/null || echo not')
+uname_P := $(shell sh -c 'uname -p 2>/dev/null || echo not')
+
+# CFLAGS and LDFLAGS are for the users to override from the command line.
+CFLAGS = -g
+LDFLAGS =
+
+PREFIX = $(HOME)/local/liboi
+
+CC = gcc
+AR = ar
+RM = rm -f
+RANLIB = ranlib
diff --git a/deps/liboi/oi.h b/deps/liboi/oi.h
new file mode 100644
index 0000000000..c7145ea351
--- /dev/null
+++ b/deps/liboi/oi.h
@@ -0,0 +1,8 @@
+#ifndef oi_h
+#define oi_h
+
+#include
+#include
+#include
+
+#endif
diff --git a/deps/liboi/oi.pod b/deps/liboi/oi.pod
new file mode 100644
index 0000000000..8b07115ee6
--- /dev/null
+++ b/deps/liboi/oi.pod
@@ -0,0 +1,278 @@
+=head1 NAME
+
+liboi - a C library for doing evented I/O.
+
+=head1 SYNOPSIS
+
+ #include
+
+=head1 DESCRIPTION
+
+liboi is an object oriented library for doing evented socket and file I/O.
+The API is mostly about registering callbacks to be executed on certain
+events.
+
+Because most systems do not support asynchornous file I/O, the behavior is
+emulated with an internal thread pool. The thread pool is accessed with the
+C and C objects. Typically one will not need to use these
+directly as C wraps that functionality.
+
+=head2 CONVENTIONS
+
+liboi's goal is to be very simple layer above the POSIX API. To that end it
+avoids internal allocations as much as possible. Unless otherwise noted you
+should assume all pointers passed into liboi will remain your responsibility
+to maintain. That means you should not free the data passed into liboi
+until the object in question has completed.
+
+C and C objects must be attached to an event loop. This
+is completed with the C<*_attach> and C<*_detach> methods. When an object
+is detached, other methods can be called - just the loop will not churn out
+callbacks.
+
+Both C and C contain a number of callback pointers.
+These are to be set manually after calling their initalization functions.
+All classes include a C member which is left for you to use.
+
+=head1 ERROR HANDLING
+
+
+=head1 Sockets
+
+The C structure represents a socket.
+The callbacks inside C are
+ void (*on_connect) (oi_socket *);
+ void (*on_read) (oi_socket *, const void *buf, size_t count);
+ void (*on_drain) (oi_socket *);
+ void (*on_error) (oi_socket *, struct oi_error e);
+ void (*on_close) (oi_socket *);
+ void (*on_timeout) (oi_socket *);
+
+A the memory for a socket is released when the C callback is
+made. That is, the user may free the memory for the socket with-in the
+C callback.
+
+=over 4
+
+=item void oi_socket_init (oi_socket *, float timeout);
+
+Initialize a socket. C is the number of seconds of inactivity that
+is allowed before the socket closes. The timeout only starts once the
+socket is attached to a loop and open.
+
+A C argument of 0.0 signals that no timeout should be used.
+
+After calling this function, register your callbacks manually. Thus your
+code will probably look like this
+ oi_socket socket;
+ oi_socket_init(&socket, 60.0);
+ socket.on_connect = my_on_connect;
+ socket.on_read = my_on_read;
+ /* etc */
+
+
+=item int oi_socket_connect (oi_socket *, struct addrinfo *addrinfo);
+
+Open a client connect to the specified address. When the connection is made
+C will be called.
+
+Here is an example of filling in C for a local TCP connection on
+port 5555:
+ 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;
+ r = getaddrinfo(NULL, "5555", &hints, &servinfo);
+ assert(r == 0);
+ oi_socket_connect(socket, servinfo);
+
+=item void oi_socket_attach (oi_socket *, struct ev_loop *loop);
+
+A socket must be attached to a loop in order before any callbacks will be
+made.
+
+=item void oi_socket_detach (oi_socket *);
+
+Detaching a socket will not close the connection.
+
+=item void oi_socket_read_start (oi_socket *);
+
+This will make the socket start receiving data. When data is received the
+C callback is made. The maximum amount of data that can be
+receive at a time is controlled by C.
+
+The buffer returned by C is statically allocated exists only
+for the length of the callback. That means if you need to save any of the
+data coming down the line, you must copy it to a new buffer.
+
+Ideally you will have a parser attached to the C callback which can
+be interrupted at any time.
+
+C can be changed at any time.
+
+=item void oi_socket_read_stop (oi_socket *);
+
+Stops receiving data. You may receive spurious C attempts even
+though the socket reading is stopped - be prepared to handle them.
+
+=item void oi_socket_reset_timeout (oi_socket *);
+
+Reset the timeout to allow the socket to exist for another few seconds
+(however long you specified in the initialization function).
+
+=item void oi_socket_write (oi_socket *socket, oi_buf *buf);
+
+Write the I to the socket. Each socket has a queue of C objects
+to be written - this appends the specified buffer to the end of that queue.
+You will be notified when the queue is empty with the C
+callback. When the socket has written the buffer C will be
+called. The release callback does not imply that the buffer was successfully
+written.
+
+=item void oi_socket_write_simple (oi_socket *, const char *str, size_t len);
+
+Sometimes you are just hacking around and need to quickly write a string to
+the socket. This convenience function allocates an C object, and
+Cs the given string. The allocated buffer will be freed by liboi
+internally.
+
+Most production most applications will use their own memory pool and will
+not need this function.
+
+=item void oi_socket_write_eof (oi_socket *);
+
+This closes the write end of the socket. Further writes are not allowed
+after this.
+
+=item void oi_socket_close (oi_socket *);
+
+Attempts to close the socket.
+
+If the socket is secure, an SSL bye message will be sent.
+SSL recommends that you wait for a bye response from the peer however this
+tends to be overkill for most people. By default liboi will not wait for
+peer to send a matching bye message. If you require this then set
+C to 1.
+
+When the close is complete C is made. The C
+callback is not made until the program returns to the event loop. This is
+because C may free the socket memory and if C was
+called from C, then the socket object might unexpectedly
+be gone. To summarize: C does not call C and
+the socket memory is still accessable immediately after making calling
+C.
+
+=item void oi_socket_set_secure_session (oi_socket *, gnutls_session_t);
+
+This make a socket use SSL. You must create the GnuTLS session yourself and
+assign its credentials.
+
+=back
+
+=head1 Servers
+
+A server simply listens on an address for new connections. The connections
+come in the form of C objects. The key is to give a
+C callback which returns an initialized C.
+The callback looks like this
+
+ oi_socket* (*on_connection) (oi_server *, struct sockaddr *remote_addr, socklen_t remove_addr_len);
+
+Returning NULL from C will reject the connection.
+
+=over 4
+
+=item void oi_server_init (oi_server *, int backlog);
+
+Initializes a server object. C is the argument given
+internally to C. Set the C callback
+after calling this.
+
+=item int oi_server_listen (oi_server *, struct addrinfo *addrinfo);
+
+Listens on the specified address. The server will not accept connections
+until it is attached to a loop, however.
+
+=item void oi_server_attach (oi_server *, struct ev_loop *loop);
+
+Attaches a server to a loop.
+
+=item void oi_server_detach (oi_server *);
+
+Detaches a server to a loop. Does not close the server.
+
+=item void oi_server_close (oi_server *);
+
+Stops the server from listening.
+
+=back
+
+=head1 Files
+
+Files internally use a thread pool to operate without blocking.
+The thread pool is started once a file is attached and it continues until
+program termination.
+
+The following callbacks are used inside of the file object
+ void (*on_open) (oi_file *);
+ void (*on_read) (oi_file *, size_t count);
+ void (*on_drain) (oi_file *);
+ void (*on_error) (oi_file *, struct oi_error);
+ void (*on_close) (oi_file *);
+
+=over 4
+
+=item int oi_file_init (oi_file *);
+
+Initializes a file object.
+
+=item void oi_file_attach (oi_file *, struct ev_loop *);
+
+Attaches a file object to a loop. If the thread pool has not been started,
+then it is started at this call.
+
+=item void oi_file_detach (oi_file *);
+
+Detaches a file object from the loop.
+
+=item int oi_file_open_path (oi_file *, const char *path, int flags, mode_t mode);
+
+Opens a file specified by the path. The C and C arguments are
+the same as used by L. The C callback is triggered
+when the file is opened. Returns 0 on success. Returns -1 if the given file
+is already open.
+
+WARNING: path argument must be valid until C object is closed and
+the C callback is made. I.E., liboi does not strdup the path
+pointer.
+
+=item int oi_file_open_stdin (oi_file *);
+
+=item int oi_file_open_stdout (oi_file *);
+
+=item int oi_file_open_stderr (oi_file *);
+
+=item void oi_file_read_start (oi_file *, void *buffer, size_t bufsize);
+
+
+
+=item void oi_file_read_stop (oi_file *);
+
+=item int oi_file_write (oi_file *, oi_buf *to_write);
+
+=item int oi_file_write_simple (oi_file *, const char *, size_t);
+
+=item int oi_file_send (oi_file *source, oi_socket *destination, off_t offset, size_t length);
+
+=item void oi_file_close (oi_file *);
+
+=over 4
+
+=back
+
+=head1 AUTHOR
+
+Ryan Dahl
+
diff --git a/deps/liboi/oi_async.c b/deps/liboi/oi_async.c
new file mode 100644
index 0000000000..1d5459d625
--- /dev/null
+++ b/deps/liboi/oi_async.c
@@ -0,0 +1,486 @@
+#include /* malloc() */
+#include /* perror() */
+#include
+#include
+#include /* read(), write() */
+#include
+#include
+#include
+#include
+
+#if HAVE_SENDFILE
+# if __linux
+# include
+# elif __freebsd
+# include
+# include
+# elif __hpux
+# include
+# elif __solaris /* not yet */
+# include
+# else
+# error sendfile support requested but not available
+# endif
+#endif
+
+#include
+#include
+
+#define NWORKERS 4
+/* TODO make adjustable
+ * once it is fix sleeping_tasks
+ */
+
+static int active_watchers = 0;
+static int active_workers = 0;
+static int readiness_pipe[2] = {-1, -1};
+static oi_queue waiting_tasks;
+static pthread_mutex_t queue_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_mutex_t finished_lock = PTHREAD_MUTEX_INITIALIZER;
+
+struct worker {
+ oi_task *task;
+ pthread_t thread;
+ pthread_attr_t thread_attr;
+};
+
+/* Sendfile and pread emulation come from Marc Lehmann's libeio and are
+ * Copyright (C)2007,2008 Marc Alexander Lehmann.
+ * Many ideas of oi_async.* are taken from libeio and in fact, I plan to
+ * use libeio once it becomes usable for me. (The problem is issuing tasks
+ * from multiple threads.)
+ */
+
+#if !HAVE_PREADWRITE
+/*
+ * make our pread/pwrite emulation safe against themselves, but not against
+ * normal read/write by using a mutex. slows down execution a lot,
+ * but that's your problem, not mine.
+ */
+static pthread_mutex_t preadwritelock = PTHREAD_MUTEX_INITIALIZER;
+#endif
+
+#if !HAVE_PREADWRITE
+# undef pread
+# undef pwrite
+# define pread eio__pread
+# define pwrite eio__pwrite
+
+static ssize_t
+eio__pread (int fd, void *buf, size_t count, off_t offset)
+{
+ ssize_t res;
+ off_t ooffset;
+
+ pthread_mutex_lock(&preadwritelock);
+ ooffset = lseek (fd, 0, SEEK_CUR);
+ lseek (fd, offset, SEEK_SET);
+ res = read (fd, buf, count);
+ lseek (fd, ooffset, SEEK_SET);
+ pthread_mutex_unlock(&preadwritelock);
+
+ return res;
+}
+
+static ssize_t
+eio__pwrite (int fd, void *buf, size_t count, off_t offset)
+{
+ ssize_t res;
+ off_t ooffset;
+
+ pthread_mutex_lock(&preadwritelock);
+ ooffset = lseek (fd, 0, SEEK_CUR);
+ lseek (fd, offset, SEEK_SET);
+ res = write (fd, buf, count);
+ lseek (fd, offset, SEEK_SET);
+ pthread_mutex_unlock(&preadwritelock);
+
+ return res;
+}
+#endif
+
+
+/* sendfile always needs emulation */
+static ssize_t
+eio__sendfile (int ofd, int ifd, off_t offset, size_t count)
+{
+ ssize_t res;
+
+ if (!count)
+ return 0;
+
+#if HAVE_SENDFILE
+# if __linux
+ res = sendfile (ofd, ifd, &offset, count);
+
+# elif __freebsd
+ /*
+ * Of course, the freebsd sendfile is a dire hack with no thoughts
+ * wasted on making it similar to other I/O functions.
+ */
+ {
+ off_t sbytes;
+ res = sendfile (ifd, ofd, offset, count, 0, &sbytes, 0);
+
+ if (res < 0 && sbytes)
+ /* maybe only on EAGAIN: as usual, the manpage leaves you guessing */
+ res = sbytes;
+ }
+
+# elif __hpux
+ res = sendfile (ofd, ifd, offset, count, 0, 0);
+
+# elif __solaris
+ {
+ struct sendfilevec vec;
+ size_t sbytes;
+
+ vec.sfv_fd = ifd;
+ vec.sfv_flag = 0;
+ vec.sfv_off = offset;
+ vec.sfv_len = count;
+
+ res = sendfilev (ofd, &vec, 1, &sbytes);
+
+ if (res < 0 && sbytes)
+ res = sbytes;
+ }
+
+# endif
+#else
+ res = -1;
+ errno = ENOSYS;
+#endif
+
+ if (res < 0
+ && (errno == ENOSYS || errno == EINVAL || errno == ENOTSOCK
+#if __solaris
+ || errno == EAFNOSUPPORT || errno == EPROTOTYPE
+#endif
+ )
+ )
+ {
+ /* emulate sendfile. this is a major pain in the ass */
+/* buffer size for various temporary buffers */
+#define EIO_BUFSIZE 65536
+ char *eio_buf = malloc (EIO_BUFSIZE);
+ errno = ENOMEM;
+ if (!eio_buf)
+ return -1;
+
+ res = 0;
+
+ while (count) {
+ ssize_t cnt;
+
+ cnt = pread (ifd, eio_buf, count > EIO_BUFSIZE ? EIO_BUFSIZE : count, offset);
+
+ if (cnt <= 0) {
+ if (cnt && !res) res = -1;
+ break;
+ }
+
+ cnt = write (ofd, eio_buf, cnt);
+
+ if (cnt <= 0) {
+ if (cnt && !res) res = -1;
+ break;
+ }
+
+ offset += cnt;
+ res += cnt;
+ count -= cnt;
+ }
+
+ free(eio_buf);
+ }
+
+ return res;
+}
+
+static oi_task*
+queue_shift(pthread_mutex_t *lock, oi_queue *queue)
+{
+ oi_queue *last = NULL;
+ pthread_mutex_lock(lock);
+ if(!oi_queue_empty(queue)) {
+ last = oi_queue_last(queue);
+ oi_queue_remove(last);
+ }
+ pthread_mutex_unlock(lock);
+
+ if(last == NULL)
+ return NULL;
+
+ return oi_queue_data(last, oi_task, queue);
+}
+
+#define P1(name,a) { \
+ t->params.name.result = name( t->params.name.a ); \
+ break; \
+}
+
+#define P2(name,a,b) { \
+ t->params.name.result = name( t->params.name.a \
+ , t->params.name.b \
+ ); \
+ break; \
+}
+
+#define P3(name,a,b,c) { \
+ t->params.name.result = name( t->params.name.a \
+ , t->params.name.b \
+ , t->params.name.c \
+ ); \
+ break; \
+}
+
+#define P4(name,a,b,c,d) { \
+ t->params.name.result = name( t->params.name.a \
+ , t->params.name.b \
+ , t->params.name.c \
+ , t->params.name.d \
+ ); \
+ break; \
+}
+
+static void
+execute_task(oi_task *t)
+{
+ errno = 0;
+ switch(t->type) {
+ case OI_TASK_OPEN: P3(open, pathname, flags, mode);
+ case OI_TASK_READ: P3(read, fd, buf, count);
+ case OI_TASK_WRITE: P3(write, fd, buf, count);
+ case OI_TASK_CLOSE: P1(close, fd);
+ case OI_TASK_SLEEP: P1(sleep, seconds);
+ case OI_TASK_SENDFILE: P4(eio__sendfile, out_fd, in_fd, offset, count);
+ case OI_TASK_GETADDRINFO: P4(getaddrinfo, nodename, servname, hints, res);
+ case OI_TASK_LSTAT: P2(lstat, path, buf);
+ default:
+ assert(0 && "unknown task type");
+ break;
+ }
+ t->errorno = errno;
+}
+
+static void
+attempt_to_get_a_task(struct worker *worker)
+{
+ char dummy;
+ assert(readiness_pipe[0] > 0);
+ int r = read(readiness_pipe[0], &dummy, 1);
+ if(r == -1 && (errno != EAGAIN || errno != EINTR)) {
+ perror("read(readiness_pipe[0])");
+ return;
+ }
+
+ // 1 pop task from queue
+ assert(worker->task == NULL);
+ oi_task *task = queue_shift(&queue_lock, &waiting_tasks);
+ if(task == NULL) return;
+ worker->task = task;
+
+ // 2 run task
+ execute_task(task);
+
+ // 3 notify complition
+ oi_async *async = task->async;
+ assert(async != NULL);
+ pthread_mutex_lock(&finished_lock);
+ oi_queue_insert_head(&async->finished_tasks, &task->queue);
+ pthread_mutex_unlock(&finished_lock);
+ ev_async_send(async->loop, &async->watcher);
+ worker->task = NULL;
+
+ /* attempt to pull another task */
+ return attempt_to_get_a_task(worker);
+}
+
+void *
+worker_loop(void *data)
+{
+ int r;
+ struct worker *worker = data;
+ fd_set readfds;
+ FD_ZERO(&readfds);
+ FD_SET(readiness_pipe[0], &readfds);
+
+ active_workers++;
+ assert(active_workers <= NWORKERS);
+
+ while(1) {
+ r = select(1+readiness_pipe[0], &readfds, 0, 0, 0);
+ if(r == -1) break;
+ attempt_to_get_a_task(worker);
+ }
+ active_workers--;
+
+ return NULL;
+}
+
+static struct worker*
+worker_new()
+{
+ int r;
+ struct worker *worker = calloc(sizeof(struct worker), 1);
+ if(worker == NULL ) { return NULL; }
+
+ worker->task = NULL;
+ pthread_attr_setdetachstate(&worker->thread_attr, PTHREAD_CREATE_DETACHED);
+
+ r = pthread_create( &worker->thread
+ , NULL // &worker->thread_attr
+ , worker_loop
+ , worker
+ );
+ if(r != 0) {
+ /* TODO: error checking */
+ perror("pthread_create");
+ goto error;
+ }
+
+ return worker;
+error:
+ free(worker);
+ return NULL;
+}
+
+static void
+start_workers()
+{
+ assert(readiness_pipe[0] == -1);
+ assert(readiness_pipe[1] == -1);
+ assert(active_workers == 0);
+
+ int r = pipe(readiness_pipe);
+ if(r < 0) {
+ perror("pipe()");
+ assert(0 && "TODO HANDLE ME");
+ }
+
+ /* set the write end non-blocking */
+ int flags = fcntl(readiness_pipe[1], F_GETFL, 0);
+ r = fcntl(readiness_pipe[1], F_SETFL, flags | O_NONBLOCK);
+ if(r < 0) {
+ assert(0 && "error setting pipe to non-blocking?");
+ /* TODO error report */
+ }
+
+ oi_queue_init(&waiting_tasks);
+
+ int i;
+ for(i = 0; i < NWORKERS; i++)
+ worker_new();
+}
+
+/*
+static void
+stop_workers()
+{
+ assert(0 && "TODO implement me");
+}
+*/
+
+static void
+on_completion(struct ev_loop *loop, ev_async *watcher, int revents)
+{
+ oi_async *async = watcher->data;
+ oi_task *task;
+
+ while((task = queue_shift(&finished_lock, &async->finished_tasks))) {
+ assert(task->active);
+ task->active = 0;
+ errno = task->errorno;
+# define done_cb(kind) { \
+ assert(task->params.kind.cb); \
+ task->params.kind.cb(task, task->params.kind.result); \
+ break; \
+ }
+ switch(task->type) {
+ case OI_TASK_OPEN: done_cb(open);
+ case OI_TASK_READ: done_cb(read);
+ case OI_TASK_WRITE: done_cb(write);
+ case OI_TASK_CLOSE: done_cb(close);
+ case OI_TASK_SLEEP: done_cb(sleep);
+ case OI_TASK_SENDFILE: done_cb(eio__sendfile);
+ case OI_TASK_GETADDRINFO: done_cb(getaddrinfo);
+ case OI_TASK_LSTAT: done_cb(lstat);
+ }
+ /* the task is possibly freed by callback. do not access it again. */
+ }
+}
+
+void
+oi_async_init (oi_async *async)
+{
+ ev_async_init(&async->watcher, on_completion);
+
+ oi_queue_init(&async->finished_tasks);
+ oi_queue_init(&async->new_tasks);
+
+ async->watcher.data = async;
+}
+
+static void
+dispatch_tasks(oi_async *async)
+{
+ while(!oi_queue_empty(&async->new_tasks)) {
+ oi_queue *last = oi_queue_last(&async->new_tasks);
+ oi_queue_remove(last);
+ oi_task *task = oi_queue_data(last, oi_task, queue);
+
+ // 1. add task to task queue.
+ pthread_mutex_lock(&queue_lock);
+ oi_queue_insert_head(&waiting_tasks, &task->queue);
+ pthread_mutex_unlock(&queue_lock);
+
+ // 2. write byte to pipe
+ char dummy;
+ int written = write(readiness_pipe[1], &dummy, 1);
+
+ // 3. TODO make sure byte is written
+ assert(written == 1);
+ }
+}
+
+void
+oi_async_attach (struct ev_loop *loop, oi_async *async)
+{
+ if(active_watchers == 0 && active_workers == 0)
+ start_workers();
+ active_watchers++;
+
+ ev_async_start(loop, &async->watcher);
+ async->loop = loop;
+
+ dispatch_tasks(async);
+}
+
+void
+oi_async_detach (oi_async *async)
+{
+ if(async->loop == NULL)
+ return;
+ ev_async_stop(async->loop, &async->watcher);
+ async->loop = NULL;
+ active_watchers--;
+ if(active_watchers == 0) {
+ //stop_workers();
+ }
+}
+
+void
+oi_async_submit (oi_async *async, oi_task *task)
+{
+ assert(!task->active);
+ assert(task->async == NULL);
+ task->async = async;
+ task->active = 1;
+
+ oi_queue_insert_head(&async->new_tasks, &task->queue);
+ if(ev_is_active(&async->watcher)) {
+ dispatch_tasks(async);
+ }
+}
+
diff --git a/deps/liboi/oi_async.h b/deps/liboi/oi_async.h
new file mode 100644
index 0000000000..f3ba937caf
--- /dev/null
+++ b/deps/liboi/oi_async.h
@@ -0,0 +1,218 @@
+#include
+#include
+#include
+#include
+
+#ifndef oi_async_h
+#define oi_async_h
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct oi_async oi_async;
+typedef struct oi_task oi_task;
+
+struct oi_async {
+ /* private */
+ ev_async watcher;
+ struct ev_loop *loop;
+
+ oi_queue finished_tasks;
+ oi_queue new_tasks;
+
+ /* public */
+ void *data;
+};
+
+typedef void (*oi_task_int_cb)(oi_task *, int result);
+typedef void (*oi_task_uint_cb)(oi_task *, unsigned int result);
+typedef void (*oi_task_ssize_cb)(oi_task *, ssize_t result);
+
+struct oi_task {
+ /* private */
+ oi_async *async;
+ oi_queue queue;
+ int type;
+ union {
+
+ struct {
+ const char *pathname;
+ int flags;
+ mode_t mode;
+ oi_task_int_cb cb;
+ int result;
+ } open;
+
+ struct {
+ int fd;
+ void *buf;
+ size_t count;
+ oi_task_ssize_cb cb;
+ ssize_t result;
+ } read;
+
+ struct {
+ int fd;
+ const void *buf;
+ size_t count;
+ oi_task_ssize_cb cb;
+ ssize_t result;
+ } write;
+
+ struct {
+ int fd;
+ oi_task_int_cb cb;
+ int result;
+ } close;
+
+ struct {
+ unsigned int seconds;
+ oi_task_uint_cb cb;
+ unsigned int result;
+ } sleep;
+
+ struct {
+ int out_fd;
+ int in_fd;
+ off_t offset;
+ size_t count;
+ oi_task_ssize_cb cb;
+ ssize_t result;
+ } eio__sendfile;
+
+ struct {
+ const char *nodename; /* restrict ? */
+ const char *servname; /* restrict ? */
+ struct addrinfo *hints;
+ struct addrinfo **res; /* restrict ? */
+ oi_task_int_cb cb;
+ int result;
+ } getaddrinfo;
+
+ struct {
+ const char *path;
+ struct stat *buf;
+ oi_task_int_cb cb;
+ int result;
+ } lstat;
+
+ } params;
+
+ /* read-only */
+ volatile unsigned active:1;
+ int errorno;
+
+ /* public */
+ void *data;
+};
+
+void oi_async_init (oi_async *);
+void oi_async_attach (struct ev_loop *loop, oi_async *);
+void oi_async_detach (oi_async *);
+void oi_async_submit (oi_async *, oi_task *);
+
+/* To submit a task for async processing
+ * (0) allocate memory for your task
+ * (1) initialize the task with one of the functions below
+ * (2) optionally set the task->data pointer
+ * (3) oi_async_submit() the task
+ */
+
+enum { OI_TASK_OPEN
+ , OI_TASK_READ
+ , OI_TASK_WRITE
+ , OI_TASK_CLOSE
+ , OI_TASK_SLEEP
+ , OI_TASK_SENDFILE
+ , OI_TASK_GETADDRINFO
+ , OI_TASK_LSTAT
+ };
+
+#define oi_task_init_common(task, _type) do {\
+ (task)->active = 0;\
+ (task)->async = NULL;\
+ (task)->type = _type;\
+} while(0)
+
+static inline void
+oi_task_init_open(oi_task *t, oi_task_int_cb cb, const char *pathname, int flags, mode_t mode)
+{
+ oi_task_init_common(t, OI_TASK_OPEN);
+ t->params.open.cb = cb;
+ t->params.open.pathname = pathname;
+ t->params.open.flags = flags;
+ t->params.open.mode = mode;
+}
+
+static inline void
+oi_task_init_read(oi_task *t, oi_task_ssize_cb cb, int fd, void *buf, size_t count)
+{
+ oi_task_init_common(t, OI_TASK_READ);
+ t->params.read.cb = cb;
+ t->params.read.fd = fd;
+ t->params.read.buf = buf;
+ t->params.read.count = count;
+}
+
+static inline void
+oi_task_init_write(oi_task *t, oi_task_ssize_cb cb, int fd, const void *buf, size_t count)
+{
+ oi_task_init_common(t, OI_TASK_WRITE);
+ t->params.write.cb = cb;
+ t->params.write.fd = fd;
+ t->params.write.buf = buf;
+ t->params.write.count = count;
+}
+
+static inline void
+oi_task_init_close(oi_task *t, oi_task_int_cb cb, int fd)
+{
+ oi_task_init_common(t, OI_TASK_CLOSE);
+ t->params.close.cb = cb;
+ t->params.close.fd = fd;
+}
+
+static inline void
+oi_task_init_sleep(oi_task *t, oi_task_uint_cb cb, unsigned int seconds)
+{
+ oi_task_init_common(t, OI_TASK_SLEEP);
+ t->params.sleep.cb = cb;
+ t->params.sleep.seconds = seconds;
+}
+
+static inline void
+oi_task_init_sendfile(oi_task *t, oi_task_ssize_cb cb, int out_fd, int in_fd, off_t offset, size_t count)
+{
+ oi_task_init_common(t, OI_TASK_SENDFILE);
+ t->params.eio__sendfile.cb = cb;
+ t->params.eio__sendfile.out_fd = out_fd;
+ t->params.eio__sendfile.in_fd = in_fd;
+ t->params.eio__sendfile.offset = offset;
+ t->params.eio__sendfile.count = count;
+}
+
+static inline void
+oi_task_init_getaddrinfo(oi_task *t, oi_task_int_cb cb, const char *node,
+ const char *service, struct addrinfo *hints, struct addrinfo **res)
+{
+ oi_task_init_common(t, OI_TASK_GETADDRINFO);
+ t->params.getaddrinfo.cb = cb;
+ t->params.getaddrinfo.nodename = node;
+ t->params.getaddrinfo.servname = service;
+ t->params.getaddrinfo.hints = hints;
+ t->params.getaddrinfo.res = res;
+}
+
+static inline void
+oi_task_init_lstat(oi_task *t, oi_task_int_cb cb, const char *path, struct stat *buf)
+{
+ oi_task_init_common(t, OI_TASK_LSTAT);
+ t->params.lstat.cb = cb;
+ t->params.lstat.path = path;
+ t->params.lstat.buf = buf;
+}
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* oi_async_h */
diff --git a/deps/liboi/oi_buf.c b/deps/liboi/oi_buf.c
new file mode 100644
index 0000000000..a192f7dc58
--- /dev/null
+++ b/deps/liboi/oi_buf.c
@@ -0,0 +1,41 @@
+#include
+#include
+#include
+
+void oi_buf_destroy
+ ( oi_buf *buf
+ )
+{
+ free(buf->base);
+ free(buf);
+}
+
+oi_buf * oi_buf_new2
+ ( size_t len
+ )
+{
+ oi_buf *buf = malloc(sizeof(oi_buf));
+ if(!buf)
+ return NULL;
+ buf->base = malloc(len);
+ if(!buf->base) {
+ free(buf);
+ return NULL;
+ }
+ buf->len = len;
+ buf->release = oi_buf_destroy;
+ return buf;
+}
+
+oi_buf * oi_buf_new
+ ( const char *base
+ , size_t len
+ )
+{
+ oi_buf *buf = oi_buf_new2(len);
+ if(!buf)
+ return NULL;
+ memcpy(buf->base, base, len);
+ return buf;
+}
+
diff --git a/deps/liboi/oi_buf.h b/deps/liboi/oi_buf.h
new file mode 100644
index 0000000000..b810c050e5
--- /dev/null
+++ b/deps/liboi/oi_buf.h
@@ -0,0 +1,30 @@
+#include
+
+#ifndef oi_buf_h
+#define oi_buf_h
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct oi_buf oi_buf;
+
+struct oi_buf {
+ /* public */
+ char *base;
+ size_t len;
+ void (*release) (oi_buf *); /* called when oi is done with the object */
+ void *data;
+
+ /* private */
+ size_t written;
+ oi_queue queue;
+};
+
+oi_buf * oi_buf_new (const char* base, size_t len);
+oi_buf * oi_buf_new2 (size_t len);
+void oi_buf_destroy (oi_buf *);
+
+#ifdef __cplusplus
+}
+#endif
+#endif // oi_buf_h
diff --git a/deps/liboi/oi_error.h b/deps/liboi/oi_error.h
new file mode 100644
index 0000000000..0f8730f5b5
--- /dev/null
+++ b/deps/liboi/oi_error.h
@@ -0,0 +1,25 @@
+#ifndef oi_error_h
+#define oi_error_h
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct oi_error {
+ enum { OI_ERROR_GNUTLS
+ , OI_ERROR_EV
+ , OI_ERROR_CLOSE
+ , OI_ERROR_SHUTDOWN
+ , OI_ERROR_OPEN
+ , OI_ERROR_SEND
+ , OI_ERROR_RECV
+ , OI_ERROR_WRITE
+ , OI_ERROR_READ
+ , OI_ERROR_SENDFILE
+ } domain;
+ int code; /* errno */
+};
+
+#ifdef __cplusplus
+}
+#endif
+#endif // oi_error_h
diff --git a/deps/liboi/oi_file.c b/deps/liboi/oi_file.c
new file mode 100644
index 0000000000..1073b0852a
--- /dev/null
+++ b/deps/liboi/oi_file.c
@@ -0,0 +1,391 @@
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+#define RELEASE_BUF(buf) if(buf->release) { buf->release(buf); }
+#define DRAIN_CB(file) if(file->on_drain) { file->on_drain(file); }
+#define RAISE_ERROR(s, _domain, _code) do { \
+ if(s->on_error) { \
+ struct oi_error __oi_error; \
+ __oi_error.domain = _domain; \
+ __oi_error.code = _code; \
+ s->on_error(s, __oi_error); \
+ } \
+} while(0) \
+
+/* forwards */
+static void dispatch_write_buf (oi_file *file);
+static void maybe_do_read (oi_file *file);
+
+static void
+after_read(oi_task *task, ssize_t recved)
+{
+ oi_file *file = task->data;
+
+ if(recved == -1) {
+ RAISE_ERROR(file, OI_ERROR_READ, errno);
+ return;
+ }
+
+ if(recved == 0)
+ oi_file_read_stop(file);
+
+ if(file->on_read)
+ file->on_read(file, recved);
+
+ maybe_do_read(file);
+}
+
+static void
+maybe_do_read(oi_file *file)
+{
+ if ( file->read_buffer == NULL
+ || file->write_buf != NULL
+ || file->write_socket != NULL
+ || !oi_queue_empty(&file->write_queue)
+ || file->io_task.active
+ ) return;
+
+ assert(file->fd > 0);
+
+ oi_task_init_read ( &file->io_task
+ , after_read
+ , file->fd
+ , file->read_buffer
+ , file->read_buffer_size
+ );
+ file->io_task.data = file;
+ oi_async_submit(&file->async, &file->io_task);
+}
+
+static void
+submit_read (oi_file *file)
+{
+}
+
+int
+oi_file_init (oi_file *file)
+{
+ oi_async_init(&file->async);
+ file->async.data = file;
+
+ oi_queue_init(&file->write_queue);
+
+ file->fd = -1;
+ file->loop = NULL;
+ file->write_buf = NULL;
+ file->read_buffer = NULL;
+
+ file->on_open = NULL;
+ file->on_read = NULL;
+ file->on_drain = NULL;
+ file->on_error = NULL;
+ file->on_close = NULL;
+ return 0;
+}
+
+void
+oi_file_read_start (oi_file *file, void *buffer, size_t bufsize)
+{
+ file->read_buffer = buffer;
+ file->read_buffer_size = bufsize;
+ maybe_do_read(file);
+}
+
+void
+oi_file_read_stop (oi_file *file)
+{
+ file->read_buffer = NULL;
+}
+
+void
+oi_api_free_buf_with_heap_base(oi_buf *buf)
+{
+ free(buf->base);
+ free(buf);
+}
+
+static void
+after_open(oi_task *task, int result)
+{
+ oi_file *file = task->data;
+
+ if(result == -1) {
+ RAISE_ERROR(file, OI_ERROR_OPEN, errno);
+ return;
+ }
+
+ file->fd = result;
+
+ if(file->on_open) {
+ file->on_open(file);
+ }
+
+ maybe_do_read(file);
+}
+
+int
+oi_file_open_path (oi_file *file, const char *path, int flags, mode_t mode)
+{
+ if(file->fd >= 0)
+ return -1;
+ oi_task_init_open( &file->io_task
+ , after_open
+ , path
+ , flags
+ , mode
+ );
+ file->io_task.data = file;
+ oi_async_submit(&file->async, &file->io_task);
+ return 0;
+}
+
+int
+oi_file_open_stdin (oi_file *file)
+{
+ if(file->fd >= 0)
+ return -1;
+ file->fd = STDIN_FILENO;
+ if(file->on_open)
+ file->on_open(file);
+ return 0;
+}
+
+int
+oi_file_open_stdout (oi_file *file)
+{
+ if(file->fd >= 0)
+ return -1;
+ file->fd = STDOUT_FILENO;
+ if(file->on_open)
+ file->on_open(file);
+ return 0;
+}
+
+int
+oi_file_open_stderr (oi_file *file)
+{
+ if(file->fd >= 0)
+ return -1;
+ file->fd = STDERR_FILENO;
+ if(file->on_open)
+ file->on_open(file);
+ return 0;
+}
+
+void
+oi_file_attach (oi_file *file, struct ev_loop *loop)
+{
+ oi_async_attach (loop, &file->async);
+ file->loop = loop;
+}
+
+void
+oi_file_detach (oi_file *file)
+{
+ oi_async_detach (&file->async);
+ file->loop = NULL;
+}
+
+static void
+after_write(oi_task *task, ssize_t result)
+{
+ oi_file *file = task->data;
+
+ if(result == -1) {
+ RAISE_ERROR(file, OI_ERROR_WRITE, errno);
+ return;
+ }
+
+ assert(file->write_buf != NULL);
+ oi_buf *buf = file->write_buf;
+
+ buf->written += result;
+ if(buf->written < buf->len) {
+ oi_task_init_write ( &file->io_task
+ , after_write
+ , file->fd
+ , buf->base + buf->written
+ , buf->len - buf->written
+ );
+ file->io_task.data = file;
+ oi_async_submit(&file->async, &file->io_task);
+ return;
+ }
+
+ assert(buf->written == buf->len);
+
+ RELEASE_BUF(file->write_buf);
+ file->write_buf = NULL;
+
+ if(oi_queue_empty(&file->write_queue)) {
+ DRAIN_CB(file);
+ maybe_do_read(file);
+ } else {
+ dispatch_write_buf(file);
+ }
+
+ return;
+}
+
+static void
+dispatch_write_buf(oi_file *file)
+{
+ if(file->write_buf != NULL)
+ return;
+ if(oi_queue_empty(&file->write_queue))
+ return;
+
+ oi_queue *q = oi_queue_last(&file->write_queue);
+ oi_queue_remove(q);
+ oi_buf *buf = file->write_buf = oi_queue_data(q, oi_buf, queue);
+
+ assert(!file->io_task.active);
+ oi_task_init_write ( &file->io_task
+ , after_write
+ , file->fd
+ , buf->base + buf->written
+ , buf->len - buf->written
+ );
+ file->io_task.data = file;
+ oi_async_submit(&file->async, &file->io_task);
+}
+
+int
+oi_file_write (oi_file *file, oi_buf *buf)
+{
+ if(file->fd < 0)
+ return -1;
+ if(file->read_buffer)
+ return -2;
+ /* TODO better business check*/
+
+ buf->written = 0;
+ oi_queue_insert_head(&file->write_queue, &buf->queue);
+ dispatch_write_buf(file);
+
+ return 0;
+}
+
+// Writes a string to the file.
+// NOTE: Allocates memory. Avoid for performance applications.
+int
+oi_file_write_simple (oi_file *file, const char *str, size_t len)
+{
+ if(file->fd < 0)
+ return -1;
+ if(file->read_buffer)
+ return -2;
+ /* TODO better business check*/
+
+ oi_buf *buf = malloc(sizeof(oi_buf));
+ buf->base = malloc(len);
+ memcpy(buf->base, str, len);
+ buf->len = len;
+ buf->release = oi_api_free_buf_with_heap_base;
+
+ oi_file_write(file, buf);
+ return 0;
+}
+
+static void
+clear_write_queue(oi_file *file)
+{
+ while(!oi_queue_empty(&file->write_queue)) {
+ oi_queue *q = oi_queue_last(&file->write_queue);
+ oi_queue_remove(q);
+ oi_buf *buf = oi_queue_data(q, oi_buf, queue);
+ RELEASE_BUF(buf);
+ }
+}
+
+static void
+after_close(oi_task *task, int result)
+{
+ oi_file *file = task->data;
+
+ assert(oi_queue_empty(&file->write_queue));
+
+ if(result == -1) {
+ RAISE_ERROR(file, OI_ERROR_CLOSE, errno);
+ return;
+ // TODO try to close again?
+ }
+
+ file->fd = -1;
+ // TODO deinit task_queue, detach thread_pool_result_watcher
+
+ if(file->on_close) {
+ file->on_close(file);
+ }
+
+ return;
+}
+
+void
+oi_file_close (oi_file *file)
+{
+ assert(file->fd >= 0 && "file not open!");
+ clear_write_queue(file);
+ oi_task_init_close ( &file->io_task
+ , after_close
+ , file->fd
+ );
+ file->io_task.data = file;
+ oi_async_submit(&file->async, &file->io_task);
+}
+
+static void
+after_sendfile(oi_task *task, ssize_t sent)
+{
+ oi_file *file = task->data;
+ oi_socket *socket = file->write_socket;
+ assert(socket != NULL);
+ file->write_socket = NULL;
+
+ if(sent == -1) {
+ RAISE_ERROR(file, OI_ERROR_SENDFILE, errno);
+ return;
+ }
+
+ if(socket->on_drain) {
+ socket->on_drain(socket);
+ }
+
+ maybe_do_read(file);
+}
+
+int
+oi_file_send (oi_file *file, oi_socket *destination, off_t offset, size_t count)
+{
+ if(file->fd < 0)
+ return -1;
+ if(file->read_buffer)
+ return -2;
+ /* TODO better business check*/
+
+ assert(file->write_socket == NULL);
+ // (1) make sure the write queue on the socket is cleared.
+ //
+ // (2)
+ //
+ file->write_socket = destination;
+ oi_task_init_sendfile ( &file->io_task
+ , after_sendfile
+ , destination->fd
+ , file->fd
+ , offset
+ , count
+ );
+ file->io_task.data = file;
+ oi_async_submit(&file->async, &file->io_task);
+
+ return 0;
+}
+
diff --git a/deps/liboi/oi_file.h b/deps/liboi/oi_file.h
new file mode 100644
index 0000000000..6cd917ea4c
--- /dev/null
+++ b/deps/liboi/oi_file.h
@@ -0,0 +1,58 @@
+#include
+#include
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef oi_file_h
+#define oi_file_h
+
+typedef struct oi_file oi_file;
+
+int oi_file_init (oi_file *);
+
+void oi_file_attach (oi_file *, struct ev_loop *);
+void oi_file_detach (oi_file *);
+
+/* WARNING oi_file_open_path: path argument must be valid until oi_file
+ * object is closed and the on_close() callback is made. oi does not strdup
+ * the path pointer. */
+int oi_file_open_path (oi_file *, const char *path, int flags, mode_t mode);
+int oi_file_open_stdin (oi_file *);
+int oi_file_open_stdout (oi_file *);
+int oi_file_open_stderr (oi_file *);
+
+void oi_file_read_start (oi_file *, void *buffer, size_t bufsize);
+void oi_file_read_stop (oi_file *);
+int oi_file_write (oi_file *, oi_buf *to_write);
+int oi_file_write_simple (oi_file *, const char *, size_t);
+int oi_file_send (oi_file *source, oi_socket *destination, off_t offset, size_t length);
+void oi_file_close (oi_file *);
+
+struct oi_file {
+ /* private */
+ oi_async async;
+ oi_task io_task;
+ struct ev_loop *loop;
+ oi_queue write_queue;
+ oi_buf *write_buf; /* TODO this pointer is unnecessary - remove and just look at first element of the queue */
+ oi_socket *write_socket;
+ void *read_buffer;
+ size_t read_buffer_size;
+
+ /* read-only */
+ int fd;
+
+ /* public */
+ void (*on_open) (oi_file *);
+ void (*on_read) (oi_file *, size_t count);
+ void (*on_drain) (oi_file *);
+ void (*on_error) (oi_file *, struct oi_error);
+ void (*on_close) (oi_file *);
+ void *data;
+};
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* oi_file_h */
diff --git a/deps/liboi/oi_queue.h b/deps/liboi/oi_queue.h
new file mode 100644
index 0000000000..0405e336f5
--- /dev/null
+++ b/deps/liboi/oi_queue.h
@@ -0,0 +1,69 @@
+/* Copyright (C) 2002-2009 Igor Sysoev
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+#ifndef oi_queue_h
+#define oi_queue_h
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include /* offsetof() */
+
+typedef struct oi_queue oi_queue;
+struct oi_queue {
+ oi_queue *prev;
+ oi_queue *next;
+};
+
+#define oi_queue_init(q) \
+ (q)->prev = q; \
+ (q)->next = q
+
+#define oi_queue_empty(h) \
+ (h == (h)->prev)
+
+#define oi_queue_insert_head(h, x) \
+ (x)->next = (h)->next; \
+ (x)->next->prev = x; \
+ (x)->prev = h; \
+ (h)->next = x
+
+#define oi_queue_head(h) \
+ (h)->next
+
+#define oi_queue_last(h) \
+ (h)->prev
+
+#define oi_queue_remove(x) \
+ (x)->next->prev = (x)->prev; \
+ (x)->prev->next = (x)->next; \
+ (x)->prev = NULL; \
+ (x)->next = NULL
+
+#define oi_queue_data(q, type, link) \
+ (type *) ((unsigned char *) q - offsetof(type, link))
+
+#ifdef __cplusplus
+}
+#endif
+#endif // oi_queue_h
diff --git a/deps/liboi/oi_socket.c b/deps/liboi/oi_socket.c
new file mode 100644
index 0000000000..f922d23890
--- /dev/null
+++ b/deps/liboi/oi_socket.c
@@ -0,0 +1,941 @@
+#include
+#include
+#include
+#include /* close() */
+#include /* fcntl() */
+#include /* for the default methods */
+#include /* memset */
+
+#include /* TCP_NODELAY */
+
+#include
+#include
+
+#if HAVE_GNUTLS
+# include
+# define GNUTLS_NEED_WRITE (gnutls_record_get_direction(socket->session) == 1)
+# define GNUTLS_NEED_READ (gnutls_record_get_direction(socket->session) == 0)
+#endif
+
+#undef TRUE
+#define TRUE 1
+#undef FALSE
+#define FALSE 0
+#undef MIN
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+
+#define OKAY 0
+#define AGAIN 1
+#define ERROR 2
+
+#define RAISE_ERROR(s, _domain, _code) do { \
+ if(s->on_error) { \
+ struct oi_error __oi_error; \
+ __oi_error.domain = _domain; \
+ __oi_error.code = _code; \
+ s->on_error(s, __oi_error); \
+ } \
+} while(0) \
+
+static int
+full_close(oi_socket *socket)
+{
+ if(-1 == close(socket->fd) && errno == EINTR) {
+ /* TODO fd still open. next loop call close again? */
+ assert(0 && "implement me");
+ return ERROR;
+ }
+
+ socket->read_action = NULL;
+ socket->write_action = NULL;
+
+ if(socket->loop) {
+ ev_feed_event(socket->loop, &socket->read_watcher, EV_READ);
+ }
+ return OKAY;
+}
+
+static int
+half_close(oi_socket *socket)
+{
+ int r = shutdown(socket->fd, SHUT_WR);
+
+ if(r == -1) {
+ RAISE_ERROR(socket, OI_ERROR_SHUTDOWN, errno);
+ return ERROR;
+ }
+
+ socket->write_action = NULL;
+
+ /* TODO set timer to zero so we get a callback soon */
+ return OKAY;
+}
+
+static void
+update_write_buffer_after_send(oi_socket *socket, ssize_t sent)
+{
+ oi_queue *q = oi_queue_last(&socket->out_stream);
+ oi_buf *to_write = oi_queue_data(q, oi_buf, queue);
+ to_write->written += sent;
+ socket->written += sent;
+
+ if(to_write->written == to_write->len) {
+
+ oi_queue_remove(q);
+
+ if(to_write->release) {
+ to_write->release(to_write);
+ }
+
+ if(oi_queue_empty(&socket->out_stream)) {
+ ev_io_stop(socket->loop, &socket->write_watcher);
+ if(socket->on_drain)
+ socket->on_drain(socket);
+ }
+ }
+}
+
+
+#if HAVE_GNUTLS
+static int secure_socket_send(oi_socket *socket);
+static int secure_socket_recv(oi_socket *socket);
+
+/* TODO can this be done without ignoring SIGPIPE? */
+static ssize_t
+nosigpipe_push(gnutls_transport_ptr_t data, const void *buf, size_t len)
+{
+ oi_socket *socket = (oi_socket*)data;
+ assert(socket->secure);
+ int flags = 0;
+#ifdef MSG_NOSIGNAL
+ flags |= MSG_NOSIGNAL;
+#endif
+#ifdef MSG_DONTWAIT
+ flags |= MSG_DONTWAIT;
+#endif
+ int r = send(socket->fd, buf, len, flags);
+
+ if(r == -1) {
+ gnutls_transport_set_errno(socket->session, errno); /* necessary ? */
+ }
+
+ return r;
+}
+
+static int
+secure_handshake(oi_socket *socket)
+{
+ assert(socket->secure);
+
+ int r = gnutls_handshake(socket->session);
+
+ if(gnutls_error_is_fatal(r)) {
+ RAISE_ERROR(socket, OI_ERROR_GNUTLS, r);
+ return ERROR;
+ }
+
+ if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN)
+ return AGAIN;
+
+ oi_socket_reset_timeout(socket);
+
+ if(!socket->connected) {
+ socket->connected = TRUE;
+ if(socket->on_connect)
+ socket->on_connect(socket);
+ }
+
+ if(socket->read_action)
+ socket->read_action = secure_socket_recv;
+
+ if(socket->write_action)
+ socket->write_action = secure_socket_send;
+
+ return OKAY;
+}
+
+static int
+secure_socket_send(oi_socket *socket)
+{
+ ssize_t sent;
+
+ if(oi_queue_empty(&socket->out_stream)) {
+ ev_io_stop(socket->loop, &socket->write_watcher);
+ return AGAIN;
+ }
+
+ oi_queue *q = oi_queue_last(&socket->out_stream);
+ oi_buf *to_write = oi_queue_data(q, oi_buf, queue);
+
+ assert(socket->secure);
+
+ sent = gnutls_record_send( socket->session
+ , to_write->base + to_write->written
+ , to_write->len - to_write->written
+ );
+
+ if(gnutls_error_is_fatal(sent)) {
+ RAISE_ERROR(socket, OI_ERROR_GNUTLS, sent);
+ return ERROR;
+ }
+
+ if(sent == 0)
+ return AGAIN;
+
+ oi_socket_reset_timeout(socket);
+
+ if(sent == GNUTLS_E_INTERRUPTED || sent == GNUTLS_E_AGAIN) {
+ if(GNUTLS_NEED_READ) {
+ if(socket->read_action) {
+ socket->read_action = secure_socket_send;
+ } else {
+ /* TODO GnuTLS needs read but already got EOF */
+ assert(0 && "needs read but already got EOF");
+ return ERROR;
+ }
+ }
+ return AGAIN;
+ }
+
+ if(sent > 0) {
+ /* make sure the callbacks are correct */
+ if(socket->read_action)
+ socket->read_action = secure_socket_recv;
+ update_write_buffer_after_send(socket, sent);
+ return OKAY;
+ }
+
+ assert(0 && "Unhandled return code from gnutls_record_send()!");
+ return ERROR;
+}
+
+static int
+secure_socket_recv(oi_socket *socket)
+{
+ char recv_buffer[TCP_MAXWIN];
+ size_t recv_buffer_size = MIN(TCP_MAXWIN, socket->chunksize);
+ ssize_t recved;
+
+ assert(socket->secure);
+
+ recved = gnutls_record_recv(socket->session, recv_buffer, recv_buffer_size);
+
+ //printf("secure socket recv %d %p\n", recved, socket->on_connect);
+
+ if(gnutls_error_is_fatal(recved)) {
+ RAISE_ERROR(socket, OI_ERROR_GNUTLS, recved);
+ return ERROR;
+ }
+
+ if(recved == GNUTLS_E_INTERRUPTED || recved == GNUTLS_E_AGAIN) {
+ if(GNUTLS_NEED_WRITE) {
+ if(socket->write_action) {
+ printf("need write\n");
+ socket->write_action = secure_socket_recv;
+ } else {
+ /* TODO GnuTLS needs send but already closed write end */
+ assert(0 && "needs read but cannot");
+ return ERROR;
+ }
+ }
+ return AGAIN;
+ }
+
+ oi_socket_reset_timeout(socket);
+
+ /* A server may also receive GNUTLS_E_REHANDSHAKE when a client has
+ * initiated a handshake. In that case the server can only initiate a
+ * handshake or terminate the connection. */
+ if(recved == GNUTLS_E_REHANDSHAKE) {
+ if(socket->write_action) {
+ socket->read_action = secure_handshake;
+ socket->write_action = secure_handshake;
+ return OKAY;
+ } else {
+ /* TODO */
+ assert(0 && "needs read but cannot");
+ return ERROR;
+ }
+ }
+
+ if(recved >= 0) {
+ /* Got EOF */
+ if(recved == 0)
+ socket->read_action = NULL;
+
+ if(socket->write_action)
+ socket->write_action = secure_socket_send;
+
+ if(socket->on_read) { socket->on_read(socket, recv_buffer, recved); }
+
+ return OKAY;
+ }
+
+ assert(0 && "Unhandled return code from gnutls_record_send()!");
+ return ERROR;
+}
+
+static int
+secure_goodbye(oi_socket *socket, gnutls_close_request_t how)
+{
+ assert(socket->secure);
+
+ int r = gnutls_bye(socket->session, how);
+
+ if(gnutls_error_is_fatal(r)) {
+ RAISE_ERROR(socket, OI_ERROR_GNUTLS, r);
+ return ERROR;
+ }
+
+ if(r == GNUTLS_E_INTERRUPTED || r == GNUTLS_E_AGAIN)
+ return AGAIN;
+
+ return OKAY;
+}
+
+static int
+secure_full_goodbye(oi_socket *socket)
+{
+ int r = secure_goodbye(socket, GNUTLS_SHUT_RDWR);
+ if(OKAY == r) {
+ return full_close(socket);
+ }
+ return r;
+}
+
+static int
+secure_half_goodbye(oi_socket *socket)
+{
+ int r = secure_goodbye(socket, GNUTLS_SHUT_WR);
+ if(OKAY == r) {
+ return half_close(socket);
+ }
+ return r;
+}
+
+/* Tells the socket to use transport layer security (SSL). liboi does not
+ * want to make any decisions about security requirements, so the
+ * majoirty of GnuTLS configuration is left to the user. Only the transport
+ * layer of GnuTLS is controlled by liboi.
+ *
+ * That is, do not use gnutls_transport_* functions.
+ * Do use the rest of GnuTLS's API.
+ */
+void
+oi_socket_set_secure_session (oi_socket *socket, gnutls_session_t session)
+{
+ socket->session = session;
+ socket->secure = TRUE;
+}
+#endif /* HAVE GNUTLS */
+
+static int
+socket_send(oi_socket *socket)
+{
+ ssize_t sent;
+
+ assert(socket->secure == FALSE);
+
+ if(oi_queue_empty(&socket->out_stream)) {
+ ev_io_stop(socket->loop, &socket->write_watcher);
+ return AGAIN;
+ }
+
+ oi_queue *q = oi_queue_last(&socket->out_stream);
+ oi_buf *to_write = oi_queue_data(q, oi_buf, queue);
+
+ int flags = 0;
+#ifdef MSG_NOSIGNAL
+ flags |= MSG_NOSIGNAL;
+#endif
+#ifdef MSG_DONTWAIT
+ flags |= MSG_DONTWAIT;
+#endif
+
+ /* TODO use writev() here */
+
+ sent = send( socket->fd
+ , to_write->base + to_write->written
+ , to_write->len - to_write->written
+ , flags
+ );
+
+ if(sent < 0) {
+ switch(errno) {
+ case EAGAIN:
+ return AGAIN;
+
+ case ECONNREFUSED:
+ case ECONNRESET:
+ socket->write_action = NULL;
+ /* TODO maybe just clear write buffer instead of error?
+ * They should be able to read still from the socket.
+ */
+ RAISE_ERROR(socket, OI_ERROR_SEND, errno);
+ return ERROR;
+
+ default:
+ perror("send()");
+ assert(0 && "oi shouldn't let this happen.");
+ return ERROR;
+ }
+ }
+
+ oi_socket_reset_timeout(socket);
+
+ if(!socket->connected) {
+ socket->connected = TRUE;
+ if(socket->on_connect) { socket->on_connect(socket); }
+ }
+
+ update_write_buffer_after_send(socket, sent);
+
+ return OKAY;
+}
+
+static int
+socket_recv(oi_socket *socket)
+{
+ char buf[TCP_MAXWIN];
+ size_t buf_size = TCP_MAXWIN;
+ ssize_t recved;
+
+ assert(socket->secure == FALSE);
+
+ if(!socket->connected) {
+ socket->connected = TRUE;
+ if(socket->on_connect) { socket->on_connect(socket); }
+ return OKAY;
+ }
+
+ recved = recv(socket->fd, buf, buf_size, 0);
+
+ if(recved < 0) {
+ switch(errno) {
+ case EAGAIN:
+ case EINTR:
+ return AGAIN;
+
+ /* A remote host refused to allow the network connection (typically
+ * because it is not running the requested service). */
+ case ECONNREFUSED:
+ RAISE_ERROR(socket, OI_ERROR_RECV, errno);
+ return ERROR;
+
+ case ECONNRESET:
+ RAISE_ERROR(socket, OI_ERROR_RECV, errno);
+ return ERROR;
+
+ default:
+ perror("recv()");
+ printf("unmatched errno %d\n", errno);
+ assert(0 && "recv returned error that oi should have caught before.");
+ return ERROR;
+ }
+ }
+
+ oi_socket_reset_timeout(socket);
+
+ if(recved == 0) {
+ oi_socket_read_stop(socket);
+ socket->read_action = NULL;
+ }
+
+ /* NOTE: EOF is signaled with recved == 0 on callback */
+ if(socket->on_read) { socket->on_read(socket, buf, recved); }
+
+ return OKAY;
+}
+
+static void
+assign_file_descriptor(oi_socket *socket, int fd)
+{
+ socket->fd = fd;
+
+ ev_io_set (&socket->read_watcher, fd, EV_READ);
+ ev_io_set (&socket->write_watcher, fd, EV_WRITE);
+
+ socket->read_action = socket_recv;
+ socket->write_action = socket_send;
+
+#if HAVE_GNUTLS
+ if(socket->secure) {
+ gnutls_transport_set_lowat(socket->session, 0);
+ gnutls_transport_set_push_function(socket->session, nosigpipe_push);
+ gnutls_transport_set_ptr2 ( socket->session
+ /* recv */ , (gnutls_transport_ptr_t)fd
+ /* send */ , socket
+ );
+ socket->read_action = secure_handshake;
+ socket->write_action = secure_handshake;
+ }
+#endif
+}
+
+
+/* Internal callback
+ * Called by server->connection_watcher.
+ */
+static void
+on_connection(struct ev_loop *loop, ev_io *watcher, int revents)
+{
+ oi_server *server = watcher->data;
+
+ // printf("on connection!\n");
+
+ assert(server->listening);
+ assert(server->loop == loop);
+ assert(&server->connection_watcher == watcher);
+
+ if(EV_ERROR & revents) {
+ oi_server_close(server);
+ return;
+ }
+
+ struct sockaddr address; /* connector's address information */
+ socklen_t addr_len = sizeof(address);
+
+ /* TODO accept all possible connections? currently: just one */
+ int fd = accept(server->fd, (struct sockaddr*)&address, &addr_len);
+ if(fd < 0) {
+ perror("accept()");
+ return;
+ }
+
+ oi_socket *socket = NULL;
+ if(server->on_connection)
+ socket = server->on_connection(server, (struct sockaddr*)&address, addr_len);
+
+ if(socket == NULL) {
+ close(fd);
+ return;
+ }
+
+ int flags = fcntl(fd, F_GETFL, 0);
+ int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ if(r < 0) {
+ /* TODO error report */
+ }
+
+#ifdef SO_NOSIGPIPE
+ flags = 1;
+ setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &flags, sizeof(flags));
+#endif
+
+ socket->server = server;
+ assign_file_descriptor(socket, fd);
+ oi_socket_attach(socket, loop);
+}
+
+int
+oi_server_listen(oi_server *server, struct addrinfo *addrinfo)
+{
+ int fd = -1;
+ struct linger ling = {0, 0};
+ assert(server->listening == FALSE);
+
+ fd = socket( addrinfo->ai_family
+ , addrinfo->ai_socktype
+ , addrinfo->ai_protocol
+ );
+ if(fd < 0) {
+ perror("socket()");
+ return -1;
+ }
+
+ int flags = fcntl(fd, F_GETFL, 0);
+ int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ if(r < 0) {
+ perror("fcntl()");
+ return -1;
+ }
+
+ 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));
+
+ if (bind(fd, addrinfo->ai_addr, addrinfo->ai_addrlen) < 0) {
+ perror("bind()");
+ close(fd);
+ return -1;
+ }
+
+ if (listen(fd, server->backlog) < 0) {
+ perror("listen()");
+ close(fd);
+ return -1;
+ }
+
+ server->fd = fd;
+ server->listening = TRUE;
+ ev_io_set (&server->connection_watcher, server->fd, EV_READ);
+
+ return 0;
+}
+
+/**
+ * Stops the server. Will not accept new connections. Does not drop
+ * existing connections.
+ */
+void
+oi_server_close(oi_server *server)
+{
+ if(server->listening) {
+ oi_server_detach(server);
+ close(server->fd);
+ /* TODO do this on the loop? check return value? */
+ server->listening = FALSE;
+ }
+}
+
+void
+oi_server_attach (oi_server *server, struct ev_loop *loop)
+{
+ ev_io_start (loop, &server->connection_watcher);
+ server->loop = loop;
+}
+
+void
+oi_server_detach (oi_server *server)
+{
+ ev_io_stop (server->loop, &server->connection_watcher);
+ server->loop = NULL;
+}
+
+void
+oi_server_init(oi_server *server, int backlog)
+{
+ server->backlog = backlog;
+ server->listening = FALSE;
+ server->fd = -1;
+ server->connection_watcher.data = server;
+ ev_init (&server->connection_watcher, on_connection);
+
+ server->on_connection = NULL;
+ server->on_error = NULL;
+ server->data = NULL;
+}
+
+/* Internal callback. called by socket->timeout_watcher */
+static void
+on_timeout(struct ev_loop *loop, ev_timer *watcher, int revents)
+{
+ oi_socket *socket = watcher->data;
+
+ assert(watcher == &socket->timeout_watcher);
+
+ // printf("on_timeout\n");
+
+ if(socket->on_timeout) { socket->on_timeout(socket); }
+
+
+ /* TODD set timer to zero */
+ full_close(socket);
+}
+
+static void
+release_write_buffer(oi_socket *socket)
+{
+ while(!oi_queue_empty(&socket->out_stream)) {
+ oi_queue *q = oi_queue_last(&socket->out_stream);
+ oi_buf *buf = oi_queue_data(q, oi_buf, queue);
+ oi_queue_remove(q);
+ if(buf->release) { buf->release(buf); }
+ }
+}
+
+/* Internal callback. called by socket->read_watcher */
+static void
+on_io_event(struct ev_loop *loop, ev_io *watcher, int revents)
+{
+ oi_socket *socket = watcher->data;
+
+ if(revents & EV_ERROR) {
+ RAISE_ERROR(socket, OI_ERROR_EV, 0);
+ goto close;
+ }
+
+ int r;
+ int have_read_event = TRUE;
+ int have_write_event = TRUE;
+
+ while(have_read_event || have_write_event) {
+
+ if(socket->read_action) {
+ r = socket->read_action(socket);
+ if(r == ERROR) goto close;
+ if(r == AGAIN) have_read_event = FALSE;
+ } else {
+ have_read_event = FALSE;
+ }
+
+ if(socket->write_action) {
+ r = socket->write_action(socket);
+ if(r == ERROR) goto close;
+ if(r == AGAIN) have_write_event = FALSE;
+ } else {
+ have_write_event = FALSE;
+ }
+
+
+ if(socket->read_watcher.active == FALSE)
+ have_read_event = FALSE;
+ if(socket->write_watcher.active == FALSE)
+ have_write_event = FALSE;
+ }
+
+ if(socket->write_action == NULL && socket->read_action == NULL)
+ goto close;
+
+ return;
+
+close:
+ release_write_buffer(socket);
+
+ ev_clear_pending (socket->loop, &socket->write_watcher);
+ ev_clear_pending (socket->loop, &socket->read_watcher);
+ ev_clear_pending (socket->loop, &socket->timeout_watcher);
+
+ oi_socket_detach(socket);
+
+ if(socket->on_close) { socket->on_close(socket); }
+ /* WARNING: user can free socket in on_close so no more
+ * access beyond this point. */
+}
+
+/**
+ * If using SSL do consider setting
+ * gnutls_db_set_retrieve_function (socket->session, _);
+ * gnutls_db_set_remove_function (socket->session, _);
+ * gnutls_db_set_store_function (socket->session, _);
+ * gnutls_db_set_ptr (socket->session, _);
+ */
+void
+oi_socket_init(oi_socket *socket, float timeout)
+{
+ socket->fd = -1;
+ socket->server = NULL;
+ socket->loop = NULL;
+ socket->connected = FALSE;
+
+ oi_queue_init(&socket->out_stream);
+
+ ev_init (&socket->write_watcher, on_io_event);
+ socket->write_watcher.data = socket;
+
+ ev_init(&socket->read_watcher, on_io_event);
+ socket->read_watcher.data = socket;
+
+ socket->secure = FALSE;
+ socket->wait_for_secure_hangup = FALSE;
+#if HAVE_GNUTLS
+ socket->session = NULL;
+#endif
+
+ /* TODO higher resolution timer */
+ ev_timer_init(&socket->timeout_watcher, on_timeout, 0., timeout);
+ socket->timeout_watcher.data = socket;
+
+ socket->read_action = NULL;
+ socket->write_action = NULL;
+
+ socket->chunksize = TCP_MAXWIN;
+ socket->on_connect = NULL;
+ socket->on_read = NULL;
+ socket->on_drain = NULL;
+ socket->on_error = NULL;
+ socket->on_timeout = NULL;
+}
+
+void
+oi_socket_write_eof (oi_socket *socket)
+{
+#if HAVE_GNUTLS
+ /* try to hang up properly for secure connections */
+ if(socket->secure)
+ {
+ if( socket->connected /* completed handshake */
+ && socket->write_action /* write end is open */
+ )
+ {
+ socket->write_action = secure_half_goodbye;
+ if(socket->loop)
+ ev_io_start(socket->loop, &socket->write_watcher);
+ return;
+ }
+ /* secure servers cannot handle half-closed connections? */
+ full_close(socket);
+ return;
+ }
+#endif // HAVE_GNUTLS
+
+ if(socket->write_action)
+ half_close(socket);
+ else
+ full_close(socket);
+}
+
+void
+oi_socket_close (oi_socket *socket)
+{
+#if HAVE_GNUTLS
+ /* try to hang up properly for secure connections */
+ if( socket->secure
+ && socket->connected /* completed handshake */
+ && socket->write_action /* write end is open */
+ )
+ {
+ if(socket->wait_for_secure_hangup && socket->read_action) {
+ socket->write_action = secure_full_goodbye;
+ socket->read_action = secure_full_goodbye;
+ } else {
+ socket->write_action = secure_half_goodbye;
+ socket->read_action = NULL;
+ }
+
+ if(socket->loop)
+ ev_io_start(socket->loop, &socket->write_watcher);
+
+ return;
+ }
+#endif // HAVE_GNUTLS
+
+ full_close(socket);
+}
+
+/*
+ * Resets the timeout to stay alive for another socket->timeout seconds
+ */
+void
+oi_socket_reset_timeout(oi_socket *socket)
+{
+ ev_timer_again(socket->loop, &socket->timeout_watcher);
+}
+
+/**
+ * Writes a string to the socket. This is actually sets a watcher which may
+ * take multiple iterations to write the entire string.
+ */
+void
+oi_socket_write(oi_socket *socket, oi_buf *buf)
+{
+ if(socket->write_action == NULL)
+ return;
+
+ oi_queue_insert_head(&socket->out_stream, &buf->queue);
+
+ buf->written = 0;
+ ev_io_start(socket->loop, &socket->write_watcher);
+}
+
+static void
+free_simple_buf ( oi_buf *buf )
+{
+ free(buf->base);
+ free(buf);
+}
+
+/* Writes a string to the socket.
+ * NOTE: Allocates memory. Avoid for performance applications.
+ */
+void
+oi_socket_write_simple(oi_socket *socket, const char *str, size_t len)
+{
+ oi_buf *buf = malloc(sizeof(oi_buf));
+ buf->release = free_simple_buf;
+ buf->base = strdup(str);
+ buf->len = len;
+
+ oi_socket_write(socket, buf);
+}
+
+void
+oi_socket_attach(oi_socket *socket, struct ev_loop *loop)
+{
+ socket->loop = loop;
+
+ ev_timer_again(loop, &socket->timeout_watcher);
+
+ if(socket->read_action)
+ ev_io_start(loop, &socket->read_watcher);
+
+ if(socket->write_action)
+ ev_io_start(loop, &socket->write_watcher);
+
+ /* make sure the io_event happens soon in the case we're being reattached */
+ ev_feed_event(loop, &socket->read_watcher, EV_READ);
+}
+
+void
+oi_socket_detach(oi_socket *socket)
+{
+ if(socket->loop) {
+ ev_io_stop(socket->loop, &socket->write_watcher);
+ ev_io_stop(socket->loop, &socket->read_watcher);
+ ev_timer_stop(socket->loop, &socket->timeout_watcher);
+ socket->loop = NULL;
+ }
+}
+
+void
+oi_socket_read_stop (oi_socket *socket)
+{
+ ev_io_stop(socket->loop, &socket->read_watcher);
+ ev_clear_pending (socket->loop, &socket->read_watcher);
+}
+
+void
+oi_socket_read_start (oi_socket *socket)
+{
+ if(socket->read_action) {
+ ev_io_start(socket->loop, &socket->read_watcher);
+ /* XXX feed event? */
+ }
+}
+
+int
+oi_socket_connect(oi_socket *s, struct addrinfo *addrinfo)
+{
+ int fd = socket( addrinfo->ai_family
+ , addrinfo->ai_socktype
+ , addrinfo->ai_protocol
+ );
+ if(fd < 0) {
+ perror("socket()");
+ return -1;
+ }
+
+ int flags = fcntl(fd, F_GETFL, 0);
+ int r = fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+ if(r < 0) {
+ perror("fcntl()");
+ return -1;
+ }
+
+#ifdef SO_NOSIGPIPE
+ flags = 1;
+ setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &flags, sizeof(flags));
+#endif
+
+ r = connect( fd
+ , addrinfo->ai_addr
+ , addrinfo->ai_addrlen
+ );
+
+ if(r < 0 && errno != EINPROGRESS) {
+ perror("connect");
+ close(fd);
+ return -1;
+ }
+
+ assign_file_descriptor(s, fd);
+
+ return 0;
+}
+
diff --git a/deps/liboi/oi_socket.h b/deps/liboi/oi_socket.h
new file mode 100644
index 0000000000..1b51f0f21c
--- /dev/null
+++ b/deps/liboi/oi_socket.h
@@ -0,0 +1,98 @@
+#include
+#include
+#include
+#include
+#include
+
+#ifndef oi_socket_h
+#define oi_socket_h
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef HAVE_GNUTLS
+# define HAVE_GNUTLS 0
+#endif
+#if HAVE_GNUTLS
+# include
+#endif
+
+typedef struct oi_server oi_server;
+typedef struct oi_socket oi_socket;
+
+void oi_server_init (oi_server *, int backlog);
+ int oi_server_listen (oi_server *, struct addrinfo *addrinfo);
+void oi_server_attach (oi_server *, struct ev_loop *loop);
+void oi_server_detach (oi_server *);
+void oi_server_close (oi_server *);
+
+void oi_socket_init (oi_socket *, float timeout);
+ int oi_socket_pair (oi_socket *a, oi_socket *b); /* TODO */
+ int oi_socket_connect (oi_socket *, struct addrinfo *addrinfo);
+void oi_socket_attach (oi_socket *, struct ev_loop *loop);
+void oi_socket_detach (oi_socket *);
+void oi_socket_read_start (oi_socket *);
+void oi_socket_read_stop (oi_socket *);
+void oi_socket_reset_timeout (oi_socket *);
+void oi_socket_write (oi_socket *, oi_buf *);
+void oi_socket_write_simple (oi_socket *, const char *str, size_t len);
+void oi_socket_write_eof (oi_socket *);
+void oi_socket_close (oi_socket *);
+#if HAVE_GNUTLS
+void oi_socket_set_secure_session (oi_socket *, gnutls_session_t);
+#endif
+
+struct oi_server {
+ /* read only */
+ int fd;
+ int backlog;
+ struct ev_loop *loop;
+ unsigned listening:1;
+
+ /* private */
+ ev_io connection_watcher;
+
+ /* public */
+ oi_socket* (*on_connection) (oi_server *, struct sockaddr *remote_addr, socklen_t remove_addr_len);
+ void (*on_error) (oi_server *, struct oi_error e);
+ void *data;
+};
+
+struct oi_socket {
+ /* read only */
+ int fd;
+ struct ev_loop *loop;
+ oi_server *server;
+ oi_queue out_stream;
+ size_t written;
+ unsigned connected:1;
+ unsigned secure:1;
+ unsigned wait_for_secure_hangup:1;
+
+ /* if these are NULL then it means that end of the socket is closed. */
+ int (*read_action) (oi_socket *);
+ int (*write_action) (oi_socket *);
+
+ /* private */
+ ev_io write_watcher;
+ ev_io read_watcher;
+ ev_timer timeout_watcher;
+#if HAVE_GNUTLS
+ gnutls_session_t session;
+#endif
+
+ /* public */
+ size_t chunksize; /* the maximum chunk that on_read() will return */
+ void (*on_connect) (oi_socket *);
+ void (*on_read) (oi_socket *, const void *buf, size_t count);
+ void (*on_drain) (oi_socket *);
+ void (*on_error) (oi_socket *, struct oi_error e);
+ void (*on_close) (oi_socket *);
+ void (*on_timeout) (oi_socket *);
+ void *data;
+};
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* oi_socket_h */
diff --git a/deps/liboi/test/common.c b/deps/liboi/test/common.c
new file mode 100644
index 0000000000..4703b402fd
--- /dev/null
+++ b/deps/liboi/test/common.c
@@ -0,0 +1,110 @@
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+
+#include
+#include
+#include
+
+#define HOST "127.0.0.1"
+#define SOCKFILE "/tmp/oi.sock"
+#define PORT "5000"
+
+int nconnections;
+
+static void
+on_peer_close(oi_socket *socket)
+{
+ //printf("server connection closed\n");
+#if HAVE_GNUTLS
+#if SECURE
+ gnutls_deinit(socket->session);
+#endif
+#endif
+ free(socket);
+}
+
+static void
+on_client_timeout(oi_socket *socket)
+{
+ printf("client connection timeout\n");
+ assert(0);
+}
+
+static void
+on_peer_timeout(oi_socket *socket)
+{
+ fprintf(stderr, "peer connection timeout\n");
+ assert(0);
+}
+
+static void
+on_client_error(oi_socket *socket, struct oi_error e)
+{
+ assert(0);
+}
+
+
+#if HAVE_GNUTLS
+#if SECURE
+#define DH_BITS 768
+gnutls_anon_server_credentials_t server_credentials;
+const int kx_prio[] = { GNUTLS_KX_ANON_DH, 0 };
+static gnutls_dh_params_t dh_params;
+
+void anon_tls_init()
+{
+ gnutls_global_init();
+
+ gnutls_dh_params_init (&dh_params);
+
+ fprintf(stderr, "..");
+ fsync((int)stderr);
+ gnutls_dh_params_generate2 (dh_params, DH_BITS);
+ fprintf(stderr, ".");
+
+ gnutls_anon_allocate_server_credentials (&server_credentials);
+ gnutls_anon_set_server_dh_params (server_credentials, dh_params);
+}
+
+void anon_tls_server(oi_socket *socket)
+{
+ gnutls_session_t session;
+ socket->data = session;
+
+ int r = gnutls_init(&session, GNUTLS_SERVER);
+ assert(r == 0);
+ gnutls_set_default_priority(session);
+ gnutls_kx_set_priority (session, kx_prio);
+ gnutls_credentials_set(session, GNUTLS_CRD_ANON, server_credentials);
+ gnutls_dh_set_prime_bits(session, DH_BITS);
+
+ oi_socket_set_secure_session(socket, session);
+}
+
+void anon_tls_client(oi_socket *socket)
+{
+ gnutls_session_t client_session;
+ gnutls_anon_client_credentials_t client_credentials;
+
+ gnutls_anon_allocate_client_credentials (&client_credentials);
+ gnutls_init (&client_session, GNUTLS_CLIENT);
+ gnutls_set_default_priority(client_session);
+ gnutls_kx_set_priority(client_session, kx_prio);
+ /* Need to enable anonymous KX specifically. */
+ gnutls_credentials_set (client_session, GNUTLS_CRD_ANON, client_credentials);
+
+ oi_socket_set_secure_session(socket, client_session);
+ assert(socket->secure);
+}
+
+#endif // SECURE
+#endif // HAVE_GNUTLS
diff --git a/deps/liboi/test/connection_interruption.c b/deps/liboi/test/connection_interruption.c
new file mode 100644
index 0000000000..0b9169a9d1
--- /dev/null
+++ b/deps/liboi/test/connection_interruption.c
@@ -0,0 +1,159 @@
+#include "test/common.c"
+#define NCONN 100
+#define TIMEOUT 1000.0
+
+static oi_server server;
+
+static void
+on_peer_read(oi_socket *socket, const void *base, size_t len)
+{
+ assert(len == 0);
+ oi_socket_write_simple(socket, "BYE", 3);
+ //printf("server wrote bye\n");
+}
+
+static void
+on_peer_drain(oi_socket *socket)
+{
+ oi_socket_close(socket);
+}
+
+static void
+on_peer_error2(oi_socket *socket, struct oi_error e)
+{
+ if(e.domain == OI_ERROR_GNUTLS) return;
+ assert(0);
+}
+
+static oi_socket*
+on_server_connection(oi_server *server, struct sockaddr *addr, socklen_t len)
+{
+ oi_socket *socket = malloc(sizeof(oi_socket));
+ oi_socket_init(socket, TIMEOUT);
+ socket->on_read = on_peer_read;
+ socket->on_drain = on_peer_drain;
+ socket->on_error = on_peer_error2;
+ socket->on_close = on_peer_close;
+ socket->on_timeout = on_peer_timeout;
+
+#if HAVE_GNUTLS
+# if SECURE
+ anon_tls_server(socket);
+# endif
+#endif
+
+ //printf("on server connection\n");
+
+ return socket;
+}
+
+static void
+on_client_connect(oi_socket *socket)
+{
+ //printf("on client connection\n");
+ oi_socket_write_eof(socket);
+}
+
+static void
+on_client_close(oi_socket *socket)
+{
+ oi_socket_close(socket); // already closed, but it shouldn't crash if we try to do it again
+
+ //printf("client connection closed\n");
+ if(++nconnections == NCONN) {
+ oi_server_detach(&server);
+ //printf("detaching server\n");
+ }
+}
+
+static void
+on_client_read(oi_socket *socket, const void *base, size_t len)
+{
+ char buf[200000];
+ strncpy(buf, base, len);
+ buf[len] = 0;
+
+ //printf("client got message: %s\n", buf);
+
+ if(strcmp(buf, "BYE") == 0) {
+ oi_socket_close(socket);
+ } else {
+ assert(0);
+ }
+}
+
+int
+main(int argc, const char *argv[])
+{
+ int r;
+ struct ev_loop *loop = ev_default_loop(0);
+
+ oi_server_init(&server, 1000);
+ server.on_connection = on_server_connection;
+#if HAVE_GNUTLS
+# if SECURE
+ anon_tls_init();
+# endif
+#endif
+
+ struct addrinfo *servinfo;
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof hints);
+#if TCP
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+ r = getaddrinfo(NULL, PORT, &hints, &servinfo);
+ assert(r == 0);
+#else
+ struct stat tstat;
+ if (lstat(SOCKFILE, &tstat) == 0) {
+ if (S_ISSOCK(tstat.st_mode))
+ unlink(SOCKFILE);
+ }
+
+ servinfo = malloc(sizeof(struct addrinfo));
+ servinfo->ai_family = AF_UNIX;
+ servinfo->ai_socktype = SOCK_STREAM;
+ servinfo->ai_protocol = 0;
+
+ struct sockaddr_un *sockaddr = calloc(sizeof(struct sockaddr_un), 1);
+ sockaddr->sun_family = AF_UNIX;
+ strcpy(sockaddr->sun_path, SOCKFILE);
+
+ servinfo->ai_addr = (struct sockaddr*)sockaddr;
+ servinfo->ai_addrlen = sizeof(struct sockaddr_un);
+#endif
+
+ oi_server_listen(&server, servinfo);
+ oi_server_attach(&server, loop);
+
+ int i;
+ for(i = 0; i < NCONN; i++) {
+ oi_socket *client = malloc(sizeof(oi_socket));
+ oi_socket_init(client, TIMEOUT);
+ client->on_read = on_client_read;
+ client->on_error = on_client_error;
+ client->on_connect = on_client_connect;
+ client->on_close = on_client_close;
+ client->on_timeout = on_client_timeout;
+#if HAVE_GNUTLS
+#if SECURE
+ anon_tls_client(client);
+#endif
+#endif
+ r = oi_socket_connect(client, servinfo);
+ assert(r == 0 && "problem connecting");
+ oi_socket_attach(client, loop);
+ }
+
+ ev_loop(loop, 0);
+
+ assert(nconnections == NCONN);
+
+#if TCP
+ freeaddrinfo(servinfo);
+#endif
+
+ return 0;
+}
diff --git a/deps/liboi/test/echo.c b/deps/liboi/test/echo.c
new file mode 100644
index 0000000000..c68b94cf34
--- /dev/null
+++ b/deps/liboi/test/echo.c
@@ -0,0 +1,102 @@
+#include "test/common.c"
+
+int successful_ping_count;
+
+static void
+on_peer_read(oi_socket *socket, const void *base, size_t len)
+{
+ if(len == 0)
+ return;
+
+ oi_socket_write_simple(socket, base, len);
+}
+
+static void
+on_peer_error(oi_socket *socket, struct oi_error e)
+{
+ assert(0);
+}
+
+static oi_socket*
+on_server_connection(oi_server *server, struct sockaddr *addr, socklen_t len)
+{
+ oi_socket *socket = malloc(sizeof(oi_socket));
+ oi_socket_init(socket, 5.0);
+ socket->on_read = on_peer_read;
+ socket->on_error = on_peer_error;
+ socket->on_close = on_peer_close;
+ socket->on_timeout = on_peer_timeout;
+
+ nconnections++;
+
+#if HAVE_GNUTLS
+# if SECURE
+ anon_tls_server(socket);
+# endif
+#endif
+
+ //printf("on server connection\n");
+
+ return socket;
+}
+
+int
+main(int argc, const char *argv[])
+{
+ int r;
+ struct ev_loop *loop = ev_default_loop(0);
+ oi_server server;
+ oi_socket client;
+
+ //printf("sizeof(oi_server): %d\n", sizeof(oi_server));
+ //printf("sizeof(oi_socket): %d\n", sizeof(oi_socket));
+
+ oi_server_init(&server, 10);
+ server.on_connection = on_server_connection;
+
+#if HAVE_GNUTLS
+# if SECURE
+ anon_tls_init();
+# endif
+#endif
+
+ struct addrinfo *servinfo;
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof hints);
+#if TCP
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+ r = getaddrinfo(NULL, PORT, &hints, &servinfo);
+ assert(r == 0);
+#else
+ struct stat tstat;
+ if (lstat(SOCKFILE, &tstat) == 0) {
+ if (S_ISSOCK(tstat.st_mode))
+ unlink(SOCKFILE);
+ }
+
+ servinfo = malloc(sizeof(struct addrinfo));
+ servinfo->ai_family = AF_UNIX;
+ servinfo->ai_socktype = SOCK_STREAM;
+ servinfo->ai_protocol = 0;
+
+ struct sockaddr_un *sockaddr = calloc(sizeof(struct sockaddr_un), 1);
+ sockaddr->sun_family = AF_UNIX;
+ strcpy(sockaddr->sun_path, SOCKFILE);
+
+ servinfo->ai_addr = (struct sockaddr*)sockaddr;
+ servinfo->ai_addrlen = sizeof(struct sockaddr_un);
+#endif
+ r = oi_server_listen(&server, servinfo);
+ assert(r == 0);
+ oi_server_attach(&server, loop);
+
+ ev_loop(loop, 0);
+
+#if TCP
+ freeaddrinfo(servinfo);
+#endif
+
+ return 0;
+}
diff --git a/deps/liboi/test/fancy_copy.c b/deps/liboi/test/fancy_copy.c
new file mode 100644
index 0000000000..f5d2ba7194
--- /dev/null
+++ b/deps/liboi/test/fancy_copy.c
@@ -0,0 +1,223 @@
+/* This test uses most of oi's facilities. It starts by opening a new file
+ * and writing N characters to it. Once the first chunk is written, it opens
+ * that file with another handle
+ *
+ * /tmp/oi_test_src == /tmp/oi_test_dst
+ * | ^
+ * | |
+ * stream | | write
+ * | |
+ * V |
+ * client ----------> server/connection
+ */
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#ifndef TRUE
+# define TRUE 1
+#endif
+#ifndef FALSE
+# define FALSE 0
+#endif
+
+#include
+#include
+
+#define PORT "5555"
+
+static struct ev_loop *loop;
+static oi_file file_src;
+static oi_file file_dst;
+static oi_socket client;
+static oi_server server;
+static oi_socket connection;
+static int got_connection = 0;
+
+static char *src_filename;
+static char *dst_filename;
+
+static void
+file_error(oi_file *_)
+{
+ assert(0);
+}
+
+static void
+on_file_dst_open (oi_file *_)
+{
+ assert(connection.fd > 0);
+ oi_socket_read_start(&connection);
+ //printf("file_dst is open\n");
+}
+
+static void
+on_file_dst_close (oi_file *_)
+{
+ //printf("file dst closed\n");
+ oi_file_detach(&file_dst);
+}
+
+static void
+on_connection_read (oi_socket *_, const void *buf, size_t count)
+{
+ assert(file_dst.fd > 0);
+ if(count == 0) {
+ file_dst.on_drain = oi_file_close;
+ oi_socket_close(&connection);
+ } else {
+ oi_file_write_simple(&file_dst, buf, count);
+ }
+}
+
+static void
+on_connection_connect (oi_socket *_)
+{
+ oi_file_init(&file_dst);
+ file_dst.on_open = on_file_dst_open;
+ file_dst.on_close = on_file_dst_close;
+ oi_file_open_path(&file_dst, dst_filename, O_WRONLY | O_CREAT, 0644);
+ oi_file_attach(&file_dst, loop);
+
+ oi_socket_read_stop(&connection);
+}
+
+static void
+on_connection_timeout (oi_socket *_)
+{
+ assert(0);
+}
+
+static void
+on_connection_error (oi_socket *_, struct oi_error e)
+{
+ assert(0);
+}
+
+static void
+on_connection_close (oi_socket *_)
+{
+ oi_server_close(&server);
+ oi_server_detach(&server);
+}
+
+static oi_socket*
+on_server_connection(oi_server *_, struct sockaddr *addr, socklen_t len)
+{
+ assert(got_connection == FALSE);
+ oi_socket_init(&connection, 5.0);
+ connection.on_connect = on_connection_connect;
+ connection.on_read = on_connection_read;
+ connection.on_error = on_connection_error;
+ connection.on_close = on_connection_close;
+ connection.on_timeout = on_connection_timeout;
+ connection.on_drain = oi_socket_close;
+ got_connection = TRUE;
+ //printf("on server connection\n");
+ return &connection;
+}
+
+static void
+on_client_read (oi_socket *_, const void *buf, size_t count)
+{
+ assert(0);
+}
+
+static void
+on_client_error (oi_socket *_, struct oi_error e)
+{
+ assert(0);
+}
+
+static void
+on_client_connect (oi_socket *_)
+{
+ if(file_src.fd > 0) {
+ oi_file_send(&file_src, &client, 0, 50*1024);
+ }
+}
+
+static void
+on_client_drain (oi_socket *_)
+{
+ oi_socket_close(&client);
+ oi_file_close(&file_src);
+}
+
+static void
+on_file_src_open (oi_file *_)
+{
+ if(client.fd > 0) {
+ oi_file_send(&file_src, &client, 0, 50*1024);
+ }
+}
+
+static void
+on_client_timeout (oi_socket *_)
+{
+ assert(0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ int r;
+ loop = ev_default_loop(0);
+
+ assert(argc == 3);
+ src_filename = argv[1];
+ dst_filename = argv[2];
+
+ assert(strlen(src_filename) > 0);
+ assert(strlen(dst_filename) > 0);
+
+ oi_server_init(&server, 10);
+ server.on_connection = on_server_connection;
+
+ 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;
+ r = getaddrinfo(NULL, PORT, &hints, &servinfo);
+ assert(r == 0);
+
+ r = oi_server_listen(&server, servinfo);
+ assert(r >= 0 && "problem listening");
+ oi_server_attach(&server, loop);
+
+ oi_socket_init(&client, 5.0);
+ client.on_read = on_client_read;
+ client.on_error = on_client_error;
+ client.on_connect = on_client_connect;
+ client.on_timeout = on_client_timeout;
+ //client.on_close = oi_socket_detach;
+ client.on_drain = on_client_drain;
+ r = oi_socket_connect(&client, servinfo);
+ assert(r == 0 && "problem connecting");
+ oi_socket_attach(&client, loop);
+
+ oi_file_init(&file_src);
+ file_src.on_open = on_file_src_open;
+ file_src.on_drain = file_error;
+ file_src.on_close = oi_file_detach;
+ oi_file_open_path(&file_src, src_filename, O_RDONLY, 0700);
+ oi_file_attach(&file_src, loop);
+
+ ev_loop(loop, 0);
+
+ printf("\noi_file: %d bytes\n", sizeof(oi_file));
+ printf("oi_socket: %d bytes\n", sizeof(oi_socket));
+
+ return 0;
+}
+
diff --git a/deps/liboi/test/ping_pong.c b/deps/liboi/test/ping_pong.c
new file mode 100644
index 0000000000..93199eaa42
--- /dev/null
+++ b/deps/liboi/test/ping_pong.c
@@ -0,0 +1,165 @@
+#include "test/common.c"
+
+#define PING "PING"
+#define PONG "PONG"
+#define EXCHANGES 100
+
+int successful_ping_count;
+
+static void
+on_peer_read(oi_socket *socket, const void *base, size_t len)
+{
+ if(len == 0)
+ return;
+
+ char buf[2000];
+ strncpy(buf, base, len);
+ buf[len] = 0;
+ //printf("server got message: %s\n", buf);
+
+ oi_socket_write_simple(socket, PONG, sizeof PONG);
+}
+
+static void
+on_peer_error(oi_socket *socket, struct oi_error e)
+{
+ assert(0);
+}
+
+static void
+on_client_close(oi_socket *socket)
+{
+ //printf("client connection closed\n");
+ ev_unloop(socket->loop, EVUNLOOP_ALL);
+}
+
+static oi_socket*
+on_server_connection(oi_server *server, struct sockaddr *addr, socklen_t len)
+{
+ oi_socket *socket = malloc(sizeof(oi_socket));
+ oi_socket_init(socket, 5.0);
+ socket->on_read = on_peer_read;
+ socket->on_error = on_peer_error;
+ socket->on_close = on_peer_close;
+ socket->on_timeout = on_peer_timeout;
+
+ nconnections++;
+
+#if HAVE_GNUTLS
+# if SECURE
+ anon_tls_server(socket);
+# endif
+#endif
+
+ //printf("on server connection\n");
+
+ return socket;
+}
+
+static void
+on_client_connect(oi_socket *socket)
+{
+ //printf("client connected. sending ping\n");
+ oi_socket_write_simple(socket, PING, sizeof PING);
+}
+
+static void
+on_client_read(oi_socket *socket, const void *base, size_t len)
+{
+ char buf[200000];
+ strncpy(buf, base, len);
+ buf[len] = 0;
+ //printf("client got message: %s\n", buf);
+
+ if(strcmp(buf, PONG) == 0) {
+
+ if(++successful_ping_count > EXCHANGES) {
+ oi_socket_close(socket);
+ return;
+ }
+ oi_socket_write_simple(socket, PING, sizeof PING);
+ } else {
+ assert(0);
+ }
+}
+
+int
+main(int argc, const char *argv[])
+{
+ int r;
+ struct ev_loop *loop = ev_default_loop(0);
+ oi_server server;
+ oi_socket client;
+
+ //printf("sizeof(oi_server): %d\n", sizeof(oi_server));
+ //printf("sizeof(oi_socket): %d\n", sizeof(oi_socket));
+
+ oi_server_init(&server, 10);
+ server.on_connection = on_server_connection;
+
+#if HAVE_GNUTLS
+# if SECURE
+ anon_tls_init();
+# endif
+#endif
+
+ struct addrinfo *servinfo;
+ struct addrinfo hints;
+ memset(&hints, 0, sizeof hints);
+#if TCP
+ hints.ai_family = AF_UNSPEC;
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_PASSIVE;
+ r = getaddrinfo(NULL, PORT, &hints, &servinfo);
+ assert(r == 0);
+#else
+ struct stat tstat;
+ if (lstat(SOCKFILE, &tstat) == 0) {
+ if (S_ISSOCK(tstat.st_mode))
+ unlink(SOCKFILE);
+ }
+
+ servinfo = malloc(sizeof(struct addrinfo));
+ servinfo->ai_family = AF_UNIX;
+ servinfo->ai_socktype = SOCK_STREAM;
+ servinfo->ai_protocol = 0;
+
+ struct sockaddr_un *sockaddr = calloc(sizeof(struct sockaddr_un), 1);
+ sockaddr->sun_family = AF_UNIX;
+ strcpy(sockaddr->sun_path, SOCKFILE);
+
+ servinfo->ai_addr = (struct sockaddr*)sockaddr;
+ servinfo->ai_addrlen = sizeof(struct sockaddr_un);
+#endif
+ r = oi_server_listen(&server, servinfo);
+ assert(r == 0);
+ oi_server_attach(&server, loop);
+
+ oi_socket_init(&client, 5.0);
+ client.on_read = on_client_read;
+ client.on_error = on_client_error;
+ client.on_connect = on_client_connect;
+ client.on_close = on_client_close;
+ client.on_timeout = on_client_timeout;
+
+#if HAVE_GNUTLS
+# if SECURE
+ anon_tls_client(&client);
+# endif
+#endif
+
+ r = oi_socket_connect(&client, servinfo);
+ assert(r == 0 && "problem connecting");
+ oi_socket_attach(&client, loop);
+
+ ev_loop(loop, 0);
+
+ assert(successful_ping_count == EXCHANGES + 1);
+ assert(nconnections == 1);
+
+#if TCP
+ freeaddrinfo(servinfo);
+#endif
+
+ return 0;
+}
diff --git a/deps/liboi/test/sleeping_tasks.c b/deps/liboi/test/sleeping_tasks.c
new file mode 100644
index 0000000000..d9aeadb799
--- /dev/null
+++ b/deps/liboi/test/sleeping_tasks.c
@@ -0,0 +1,54 @@
+#include /* sleep() */
+#include /* malloc(), free() */
+#include
+#include
+#include
+
+#define SLEEPS 4
+static int runs = 0;
+
+static void
+done (oi_task *task, unsigned int result)
+{
+ assert(result == 0);
+ if(++runs == SLEEPS) {
+ ev_timer *timer = task->data;
+ ev_timer_stop(task->async->loop, timer);
+ oi_async_detach(task->async);
+ }
+ free(task);
+}
+
+static void
+on_timeout(struct ev_loop *loop, ev_timer *w, int events)
+{
+ assert(0 && "timeout before all sleeping tasks were complete!");
+}
+
+int
+main()
+{
+ struct ev_loop *loop = ev_default_loop(0);
+ oi_async async;
+ ev_timer timer;
+ int i;
+
+ oi_async_init(&async);
+ for(i = 0; i < SLEEPS; i++) {
+ oi_task *task = malloc(sizeof(oi_task));
+ oi_task_init_sleep(task, done, 1);
+ task->data = &timer;
+ oi_async_submit(&async, task);
+ }
+ oi_async_attach(loop, &async);
+
+
+ ev_timer_init (&timer, on_timeout, 1.2, 0.);
+ ev_timer_start (loop, &timer);
+
+ ev_loop(loop, 0);
+
+ assert(runs == SLEEPS);
+
+ return 0;
+}
diff --git a/deps/liboi/test/stdout.c b/deps/liboi/test/stdout.c
new file mode 100644
index 0000000000..1bb2469406
--- /dev/null
+++ b/deps/liboi/test/stdout.c
@@ -0,0 +1,64 @@
+#include /* sleep() */
+#include /* malloc(), free() */
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+
+static oi_file file;
+static oi_file out;
+
+#define READ_BUFSIZE (5)
+static char read_buf[READ_BUFSIZE];
+# define SEP "~~~~~~~~~~~~~~~~~~~~~~\n"
+
+static void
+on_open(oi_file *f)
+{
+ oi_file_write_simple(&out, "\n", 1);
+ oi_file_write_simple(&out, SEP, sizeof(SEP));
+ oi_file_read_start(f, read_buf, READ_BUFSIZE);
+}
+
+static void
+on_close(oi_file *f)
+{
+ oi_file_write_simple(&out, SEP, sizeof(SEP));
+ oi_file_detach(f);
+ out.on_drain = oi_file_detach;
+}
+
+static void
+on_read(oi_file *f, size_t recved)
+{
+ if(recved == 0) /* EOF */
+ oi_file_close(f);
+ else
+ oi_file_write_simple(&out, read_buf, recved);
+}
+
+int
+main()
+{
+ struct ev_loop *loop = ev_default_loop(0);
+
+ oi_file_init(&file);
+ file.on_open = on_open;
+ file.on_read = on_read;
+ file.on_close = on_close;
+ oi_file_open_path(&file, "LICENSE", O_RDONLY, 0);
+ oi_file_attach(&file, loop);
+
+ oi_file_init(&out);
+ oi_file_open_stdout(&out);
+ oi_file_attach(&out, loop);
+
+ ev_loop(loop, 0);
+ return 0;
+}
diff --git a/deps/liboi/test/timeout.rb b/deps/liboi/test/timeout.rb
new file mode 100755
index 0000000000..dd1dd729ed
--- /dev/null
+++ b/deps/liboi/test/timeout.rb
@@ -0,0 +1,96 @@
+#!/usr/bin/env ruby
+
+def test(description)
+ pid = fork do
+ exec(File.dirname(__FILE__) + "/echo")
+ end
+
+ begin
+ sleep 0.5 # give time for the server to start
+ yield(pid)
+ rescue
+ puts "\033[1;31mFAIL\033[m: #{description}"
+ raise $!
+ ensure
+ `kill -9 #{pid}`
+ end
+ puts "\033[1;32mPASS\033[m: #{description}"
+end
+
+test("make sure echo server works") do
+ socket = TCPSocket.open("localhost", 5000)
+ w = socket.write("hello");
+ raise "error" unless w == 5
+
+ got = socket.recv(5);
+ raise "error" unless got == "hello"
+
+ socket.close
+end
+
+test("doing nothing should not timeout the server") do |pid|
+ 10.times do
+ print "."
+ STDOUT.flush
+ if Process.waitpid(pid, Process::WNOHANG)
+ raise "server died when it shouldn't have"
+ end
+ sleep 1
+ end
+ puts ""
+end
+
+test("connecting and doing nothing to should timeout in 5 seconds") do |pid|
+ socket = TCPSocket.open("localhost", 5000)
+ i = 0
+ 10.times do
+ print "."
+ STDOUT.flush
+ break if Process.waitpid(pid, Process::WNOHANG)
+ sleep 1
+ i+=1
+ end
+ puts ""
+ raise "died too soon (after #{i} seconds)" if i < 5
+ raise "died too late (after #{i} seconds)" if i > 6
+end
+
+
+test("connecting and writing once to should timeout in 5 seconds") do |pid|
+ socket = TCPSocket.open("localhost", 5000)
+ w = socket.write("hello");
+ raise "error" unless w == 5
+
+ i = 0
+ 10.times do
+ print "."
+ STDOUT.flush
+ break if Process.waitpid(pid, Process::WNOHANG)
+ sleep 1
+ i+=1
+ end
+ puts ""
+ raise "died too soon (after #{i} seconds)" if i < 5
+ raise "died too late (after #{i} seconds)" if i > 6
+end
+
+test("connecting waiting 3, writing once to should timeout in 8 seconds") do |pid|
+ socket = TCPSocket.open("localhost", 5000)
+
+ sleep 3
+
+ w = socket.write("hello");
+ raise "error" unless w == 5
+
+ i = 0
+ 10.times do
+ print "."
+ STDOUT.flush
+ break if Process.waitpid(pid, Process::WNOHANG)
+ sleep 1
+ i+=1
+ end
+ puts ""
+ raise "died too soon (after #{i} seconds)" if i < 5
+ raise "died too late (after #{i} seconds)" if i > 6
+end
diff --git a/deps/v8/AUTHORS b/deps/v8/AUTHORS
new file mode 100644
index 0000000000..060393f645
--- /dev/null
+++ b/deps/v8/AUTHORS
@@ -0,0 +1,15 @@
+# Below is a list of people and organizations that have contributed
+# to the V8 project. Names should be added to the list like so:
+#
+# Name/Organization
+
+Google Inc.
+
+Rene Rebe
+Rafal Krypa
+Jay Freeman
+Daniel James
+Paolo Giarrusso
+Daniel Andersson
+Alexander Botero-Lowry
+Matt Hanselman
diff --git a/deps/v8/ChangeLog b/deps/v8/ChangeLog
new file mode 100644
index 0000000000..d5c30c7f5e
--- /dev/null
+++ b/deps/v8/ChangeLog
@@ -0,0 +1,816 @@
+2009-04-15: Version 1.1.10
+
+ Fixed crash bug that occurred when loading a const variable in the
+ presence of eval.
+
+ Allowed using with and eval in registered extensions in debug mode
+ by fixing bogus assert.
+
+ Fixed the source position for function returns to enable the
+ debugger to break there.
+
+
+2009-04-14: Version 1.1.9
+
+ Made the stack traversal code in the profiler robust by avoiding
+ to look into the heap.
+
+ Added name inferencing for anonymous functions to facilitate
+ debugging and profiling.
+
+ Re-enabled stats timers in the developer shell (d8).
+
+ Fixed issue 303 by avoiding to shortcut cons-symbols.
+
+
+2009-04-11: Version 1.1.8
+
+ Changed test-debug/ThreadedDebugging to be non-flaky (issue 96).
+
+ Fixed step-in handling for Function.prototype.apply and call in
+ the debugger (issue 269).
+
+ Fixed v8::Object::DeleteHiddenValue to not bail out when there
+ are no hidden properties.
+
+ Added workaround for crash bug, where external symbol table
+ entries with deleted resources would lead to NPEs when looking
+ up in the symbol table.
+
+
+2009-04-07: Version 1.1.7
+
+ Added support for easily importing additional environment
+ variables into the SCons build.
+
+ Optimized strict equality checks.
+
+ Fixed crash in indexed setters on objects without a corresponding
+ getter (issue 298).
+
+ Re-enabled script compilation cache.
+
+
+2009-04-01: Version 1.1.6
+
+ Reverted an unsafe code generator change.
+
+
+2009-04-01: Version 1.1.5
+
+ Fixed bug that caused function literals to not be optimized as
+ much as other functions.
+
+ Improved profiler support.
+
+ Fixed a crash bug in connection with debugger unloading.
+
+ Fixed a crash bug in the code generator caused by losing the
+ information that a frame element was copied.
+
+ Fixed an exception propagation bug that could cause non-null
+ return values when exceptions were thrown.
+
+
+2009-03-30: Version 1.1.4
+
+ Optimized String.prototype.match.
+
+ Improved the stack information in profiles.
+
+ Fixed bug in ARM port making it possible to compile the runtime
+ system for thumb mode again.
+
+ Implemented a number of optimizations in the code generator.
+
+ Fixed a number of memory leaks in tests.
+
+ Fixed crash bug in connection with script source code and external
+ strings.
+
+
+2009-03-24: Version 1.1.3
+
+ Fixed assertion failures in compilation of loop conditions.
+
+ Removed STL dependency from developer shell (d8).
+
+ Added infrastructure for protecting the V8 heap from corruption
+ caused by memory modifications from the outside.
+
+
+2009-03-24: Version 1.1.2
+
+ Improved frame merge code generated by the code generator.
+
+ Optimized String.prototype.replace.
+
+ Implemented __defineGetter__ and __defineSetter__ for properties
+ with integer keys on non-array objects.
+
+ Improved debugger and profiler support.
+
+ Fixed a number of portability issues to allow compilation for
+ smaller ARM devices.
+
+ Exposed object cloning through the API.
+
+ Implemented hidden properties. This is used to expose an identity
+ hash for objects through the API.
+
+ Implemented restarting of regular expressions if their input
+ string changes representation during preemption.
+
+ Fixed a code generator bug that could cause assignments in loops
+ to be ignored if using continue to break out of the loop (issue
+ 284).
+
+
+2009-03-12: Version 1.1.1
+
+ Fixed an assertion in the new compiler to take stack overflow
+ exceptions into account.
+
+ Removed exception propagation code that could cause crashes.
+
+ Fixed minor bug in debugger line number computations.
+
+ 8-byte align the C stack on Linux and Windows to speed up floating
+ point computations.
+
+
+2009-03-12: Version 1.1.0
+
+ Improved code generation infrastructure by doing simple register
+ allocation and constant folding and propagation.
+
+ Optimized regular expression matching by avoiding to create
+ intermediate string arrays and by flattening nested array
+ representations of RegExp data.
+
+ Traverse a few stack frames when recording profiler samples to
+ include partial call graphs in the profiling output.
+
+ Added support for using OProfile to profile generated code.
+
+ Added remote debugging support to the D8 developer shell.
+
+ Optimized creation of nested literals like JSON objects.
+
+ Fixed a bug in garbage collecting unused maps and turned it on by
+ default (--collect-maps).
+
+ Added support for running tests under Valgrind.
+
+
+2009-02-27: Version 1.0.3
+
+ Optimized double-to-integer conversions in bit operations by using
+ SSE3 instructions if available.
+
+ Optimized initialization sequences that store to multiple
+ properties of the same object.
+
+ Changed the D8 debugger frontend to use JSON messages.
+
+ Force garbage collections when disposing contexts.
+
+ Align code objects at 32-byte boundaries.
+
+
+2009-02-25: Version 1.0.2
+
+ Improved profiling support by performing simple call stack
+ sampling for ticks and by fixing a bug in the logging of code
+ addresses.
+
+ Fixed a number of debugger issues.
+
+ Optimized code that uses eval.
+
+ Fixed a couple of bugs in the regular expression engine.
+
+ Reduced the size of generated code for certain regular expressions.
+
+ Removed JSCRE completely.
+
+ Fixed issue where test could not be run if there was a dot in the
+ checkout path.
+
+
+2009-02-13: Version 1.0.1
+
+ Fixed two crash-bugs in irregexp (issue 231 and 233).
+
+ Fixed a number of minor bugs (issue 87, 227 and 228).
+
+ Added support for morphing strings to external strings on demand
+ to avoid having to create copies in the embedding code.
+
+ Removed experimental support for external symbol callbacks.
+
+
+2009-02-09: Version 1.0.0
+
+ Fixed crash-bug in the code generation for case independent 16 bit
+ backreferences.
+
+ Made shells more robust in the presence of string conversion
+ failures (issue 224).
+
+ Fixed a potential infinite loop when attempting to resolve
+ eval (issue 221).
+
+ Miscellaneous fixes to the new regular expression engine.
+
+ Reduced binary by stripping unneeded text from JavaScript library and
+ minifying some JavaScript files.
+
+
+2009-01-27: Version 0.4.9
+
+ Enabled new regular expression engine.
+
+ Made a number of changes to the debugger protocol.
+
+ Fixed a number of bugs in the preemption support.
+
+ Added -p option to the developer shell to run files in parallel
+ using preemption.
+
+ Fixed a number of minor bugs (including issues 176, 187, 189, 192,
+ 193, 198 and 201).
+
+ Fixed a number of bugs in the serialization/deserialization
+ support for the ARM platform.
+
+
+2009-01-19: Version 0.4.8.1
+
+ Minor patch to debugger support.
+
+
+2009-01-16: Version 0.4.8
+
+ Fixed string length bug on ARM (issue 171).
+
+ Made most methods in the API const.
+
+ Optimized object literals by improving data locality.
+
+ Fixed bug that caused incomplete functions to be cached in case of
+ stack overflow exceptions.
+
+ Fixed bugs that caused catch variables and variables introduced by
+ eval to behave incorrectly when using accessors (issues 186, 190
+ and 191).
+
+
+2009-01-06: Version 0.4.7
+
+ Minor bugfixes and optimizations.
+
+ Added command line debugger to D8 shell.
+
+ Fixed subtle bug that caused the wrong 'this' to be used when
+ calling a caught function in a catch clause.
+
+ Inline array loads within loops directly in the code instead of
+ always calling a stub.
+
+
+2008-12-11: Version 0.4.6
+
+ Fixed exception reporting bug where certain exceptions were
+ incorrectly reported as uncaught.
+
+ Improved the memory allocation strategy used during compilation to
+ make running out of memory when compiling huge scripts less
+ likely.
+
+ Optimized String.replace by avoiding the construction of certain
+ sub strings.
+
+ Fixed bug in code generation for large switch statements on ARM.
+
+ Fixed bug that caused V8 to change the global object template
+ passed in by the user.
+
+ Changed the API for creating object groups used during garbage
+ collection. Entire object groups are now passed to V8 instead of
+ individual members of the groups.
+
+
+2008-12-03: Version 0.4.5
+
+ Added experimental API support for allocating V8 symbols as
+ external strings.
+
+ Fixed bugs in debugging support on ARM.
+
+ Changed eval implementation to correctly detect whether or not a
+ call to eval is aliased.
+
+ Fixed bug caused by a combination of the compilation cache and
+ dictionary probing in native code. The bug caused us to sometimes
+ call functions that had not yet been compiled.
+
+ Added platform support for FreeBSD.
+
+ Added support for building V8 on Windows with either the shared or
+ static version of MSVCRT
+
+ Added the v8::jscre namespace around the jscre functions to avoid
+ link errors (duplicate symbols) when building Google Chrome.
+
+ Added support for calling a JavaScript function with the current
+ debugger execution context as its argument to the debugger
+ interface.
+
+ Changed the type of names of counters from wchar_t to char.
+
+ Changed the Windows system call used to compute daylight savings
+ time. The system call that we used to use became four times
+ slower on WinXP SP3.
+
+ Added support in the d8 developer shell for memory-mapped counters
+ and added a stats-viewer tool.
+
+ Fixed bug in upper/lower case mappings (issue 149).
+
+
+2008-11-17: Version 0.4.4
+
+ Reduced code size by using shorter instruction encoding when
+ possible.
+
+ Added a --help option to the shell sample and to the d8 shell.
+
+ Added visual studio project files for building the ARM simulator.
+
+ Fixed a number of ARM simulator issues.
+
+ Fixed bug in out-of-memory handling on ARM.
+
+ Implemented shell support for passing arguments to a script from
+ the command line.
+
+ Fixed bug in date code that made certain date functions return -0
+ instead of 0 for dates before the epoch.
+
+ Restricted applications of eval so it can only be used in the
+ context of the associated global object.
+
+ Treat byte-order marks as whitespace characters.
+
+
+2008-11-04: Version 0.4.3
+
+ Added support for API accessors that prohibit overwriting by
+ accessors defined in JavaScript code by using __defineGetter__ and
+ __defineSetter__.
+
+ Improved handling of conditionals in test status files.
+
+ Introduced access control in propertyIsEnumerable.
+
+ Improved performance of some string operations by caching
+ information about the type of the string between operations.
+
+ Fixed bug in fast-case code for switch statements that only have
+ integer labels.
+
+
+2008-10-30: Version 0.4.2
+
+ Improved performance of Array.prototype.concat by moving the
+ implementation to C++ (issue 123).
+
+ Fixed heap growth policy to avoid growing old space to its maximum
+ capacity before doing a garbage collection and fixed issue that
+ would lead to artificial out of memory situations (issue 129).
+
+ Fixed Date.prototype.toLocaleDateString to return the date in the
+ same format as WebKit.
+
+ Added missing initialization checks to debugger API.
+
+ Added removing of unused maps during GC.
+
+
+2008-10-28: Version 0.4.1
+
+ Added caching of RegExp data in compilation cache.
+
+ Added Visual Studio project file for d8 shell.
+
+ Fixed function call performance regression introduced in version
+ 0.4.0 when splitting the global object in two parts (issue 120).
+
+ Fixed issue 131 by checking for empty handles before throwing and
+ reporting exceptions.
+
+
+2008-10-23: Version 0.4.0
+
+ Split the global object into two parts: The state holding global
+ object and the global object proxy.
+
+ Fixed bug that affected the value of an assignment to an element
+ in certain cases (issue 116).
+
+ Added GetPropertyNames functionality (issue 33) and extra Date
+ functions (issue 77) to the API.
+
+ Changed WeakReferenceCallback to take a Persistent instead
+ of a Persistent