Merge branch 'release/0.3.7'
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/.gitignore
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6e83553..d8558f8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,12 +4,11 @@
 
 set(PROJECT_MAJOR_VERSION 0)
 set(PROJECT_MINOR_VERSION 3)
-set(PROJECT_PATCH_VERSION 6)
+set(PROJECT_PATCH_VERSION 7)
 
 set (PROJECT_VERSION ${PROJECT_MAJOR_VERSION}.${PROJECT_MINOR_VERSION}.${PROJECT_PATCH_VERSION})
 set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/CMakeModules)
 
-
 INCLUDE (CheckFunctionExists)
 INCLUDE (CheckIncludeFiles)
 INCLUDE (CheckTypeSize)
@@ -84,7 +83,10 @@
 
 OPTION(DISABLE_SSL "Disable ssl support" OFF)
 OPTION(DISABLE_EVTHR "Disable evthread support" OFF)
+OPTION(EXPAND_HTTP_PARSER_MACROS "Enable pre-processor expansion" OFF)
+
 SET(CMAKE_INCLUDE_CURRENT_DIR ON)
+SET(EXPAND_HTTP_PARSER_MACROS ON)
 
 include(BaseConfig)
 
@@ -96,11 +98,11 @@
 find_package(LibEvent)
 
 include_directories(
+	${CMAKE_CURRENT_SOURCE_DIR}/libhtparse
 	${CMAKE_CURRENT_BINARY_DIR}/oniguruma
 	${CMAKE_CURRENT_SOURCE_DIR}/oniguruma
 	${CMAKE_CURRENT_SOURCE_DIR}
   ${CMAKE_CURRENT_SOURCE_DIR}/evthr
-	${CMAKE_CURRENT_SOURCE_DIR}/http_parser
 )
 
 set(LIBEVHTP_EXTERNAL_LIBS ${LIBEVENT_LIBRARY} ${LIBEVENT_PTHREADS_LIBRARY} ${LIBEVENT_OPENSSL_LIBRARY})
@@ -114,32 +116,20 @@
 endif(NOT ${LIBEVENT_OPENSSL_FOUND})
 
 if (DISABLE_EVTHR)
-	set(LIBEVHTP_SOURCES http_parser/http_parser.c evhtp.c)
+	set(LIBEVHTP_SOURCES evhtp.c libhtparse/htparse.c)
 else()
-	set(LIBEVHTP_SOURCES evthr/evthr.c http_parser/http_parser.c evhtp.c)
+	set(LIBEVHTP_SOURCES evthr/evthr.c evhtp.c libhtparse/htparse.c)
 endif(DISABLE_EVTHR)
 
-
-#add_library(libevhtp STATIC ${LIBEVHTP_SOURCES} ${ONIG_SOURCES})
 add_library(libevhtp SHARED ${LIBEVHTP_SOURCES} ${ONIG_SOURCES})
+
 set_target_properties(libevhtp PROPERTIES OUTPUT_NAME "evhtp")
+
 install (TARGETS libevhtp DESTINATION lib)
 install (FILES evhtp.h DESTINATION include)
+install (FILES libhtparse/htparse.h DESTINATION include)
 install (FILES evthr/evthr.h DESTINATION include)
-install (FILES http_parser/http_parser.h DESTINATION include)
 
 add_executable(test test.c)
 target_link_libraries(test libevhtp ${LIBEVHTP_EXTERNAL_LIBS})
 
-add_executable(oniguruma_test_posix ${CMAKE_CURRENT_SOURCE_DIR}/oniguruma/sample/posix.c)
-add_executable(oniguruma_test_listcap ${CMAKE_CURRENT_SOURCE_DIR}/oniguruma/sample/listcap.c)
-add_executable(oniguruma_test_names ${CMAKE_CURRENT_SOURCE_DIR}/oniguruma/sample/names.c)
-add_executable(oniguruma_test_simple ${CMAKE_CURRENT_SOURCE_DIR}/oniguruma/sample/simple.c)
-add_executable(oniguruma_test_sql ${CMAKE_CURRENT_SOURCE_DIR}/oniguruma/sample/sql.c)
-add_executable(oniguruma_test_syntax ${CMAKE_CURRENT_SOURCE_DIR}/oniguruma/sample/syntax.c)
-target_link_libraries(oniguruma_test_posix libevhtp ${LIBEVHTP_EXTERNAL_LIBS})
-target_link_libraries(oniguruma_test_listcap libevhtp ${LIBEVHTP_EXTERNAL_LIBS})
-target_link_libraries(oniguruma_test_names libevhtp ${LIBEVHTP_EXTERNAL_LIBS})
-target_link_libraries(oniguruma_test_simple libevhtp ${LIBEVHTP_EXTERNAL_LIBS})
-target_link_libraries(oniguruma_test_sql libevhtp ${LIBEVHTP_EXTERNAL_LIBS})
-target_link_libraries(oniguruma_test_syntax libevhtp ${LIBEVHTP_EXTERNAL_LIBS})
diff --git a/CMakeModules/BaseConfig.cmake b/CMakeModules/BaseConfig.cmake
index 36a053b..2bef005 100644
--- a/CMakeModules/BaseConfig.cmake
+++ b/CMakeModules/BaseConfig.cmake
@@ -1,15 +1,15 @@
 if (CMAKE_COMPILER_IS_GNUCC)
 
-	set(RSN_BASE_C_FLAGS      "-Wall")
+	set(RSN_BASE_C_FLAGS      "-Wall -Wextra")
 	set(CMAKE_C_FLAGS         "${CMAKE_C_FLAGS} ${RSN_BASE_C_FLAGS} -DPROJECT_VERSION=\"${PROJECT_VERSION}\"")
 	set(CMAKE_C_FLAGS_DEBUG   "${CMAKE_C_FLAGS_DEBUG} ${RSN_BASE_C_FLAGS} -ggdb")
 	set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} ${RSN_BASE_C_FLAGS}") 
 
-	if(APPLE) 
+	if(APPLE)
 		set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wshorten-64-to-32 -D_BSD_SOURCE")
 	endif(APPLE)
 
-	if (UNIX) 
+	if (UNIX)
 		set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_BSD_SOURCE -D_POSIX_C_SOURCE=199309L")
 	endif(UNIX)
 
diff --git a/ChangeLog b/ChangeLog
index 1155af1..aad920f 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,77 @@
+v0.3.7
+ - Due to various problems http-parser (ry/http-parser) I have written my own
+   parser (derived and ispired from various nginx functions) (./libhtparse).
+
+ - Re-introduced the pre_accept_cb and post_accept_cb which deprecates the 
+      evhtp_set_connection_hooks (which would call a defined function immediately
+      after a connection was accepted, so it was confusing I think).
+
+      The new functions to set the pre/post accepts are:
+       * evhtp_set_pre_accept_cb(evhtp_t *, evhtp_pre_accept cb, void * arg);
+       * evhtp_set_post_accept_cb(evhtp_t *, evhtp_post_accept cb, void * arg);
+         - evhtp_pre_accept functions have the following attributes:
+           * int fd            : (the allocated file descriptor)
+           * struct sockaddr * : self-explanitory
+           * int slen          : size of sockaddr
+           * void * arg        : argument passed to evhtp_set_pre_accept_cb
+         - evhtp_post_accept functions have the following attributes:
+           * evhtp_conn_t *    : self explanitory
+           * void * arg        : argument passed to evhtp_set_post_accept_cb
+
+
+ - libevhtp now properly honors all return evhtp_res's from defined request hooks.
+   Meaning if you return something other than EVHTP_RES_OK, the proper error
+   handling is done with the exception of EVHTP_RES_PAUSE (see notes on pausing)
+
+ - Added [currently only half-working] methods to allow for suspending and
+   resuming parser execution and connection handling.
+
+   A hook which returns an evhtp_res value can now return "EVHTP_RES_PAUSE" which
+   informs libevhtp to stop all request processing on a connection. Alternatively
+   you can use the functions evhtp_request_pause(evhtp_request_t *);
+
+   You may also get a copy of the suspend/resume event timer directly via the
+   function evhtp_request_get_resume_ev()
+
+   To resume execution/processing one must call the function
+   evhtp_request_resume(evhtp_request_t *);
+
+    To completely disable this functionality you may call the function
+    evhtp_disable_pausing(evhtp_t *) before dropping into your event loop
+
+ - Removed unnecessary bufferevent_[enable|disable] calls, it was too tedious
+   to work with as it could cause shitty race-like conditions if the client
+   bufferevent was needed outside of the library.
+
+ - EVHTP_CLOSE_* flags have been renamed to EVHTP_FLAG_CLOSE_* and length
+   extended to 16 bits.
+
+ - added functionality to both set and get user-set htp_request args:
+   evhtp_request_set_cbargs()
+   evhtp_request_get_cbargs()
+
+ - added a hook which is called just prior to a evhtp_request_t being free()'d
+
+ - Added the ability to define per-callback hooks. Much like per-connection hooks, 
+   you can set various hooks which are called when a uri|regex (defined by the 
+   set_cb functions) was matched.
+
+   In order to do this properly, set_cb functions now return a evhtp_callback_t which
+   can be passed to the evhtp_set_callback_hook() functions.
+
+   For example:
+   evhtp_callback_t * cb = evhtp_set_cb(htp, "/derp", derp_cb, NULL);
+   evhtp_set_callback_hook(cb, EVHTP_HOOK_HDRS_READ, derp_hdrs_cb, NULL); 
+
+   In the case above once evhtp has found that the incoming request is destined
+   for the "/derp" specific callback, it will call "derp_hdrs_cb" after all
+   headers have been read.
+
+   These act just like normal per-connection hooks, but it should be noted that
+   if a per-callback hook has been enabled, the per-connection hook will be ignored
+   for that hook.
+
+ 
 v0.3.6
  - Removed submodule dependencies 
  - Added various evhtp_hdr functions
diff --git a/contrib/release_prep.sh b/contrib/release_prep.sh
index 75576c0..0a91b51 100755
--- a/contrib/release_prep.sh
+++ b/contrib/release_prep.sh
@@ -1,29 +1,29 @@
 #!/bin/bash
 # This shouldn't be used by anyone but me...kthx
 
-rm -rf http_parser/
+rm -rf libhtparse/
 rm -rf evthr/
 rm -rf oniguruma/
 
-rm -rf build/http-parser-latest*
+rm -rf build/libhtparse-latest*
 rm -rf build/libevthr-latest*
 rm -rf build/oniguruma-latest*
 
-cd build && wget http://ackers.net/packages/http-parser-latest.tar http://ackers.net/packages/libevthr-latest.tar http://ackers.net/packages/oniguruma-latest.tar
+cd build && wget http://ackers.net/packages/libhtparse-latest.tar http://ackers.net/packages/libevthr-latest.tar http://ackers.net/packages/oniguruma-latest.tar
 cd ..
 
-httparser_dirname=`tar --to-stdout -tf build/http-parser-latest.tar 2>&1 | head -n 1`
+httparser_dirname=`tar --to-stdout -tf build/libhtparse-latest.tar 2>&1 | head -n 1`
 libevthr_dirname=`tar --to-stdout -tf build/libevthr-latest.tar 2>&1 | head -n 1`
 oniguruma_dirname=`tar --to-stdout -tf build/oniguruma-latest.tar 2>&1 | head -n 1`
 
-tar -xf build/http-parser-latest.tar
+tar -xf build/libhtparse-latest.tar
 tar -xf build/libevthr-latest.tar
 tar -xf build/oniguruma-latest.tar
 
-mv $httparser_dirname http_parser
+mv $httparser_dirname libhtparse 
 mv $libevthr_dirname evthr
 mv $oniguruma_dirname oniguruma
 
-rm -rf build/http-parser-latest*
+rm -rf build/libhtparse-latest*
 rm -rf build/libevthr-latest*
 rm -rf build/oniguruma-latest*
diff --git a/evhtp.c b/evhtp.c
index 90f4424..f03680c 100644
--- a/evhtp.c
+++ b/evhtp.c
@@ -13,12 +13,12 @@
 #include <arpa/inet.h>
 #include <assert.h>
 
-#include "http_parser.h"
+#include "htparse.h"
 #include "onigposix.h"
 #include "evhtp.h"
 
-typedef struct evhtp_callback  evhtp_callback_t;
 typedef struct evhtp_callbacks evhtp_callbacks_t;
+typedef enum htp_parse_state   htp_parse_state;
 
 typedef void (*htp_conn_write_fini_cb)(evhtp_conn_t * conn, void * args);
 
@@ -43,18 +43,31 @@
 #endif
 
 struct evhtp {
-    evbase_t           * evbase;
-    evserv_t           * listener;
-    evhtp_callbacks_t  * callbacks;
-    void               * default_cbarg;
-    void               * post_accept_cbarg;
-    char               * server_name;
-    evhtp_callback_cb    default_cb;
-    evhtp_post_accept    post_accept_cb;
-    http_parser_settings psets;
-    evhtp_ssl_ctx_t    * ssl_ctx;
-    evhtp_ssl_cfg      * ssl_cfg;
-    evthr_pool_t       * pool;
+    evbase_t          * evbase;
+    evserv_t          * listener;
+    evhtp_callbacks_t * callbacks;
+    void              * default_cbarg;
+    void              * pre_accept_cbarg;
+    void              * post_accept_cbarg;
+    char              * server_name;
+    evhtp_callback_cb   default_cb;
+    evhtp_pre_accept    pre_accept_cb;
+    evhtp_post_accept   post_accept_cb;
+    htparse_hooks       psets;
+    evhtp_ssl_ctx_t   * ssl_ctx;
+    evhtp_ssl_cfg     * ssl_cfg;
+    evthr_pool_t      * pool;
+    char                suspend_enabled;
+};
+
+enum htp_parse_state {
+    htp_parse_s_nil = 0,
+    htp_parse_s_path,
+    htp_parse_s_query,
+    htp_parse_s_uri,
+    htp_parse_s_hdr_key,
+    htp_parse_s_hdr_val,
+    htp_parse_s_hdr_fin
 };
 
 struct evhtp_hooks {
@@ -64,6 +77,7 @@
     evhtp_hook_uri       _uri;
     evhtp_hook_read      _read;
     evhtp_hook_on_expect _on_expect;
+    evhtp_hook_finished  _fini;
 
     void * _hdr_cbargs;
     void * _hdrs_cbargs;
@@ -71,9 +85,12 @@
     void * _uri_cbargs;
     void * _read_cbargs;
     void * _on_expect_cbargs;
+    void * _fini_cbargs;
 };
 
 struct evhtp_request {
+    htp_parse_state   prev_state;
+    htp_parse_state   curr_state;
     char            * path;
     char            * uri;
     int               matched_soff;
@@ -86,6 +103,7 @@
     char              major;
     char              minor;
     char              chunked;
+    evhtp_hooks_t   * cb_hooks;
     evhtp_callback_cb cb;
     evhtp_stream_cb   stream_cb;
     void            * cbarg;
@@ -93,6 +111,13 @@
     evhtp_conn_t    * conn;
     evbuf_t         * buffer_in;
     evbuf_t         * buffer_out;
+
+    char ran_start : 1;
+    char ran_path  : 1;
+    char ran_query : 1;
+    char ran_uri   : 1;
+    char ran_hdr   : 1;
+    char ran_hdrs  : 1;
 };
 
 typedef enum {
@@ -105,6 +130,7 @@
     void             * cbarg;
     unsigned int       hash;
     evhtp_callback_cb  cb;
+    evhtp_hooks_t    * hooks;
     evhtp_callback_t * next;
 
     union {
@@ -124,14 +150,16 @@
     evhtp_t         * htp;
     evhtp_hooks_t   * hooks;
     evhtp_request_t * request;
-    http_parser     * parser;
+    htparser        * parser;
     int               sock;
-    evhtp_res         err;
+    evhtp_res         status;
     evhtp_cflags      flags;
     evbase_t        * evbase;
     evbev_t         * bev;
     evhtp_ssl_t     * ssl;
     evthr_t         * thr;
+
+    event_t * resume_ev;
 };
 
 #define _HTP_CONN       "Connection"
@@ -148,117 +176,210 @@
 #define _HTP_DEFCHUNKED "chunked"
 
 #ifdef HTP_DEBUG
-#define __QUOTE(x)                    # x
-#define  _QUOTE(x)                    __QUOTE(x)
+#define __QUOTE(x)                        # x
+#define  _QUOTE(x)                        __QUOTE(x)
+#define evhtp_debug_strlen(x)             strlen(x)
 
-#define evhtp_log_debug(fmt, ...)     do {                     \
-        fprintf(stdout, __FILE__ "[" _QUOTE(__LINE__) "] %s: " \
-                fmt "\n", __func__, ## __VA_ARGS__);           \
-        fflush(stdout);                                        \
+#define evhtp_log_debug(fmt, ...)         do {                                            \
+        time_t      t  = time(NULL);                                                      \
+        struct tm * dm = localtime(&t);                                                   \
+                                                                                          \
+        fprintf(stdout, "[%02d:%02d:%02d] " __FILE__ "[" _QUOTE(__LINE__) "]\t%-26s: "    \
+                fmt "\n", dm->tm_hour, dm->tm_min, dm->tm_sec, __func__, ## __VA_ARGS__); \
+        fflush(stdout);                                                                   \
 } while (0)
+
 #else
-#define evhtp_log_debug(fmt, ...)     do {} while (0)
+#define evhtp_debug_strlen(x)             0
+#define evhtp_log_debug(fmt, ...)         do {} while (0)
 #endif
 
-#define htp_conn_hook(c)              (c)->hooks
-#define htp_conn_has_hook(c, n)       (htp_conn_hook(c) && htp_conn_hook(c)->n)
-#define htp_conn_hook_cbarg(c, n)     htp_conn_hook(c)->n ## _cbargs
-#define htp_conn_hook_call(c, n, ...) htp_conn_hook(c)->n(c->request, __VA_ARGS__, htp_conn_hook_cbarg(c, n))
-#define htp_conn_hook_set(c, n, f, a) do { \
-        htp_conn_hook(c)->n       = f;     \
-        htp_conn_hook_cbarg(c, n) = a;     \
+/*
+ * FIXME:
+ *  - we should probably just create real functions instead of macros
+ *  - connection / callback hooks need to be standardized to reduce
+ *    code-duplication.
+ */
+#define htp_conn_hook(c)                  (c)->hooks
+#define htp_conn_has_hook(c, n)           (htp_conn_hook(c) && htp_conn_hook(c)->n)
+#define htp_conn_hook_cbarg(c, n)         htp_conn_hook(c)->n ## _cbargs
+
+#define htp_conn_hook_call(c, n, ...)     htp_conn_hook(c)->n(c->request, __VA_ARGS__, \
+                                                              htp_conn_hook_cbarg(c, n))
+
+#define htp_conn_hook_calln(c, n)         htp_conn_hook(c)->n(c->request, \
+                                                              htp_conn_hook_cbarg(c, n))
+#define htp_conn_hook_set(c, n, f, a)     do { \
+        htp_conn_hook(c)->n       = f;         \
+        htp_conn_hook_cbarg(c, n) = a;         \
 } while (0)
 
+#define htp_callback_hook(c)              (c)->hooks
+#define htp_callback_hook_cbarg(c, n)     htp_callback_hook(c)->n ## _cbargs
+
+#define htp_callback_hook_set(c, n, f, a) do { \
+        htp_callback_hook(c)->n       = f;     \
+        htp_callback_hook_cbarg(c, n) = a;     \
+} while (0)
+
+
+#define htp_conn_callback_hook(c)         ((c)->request)->cb_hooks
+#define htp_conn_callback_has_hook(c, n)  (htp_conn_callback_hook(c) && \
+                                           htp_conn_callback_hook(c)->n)
+
+#define htp_conn_callback_hook_cbarg(c, n) \
+    htp_conn_callback_hook(c)->n ## _cbargs
+
+#define htp_conn_callback_hook_call(c, n, ...)            \
+    htp_conn_callback_hook(c)->n(c->request, __VA_ARGS__, \
+                                 htp_conn_callback_hook_cbarg(c, n))
+
+#define htp_conn_callback_hook_calln(c, n)   \
+    htp_conn_callback_hook(c)->n(c->request, \
+                                 htp_conn_callback_hook_cbarg(c, n))
+
 #define CRLF "\r\n"
 
 static evhtp_proto        htp_proto(char major, char minor);
-static evhtp_callback_t * htp_callbacks_find_callback(evhtp_callbacks_t *, const char *);
 static evhtp_callback_t * htp_callbacks_find_callback_woffsets(evhtp_callbacks_t *, const char *, int *, int *);
 static void               htp_recv_cb(evbev_t * bev, void * arg);
 static void               htp_err_cb(evbev_t * bev, short events, void * arg);
 static evhtp_request_t  * htp_request_new(evhtp_conn_t * conn);
 
+static int                htp_run_post_accept(evhtp_t * htp, evhtp_conn_t * conn);
+static int                htp_run_pre_accept(evhtp_t * htp, int fd, struct sockaddr * s, int sl);
+
 static int                ssl_num_locks;
 static evhtp_mutex_t   ** ssl_locks;
 
 static evhtp_res
 htp_run_on_expect_hook(evhtp_conn_t * conn, const char * expt_val) {
-    evhtp_res status = EVHTP_RES_CONTINUE;
-
     evhtp_log_debug("enter");
 
-    if (htp_conn_has_hook(conn, _on_expect)) {
-        status = htp_conn_hook_call(conn, _on_expect, expt_val);
+    if (htp_conn_callback_has_hook(conn, _on_expect)) {
+        return htp_conn_callback_hook_call(conn, _on_expect, expt_val);
     }
 
-    return status;
+    if (htp_conn_has_hook(conn, _on_expect)) {
+        return htp_conn_hook_call(conn, _on_expect, expt_val);
+    }
+
+    return EVHTP_RES_CONTINUE;
 }
 
 static evhtp_res
 htp_run_hdr_hook(evhtp_conn_t * conn, evhtp_hdr_t * hdr) {
-    evhtp_res res = EVHTP_RES_OK;
-
     evhtp_log_debug("enter");
 
-    if (htp_conn_has_hook(conn, _hdr)) {
-        res = htp_conn_hook_call(conn, _hdr, hdr);
+    evhtp_log_debug("klen = %zu 3B=[%c%c%c], vlen = %zu 3B=[%c%c%c]",
+                    evhtp_debug_strlen(hdr->key),
+                    (evhtp_debug_strlen(hdr->key) > 0) ? hdr->key[0] : '0',
+                    (evhtp_debug_strlen(hdr->key) > 1) ? hdr->key[1] : '0',
+                    (evhtp_debug_strlen(hdr->key) > 2) ? hdr->key[2] : '0',
+                    evhtp_debug_strlen(hdr->val),
+                    (evhtp_debug_strlen(hdr->val) > 0) ? hdr->val[0] : '0',
+                    (evhtp_debug_strlen(hdr->val) > 1) ? hdr->val[1] : '0',
+                    (evhtp_debug_strlen(hdr->val) > 2) ? hdr->val[2] : '0');
+
+    if (htp_conn_callback_has_hook(conn, _hdr)) {
+        return htp_conn_callback_hook_call(conn, _hdr, hdr);
     }
 
-    return res;
+    if (htp_conn_has_hook(conn, _hdr)) {
+        return htp_conn_hook_call(conn, _hdr, hdr);
+    }
+
+    return EVHTP_RES_OK;
 }
 
 static evhtp_res
 htp_run_hdrs_hook(evhtp_conn_t * conn, evhtp_hdrs_t * hdrs) {
-    evhtp_res res = EVHTP_RES_OK;
-
     evhtp_log_debug("enter");
-    if (htp_conn_has_hook(conn, _hdrs)) {
-        res = htp_conn_hook_call(conn, _hdrs, hdrs);
+
+    if (conn->request->ran_hdrs) {
+        return EVHTP_RES_OK;
     }
 
-    return res;
+    conn->request->ran_hdrs = 1;
+
+    if (htp_conn_callback_has_hook(conn, _hdrs)) {
+        return htp_conn_callback_hook_call(conn, _hdrs, hdrs);
+    }
+
+    if (htp_conn_has_hook(conn, _hdrs)) {
+        return htp_conn_hook_call(conn, _hdrs, hdrs);
+    }
+
+    return EVHTP_RES_OK;
 }
 
 static evhtp_res
 htp_run_path_hook(evhtp_conn_t * conn, const char * path) {
-    evhtp_res res = EVHTP_RES_OK;
-
     evhtp_log_debug("enter");
 
-    if (htp_conn_has_hook(conn, _path)) {
-        res = htp_conn_hook_call(conn, _path, path);
+    if (conn->request->ran_path) {
+        return EVHTP_RES_OK;
     }
 
-    return res;
+    conn->request->ran_path = 1;
+
+    if (htp_conn_has_hook(conn, _path)) {
+        return htp_conn_hook_call(conn, _path, path);
+    }
+
+    return EVHTP_RES_OK;
 }
 
 static evhtp_res
 htp_run_uri_hook(evhtp_conn_t * conn, const char * uri) {
-    evhtp_res res = EVHTP_RES_OK;
-
     evhtp_log_debug("enter");
-    if (htp_conn_has_hook(conn, _uri)) {
-        res = htp_conn_hook_call(conn, _uri, uri);
+
+    if (conn->request->ran_uri) {
+        return EVHTP_RES_OK;
     }
 
-    return res;
+    conn->request->ran_uri = 1;
+
+    if (htp_conn_has_hook(conn, _uri)) {
+        return htp_conn_hook_call(conn, _uri, uri);
+    }
+
+    return EVHTP_RES_OK;
 }
 
 static evhtp_res
 htp_run_read_hook(evhtp_conn_t * conn, const char * data, size_t sz) {
-    evhtp_res res = EVHTP_RES_OK;
-
     evhtp_log_debug("enter");
-    if (htp_conn_has_hook(conn, _read)) {
-        res = htp_conn_hook_call(conn, _read, data, sz);
+
+    if (htp_conn_callback_has_hook(conn, _read)) {
+        return htp_conn_callback_hook_call(conn, _read, data, sz);
     }
 
-    return res;
+    if (htp_conn_has_hook(conn, _read)) {
+        return htp_conn_hook_call(conn, _read, data, sz);
+    }
+
+    return EVHTP_RES_OK;
+}
+
+static evhtp_res
+htp_run_finished_hook(evhtp_conn_t * conn) {
+    evhtp_log_debug("enter");
+
+    if (htp_conn_callback_has_hook(conn, _fini)) {
+        return htp_conn_callback_hook_calln(conn, _fini);
+    }
+
+    if (htp_conn_has_hook(conn, _fini)) {
+        return htp_conn_hook_calln(conn, _fini);
+    }
+
+    return EVHTP_RES_OK;
 }
 
 static int
-htp_start_cb(http_parser * p) {
-    evhtp_conn_t * conn = p->data;
+htp_start_cb(htparser * p) {
+    evhtp_conn_t * conn = htparser_get_userdata(p);
 
     evhtp_log_debug("enter");
 
@@ -267,37 +388,41 @@
 }
 
 static int
-htp_end_cb(http_parser * p) {
+htp_end_cb(htparser * p) {
     evhtp_conn_t    * conn    = NULL;
     evhtp_request_t * request = NULL;
 
     evhtp_log_debug("enter");
-    conn    = p->data;
+
+    conn    = htparser_get_userdata(p);
     request = conn->request;
 
     if (request->cb) {
+        evhtp_log_debug("calling user cb");
         request->cb(request, request->cbarg);
     }
 
     return 0;
 }
 
+#if 0
 static int
-htp_query_str_cb(http_parser * p, const char * buf, size_t len) {
-    /* evhtp_conn_t * conn = p->data; */
-
+htp_query_str_cb(htparser * p __unused__, const char * buf, size_t len) {
     evhtp_log_debug("len = %" PRIoMAX " buf = '%.*s'", len, (int)len, buf);
 
     return 0;
 }
 
+#endif
+
 static int
-htp_uri_cb(http_parser * p, const char * buf, size_t len) {
+htp_uri_cb(htparser * p, const char * buf, size_t len) {
     evhtp_conn_t    * conn;
     evhtp_request_t * request;
 
     evhtp_log_debug("enter");
-    conn              = p->data;
+
+    conn              = htparser_get_userdata(p);
     request           = conn->request;
 
     request->uri      = malloc(len + 1);
@@ -305,62 +430,93 @@
 
     memcpy(request->uri, buf, len);
 
-    if (htp_run_uri_hook(conn, request->uri) != EVHTP_RES_OK) {
-        conn->err = 1;
+    if ((conn->status = htp_run_uri_hook(conn, request->uri)) != EVHTP_RES_OK) {
         return -1;
     }
 
     return 0;
 }
 
+#if 0
 static int
-htp_fragment_cb(http_parser * p, const char * buf, size_t len) {
-    /* evhtp_conn_t * conn = p->data; */
-
+htp_fragment_cb(htparser * p __unused__, const char * buf, size_t len) {
     evhtp_log_debug("len = %" PRIoMAX " buf = '%.*s", len, (int)len, buf);
 
     return 0;
 }
 
-static int
-htp_header_key_cb(http_parser * p, const char * buf, size_t len) {
-    evhtp_hdr_t  * hdr;
-    evhtp_conn_t * conn;
+#endif
 
-    evhtp_log_debug("len = %" PRIdMAX, len);
+evhtp_hdr_t *
+evhtp_hdr_key_add(evhtp_hdrs_t * hdrs, const char * k, size_t len) {
+    evhtp_hdr_t * hdr;
 
-    conn          = p->data;
-    hdr           = malloc(sizeof(evhtp_hdr_t));
+    hdr           = calloc(sizeof(evhtp_hdr_t), 1);
     hdr->k_heaped = 1;
     hdr->key      = malloc(len + 1);
     hdr->key[len] = '\0';
 
-    memcpy(hdr->key, buf, len);
-    TAILQ_INSERT_TAIL(&conn->request->headers_in, hdr, next);
+    memcpy(hdr->key, k, len);
+
+    evhtp_log_debug("key len = %zu 3B=[%c%c%c]",
+                    evhtp_debug_strlen(hdr->key),
+                    (len > 0) ? hdr->key[0] : '0',
+                    (len > 1) ? hdr->key[1] : '0',
+                    (len > 2) ? hdr->key[2] : '0');
+
+
+    TAILQ_INSERT_TAIL(hdrs, hdr, next);
+    return hdr;
+}
+
+static int
+htp_header_key_cb(htparser * p, const char * buf, size_t len) {
+    evhtp_conn_t * conn;
+
+    evhtp_log_debug("len = %" PRIdMAX, len);
+
+    conn = htparser_get_userdata(p);
+    evhtp_hdr_key_add(&conn->request->headers_in, buf, len);
 
     return 0;
 }
 
+evhtp_hdr_t *
+evhtp_hdr_val_add(evhtp_hdrs_t * hdrs, const char * v, size_t len) {
+    evhtp_hdr_t * hdr;
+
+    hdr           = TAILQ_LAST(hdrs, evhtp_hdrs);
+
+    hdr->v_heaped = 1;
+    hdr->val      = malloc(len + 1);
+    hdr->val[len] = '\0';
+
+    memcpy(hdr->val, v, len);
+
+    evhtp_log_debug("val len = %zu 3B=[%c%c%c]",
+                    evhtp_debug_strlen(hdr->val),
+                    (len > 0) ? hdr->val[0] : '0',
+                    (len > 1) ? hdr->val[1] : '0',
+                    (len > 2) ? hdr->val[2] : '0');
+
+    return hdr;
+}
+
 static int
-htp_header_val_cb(http_parser * p, const char * buf, size_t len) {
+htp_header_val_cb(htparser * p, const char * buf, size_t len) {
     evhtp_hdr_t     * hdr  = NULL;
     evhtp_conn_t    * conn = NULL;
     evhtp_request_t * req  = NULL;
 
     evhtp_log_debug("len = %" PRIdMAX, len);
 
-    conn          = p->data;
-    req           = conn->request;
-    hdr           = TAILQ_LAST(&req->headers_in, evhtp_hdrs);
+    conn = htparser_get_userdata(p);
+    req  = conn->request;
 
-    hdr->v_heaped = 1;
-    hdr->val      = malloc(len + 1);
-    hdr->val[len] = '\0';
+    hdr  = evhtp_hdr_val_add(&req->headers_in, buf, len);
 
-    memcpy(hdr->val, buf, len);
-
-    if (htp_run_hdr_hook(conn, hdr) != EVHTP_RES_OK) {
-        conn->err = 1;
+    if ((conn->status = htp_run_hdr_hook(conn, hdr)) != EVHTP_RES_OK) {
+        evhtp_log_debug("status = %d\n", conn->status);
         return -1;
     }
 
@@ -368,19 +524,20 @@
 }
 
 static int
-htp_headers_complete_cb(http_parser * p) {
+htp_headers_complete_cb(htparser * p) {
     evhtp_conn_t * conn;
 
     evhtp_log_debug("enter");
-    conn = p->data;
+    conn = htparser_get_userdata(p);
 
-    conn->request->method = p->method;
-    conn->request->major  = p->http_major;
-    conn->request->minor  = p->http_minor;
-    conn->request->proto  = htp_proto(p->http_major, p->http_minor);
+    conn->request->method = htparser_get_method(p);
+    conn->request->major  = htparser_get_major(p);
+    conn->request->minor  = htparser_get_minor(p);
 
-    if (htp_run_hdrs_hook(conn, &conn->request->headers_in) != EVHTP_RES_OK) {
-        conn->err = 1;
+    conn->request->proto  = htp_proto(conn->request->major, conn->request->minor);
+
+    if ((conn->status = htp_run_hdrs_hook(conn, &conn->request->headers_in)) != EVHTP_RES_OK) {
+        evhtp_log_debug("Uhm..\n");
         return -1;
     }
 
@@ -394,32 +551,40 @@
         }
 
         if ((status = htp_run_on_expect_hook(conn, expt_val)) != EVHTP_RES_CONTINUE) {
-            conn->err = 1;
+            conn->status = status;
             evhtp_send_reply(conn->request, status, "no", NULL);
             return -1;
         }
 
         buf = evbuffer_new();
-        evbuffer_add_printf(buf, "HTTP/%d.%d 100 Continue\r\n\r\n", p->http_major, p->http_minor);
+
+        evbuffer_add_printf(buf, "HTTP/%d.%d 100 Continue\r\n\r\n",
+                            htparser_get_major(p),
+                            htparser_get_minor(p));
+
         evbuffer_write(buf, conn->sock);
         evbuffer_free(buf);
     }
 
     return 0;
-}
+} /* htp_headers_complete_cb */
 
 static int
-htp_path_cb(http_parser * p, const char * buf, size_t len) {
+htp_path_cb(htparser * p, const char * buf, size_t len) {
     evhtp_conn_t     * conn    = NULL;
     evhtp_request_t  * request = NULL;
     evhtp_callback_t * cb      = NULL;
 
     evhtp_log_debug("enter");
 
-    conn               = p->data;
-    request            = conn->request;
-    request->path      = malloc(len + 1);
-    request->path[len] = '\0';
+    conn                = htparser_get_userdata(p);
+    request             = conn->request;
+
+    request->path       = malloc(len + 1);
+    request->path[len]  = '\0';
+
+    request->prev_state = request->curr_state;
+    request->curr_state = htp_parse_s_path;
 
     memcpy(request->path, buf, len);
 
@@ -428,36 +593,43 @@
                                               &request->matched_soff,
                                               &request->matched_eoff);
     if (cb == NULL) {
-        if (conn->htp->default_cb == NULL) {
-            evhtp_send_reply(request, EVHTP_RES_400, "NOT FOUND", NULL);
-            return -1;
-        }
-
-        request->cb    = conn->htp->default_cb;
-        request->cbarg = conn->htp->default_cbarg;
+        request->cb       = conn->htp->default_cb;
+        request->cbarg    = conn->htp->default_cbarg;
+        request->cb_hooks = NULL;
     } else {
-        request->cb    = cb->cb;
-        request->cbarg = cb->cbarg;
+        request->cb       = cb->cb;
+        request->cbarg    = cb->cbarg;
+        request->cb_hooks = cb->hooks;
     }
 
-    if (htp_run_path_hook(conn, conn->request->path) != EVHTP_RES_OK) {
-        conn->err = 1;
+    conn->status = htp_run_path_hook(conn, conn->request->path);
+
+    evhtp_log_debug("status %d", conn->status);
+
+    if (conn->status != EVHTP_RES_OK) {
+        evhtp_log_debug("non-ok res...");
         return -1;
     }
+#if 0
+    if ((conn->status = htp_run_path_hook(conn, conn->request->path)) != EVHTP_RES_OK) {
+        return -1;
+    }
+#endif
 
+
+    evhtp_log_debug("return 0");
     return 0;
-}
+} /* htp_path_cb */
 
 static int
-htp_body_cb(http_parser * p, const char * buf, size_t len) {
-    evhtp_conn_t * conn = p->data;
+htp_body_cb(htparser * p, const char * buf, size_t len) {
+    evhtp_conn_t * conn = htparser_get_userdata(p);
 
     evhtp_log_debug("enter");
 
     evbuffer_add(evhtp_request_get_input(conn->request), buf, len);
 
-    if (htp_run_read_hook(conn, buf, len) != EVHTP_RES_OK) {
-        conn->err = 1;
+    if ((conn->status = htp_run_read_hook(conn, buf, len)) != EVHTP_RES_OK) {
         return -1;
     }
 
@@ -574,7 +746,9 @@
     return NULL;
 }
 
-static evhtp_callback_t *
+#if 0
+/* XXX eventually just rename htp_callbacks_find_callback_woffsets to this */
+static evhtp_callback_t * __unused__
 htp_callbacks_find_callback(evhtp_callbacks_t * cbs, const char * uri) {
     evhtp_callback_t * cb;
     unsigned int       hash;
@@ -611,6 +785,8 @@
     return NULL;
 }
 
+#endif
+
 static int
 htp_callbacks_add_callback(evhtp_callbacks_t * cbs, evhtp_callback_t * cb) {
     unsigned int hkey;
@@ -646,16 +822,16 @@
 
     evhtp_log_debug("enter");
 
-    if (conn->hooks) {
-        free(conn->hooks);
+    if (conn->request) {
+        evhtp_request_free(conn->request);
     }
 
     if (conn->parser) {
         free(conn->parser);
     }
 
-    if (conn->request) {
-        evhtp_request_free(conn->request);
+    if (conn->hooks) {
+        free(conn->hooks);
     }
 
     if (conn->thr) {
@@ -666,6 +842,9 @@
         bufferevent_free(conn->bev);
     }
 
+    if (conn->resume_ev) {
+        event_free(conn->resume_ev);
+    }
 
     free(conn);
 } /* htp_conn_free */
@@ -680,36 +859,35 @@
         return NULL;
     }
 
-    conn->htp          = htp;
-    conn->flags        = 0;
-    conn->hooks        = NULL;
-    conn->request      = NULL;
-    conn->sock         = 0;
-    conn->flags        = 0;
-    conn->evbase       = NULL;
-    conn->bev          = NULL;
-    conn->ssl          = NULL;
-    conn->thr          = NULL;
+    conn->htp       = htp;
+    conn->flags     = 0;
+    conn->hooks     = NULL;
+    conn->request   = NULL;
+    conn->sock      = 0;
+    conn->flags     = 0;
+    conn->evbase    = NULL;
+    conn->bev       = NULL;
+    conn->ssl       = NULL;
+    conn->thr       = NULL;
+    conn->resume_ev = NULL;
+    conn->status    = EVHTP_RES_OK;
+    conn->parser    = htparser_new();
 
-    conn->parser       = malloc(sizeof(http_parser));
-    conn->parser->data = conn;
-
-    http_parser_init(conn->parser, HTTP_REQUEST);
+    htparser_init(conn->parser);
+    htparser_set_userdata(conn->parser, conn);
     return conn;
 }
 
 static void
 htp_conn_reset(evhtp_conn_t * conn) {
-    http_parser_init(conn->parser, HTTP_REQUEST);
-
     evhtp_log_debug("enter");
 
+    htparser_init(conn->parser);
+    htparser_set_userdata(conn->parser, conn);
+
     evhtp_request_free(conn->request);
     conn->request = NULL;
 
-    bufferevent_disable(conn->bev, EV_WRITE);
-    bufferevent_enable(conn->bev, EV_READ);
-
     bufferevent_setwatermark(conn->bev, EV_READ | EV_WRITE, 0, 0);
     bufferevent_setcb(conn->bev, htp_recv_cb, NULL, htp_err_cb, conn);
 }
@@ -745,6 +923,44 @@
 }
 
 static void
+htp_connection_resume_cb(int fd __unused__, short events __unused__, void * arg) {
+    evhtp_conn_t * conn = arg;
+
+    if (conn->htp->suspend_enabled == 0 || conn->resume_ev == NULL) {
+        return;
+    }
+
+    evhtp_log_debug("enter");
+    event_del(conn->resume_ev);
+
+    return htp_recv_cb(conn->bev, arg);
+}
+
+static void
+htp_conn_resume(evhtp_conn_t * conn) {
+    evhtp_log_debug("enter");
+    if (conn->htp->suspend_enabled == 0 || conn->resume_ev == NULL) {
+        return;
+    }
+
+    /* bufferevent_enable(conn->bev, EV_READ | EV_WRITE); */
+    conn->status = EVHTP_RES_OK;
+    event_active(conn->resume_ev, EV_WRITE, 1);
+}
+
+static void
+htp_conn_suspend(evhtp_conn_t * conn) {
+    evhtp_log_debug("enter");
+
+    if (conn->htp->suspend_enabled == 0 || conn->resume_ev == NULL) {
+        return;
+    }
+
+    /* bufferevent_disable(conn->bev, EV_READ | EV_WRITE); */
+    event_add(conn->resume_ev, NULL);
+}
+
+static void
 htp_recv_cb(evbev_t * bev, void * arg) {
     evbuf_t      * ibuf;
     evhtp_conn_t * conn;
@@ -753,31 +969,48 @@
     size_t         avail;
 
     evhtp_log_debug("enter");
+
     conn     = (evhtp_conn_t *)arg;
     ibuf     = bufferevent_get_input(bev);
     avail    = evbuffer_get_length(ibuf);
     read_buf = evbuffer_pullup(ibuf, avail);
 
-    nread    = http_parser_execute(conn->parser, &conn->htp->psets, (char *)read_buf, avail);
+    nread    = htparser_run(conn->parser, &conn->htp->psets, (const char *)read_buf, avail);
 
-    if (conn->err != 0) {
-        return htp_conn_free(conn);
+    evhtp_log_debug("nread = %zu, avail = %zu", nread, avail);
+
+    switch (conn->status) {
+        case EVHTP_RES_OK:
+            break;
+        case EVHTP_RES_PAUSE:
+            evbuffer_drain(ibuf, nread);
+            return htp_conn_suspend(conn);
+        case EVHTP_RES_ERROR:
+        case EVHTP_RES_SCREWEDUP:
+            return htp_conn_free(conn);
+        default:
+            if (conn->request != NULL) {
+                return evhtp_send_reply(conn->request, conn->status, NULL, NULL);
+            }
+
+            return htp_conn_free(conn);
     }
 
-    evhtp_log_debug("nread = %zu", nread);
+    if (nread != avail) {
+        conn->status = EVHTP_RES_ERROR;
 
-    if (nread <= evbuffer_get_length(ibuf)) {
-        evbuffer_drain(ibuf, nread);
-    } else {
-        evbuffer_drain(ibuf, -1);
+        htp_conn_free(conn);
+        return;
     }
-}
+
+    evbuffer_drain(ibuf, nread);
+} /* htp_recv_cb */
 
 static void
-htp_err_cb(evbev_t * bev, short events, void * arg) {
+htp_err_cb(evbev_t * bev __unused__, short events, void * arg) {
     evhtp_conn_t * conn;
 
-    evhtp_log_debug("events = %x", events);
+    evhtp_log_debug("events = %x bev = %p", events, bev);
 
     if (events & (BEV_EVENT_ERROR | BEV_EVENT_EOF)) {
         conn = (evhtp_conn_t *)arg;
@@ -815,7 +1048,8 @@
     conn->thr    = thr;
 
     if (htp->ssl_ctx == NULL) {
-        conn->bev = bufferevent_socket_new(conn->evbase, conn->sock, BEV_OPT_CLOSE_ON_FREE);
+        conn->bev = bufferevent_socket_new(conn->evbase, conn->sock,
+                                           BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS);
     } else {
 #ifndef DISABLE_SSL
         conn->ssl = SSL_new(htp->ssl_ctx);
@@ -827,31 +1061,64 @@
 #endif
     }
 
-    bufferevent_setcb(conn->bev, htp_recv_cb, NULL, htp_err_cb, conn);
-    bufferevent_enable(conn->bev, EV_READ);
-    bufferevent_disable(conn->bev, EV_WRITE);
-
-    if (htp->post_accept_cb) {
-        htp->post_accept_cb(conn, htp->post_accept_cbarg);
+    if (htp_run_post_accept(htp, conn) < 0) {
+        return htp_conn_free(conn);
     }
 
+    bufferevent_enable(conn->bev, EV_READ);
+    bufferevent_setcb(conn->bev, htp_recv_cb, NULL, htp_err_cb, conn);
+
     evthr_inc_backlog(conn->thr);
 }
 
+static int
+htp_run_pre_accept(evhtp_t * htp, int fd, struct sockaddr * s, int sl) {
+    if (htp->pre_accept_cb == NULL) {
+        return 0;
+    }
+
+    if (htp->pre_accept_cb(fd, s, sl, htp->pre_accept_cbarg) != EVHTP_RES_OK) {
+        evutil_closesocket(fd);
+        EVUTIL_SET_SOCKET_ERROR(ECONNREFUSED);
+        return -1;
+    }
+
+    return 0;
+}
+
+static int
+htp_run_post_accept(evhtp_t * htp, evhtp_conn_t * conn) {
+    if (htp->post_accept_cb == NULL) {
+        return 0;
+    }
+
+    if (htp->post_accept_cb(conn, htp->post_accept_cbarg) != EVHTP_RES_OK) {
+        return -1;
+    }
+
+    return 0;
+}
+
 static void
-htp_accept_cb(evserv_t * serv, int fd, struct sockaddr * s, int sl, void * arg) {
+htp_accept_cb(evserv_t * serv __unused__, int fd, struct sockaddr * s, int sl, void * arg) {
     evhtp_t      * htp;
     evhtp_conn_t * conn;
 
     evhtp_log_debug("enter");
 
-    htp          = (evhtp_t *)arg;
+    htp = (evhtp_t *)arg;
+
+    if (htp_run_pre_accept(htp, fd, s, sl) < 0) {
+        return;
+    }
+
     conn         = htp_conn_new(htp);
     conn->evbase = htp->evbase;
     conn->sock   = fd;
 
-    if (htp->post_accept_cb) {
-        htp->post_accept_cb(conn, htp->post_accept_cbarg);
+    if (htp->suspend_enabled == 1) {
+        conn->resume_ev = event_new(conn->evbase, -1, EV_READ | EV_PERSIST,
+                                    htp_connection_resume_cb, conn);
     }
 
     if (htp->pool != NULL) {
@@ -874,13 +1141,12 @@
 #endif
     }
 
-    bufferevent_disable(conn->bev, EV_WRITE);
+    if (htp_run_post_accept(htp, conn) < 0) {
+        return htp_conn_free(conn);
+    }
+
     bufferevent_enable(conn->bev, EV_READ);
     bufferevent_setcb(conn->bev, htp_recv_cb, NULL, htp_err_cb, conn);
-
-    if (htp->post_accept_cb) {
-        htp->post_accept_cb(conn, htp->post_accept_cbarg);
-    }
 } /* htp_accept_cb */
 
 static void
@@ -972,23 +1238,23 @@
 
     switch (htp_code_parent(code)) {
         case EVHTP_RES_100:
-            res = (flags & EVHTP_CLOSE_ON_100);
+            res = (flags & EVHTP_FLAG_CLOSE_ON_100);
             break;
         case EVHTP_RES_200:
-            res = (flags & EVHTP_CLOSE_ON_200);
+            res = (flags & EVHTP_FLAG_CLOSE_ON_200);
             break;
         case EVHTP_RES_300:
-            res = (flags & EVHTP_CLOSE_ON_300);
+            res = (flags & EVHTP_FLAG_CLOSE_ON_300);
             break;
         case EVHTP_RES_400:
-            if (code == EVHTP_RES_EXPECTFAIL && flags & EVHTP_CLOSE_ON_EXPECT_ERR) {
+            if (code == EVHTP_RES_EXPECTFAIL && flags & EVHTP_FLAG_CLOSE_ON_EXPECT_ERR) {
                 res = 1;
             } else {
-                res = (flags & EVHTP_CLOSE_ON_400);
+                res = (flags & EVHTP_FLAG_CLOSE_ON_400);
             }
             break;
         case EVHTP_RES_500:
-            res = (flags & EVHTP_CLOSE_ON_500);
+            res = (flags & EVHTP_FLAG_CLOSE_ON_500);
             break;
         case EVHTP_RES_SCREWEDUP:
         default:
@@ -999,12 +1265,30 @@
     return res ? 1 : 0;
 }
 
-static int
-htp_should_keep_alive(evhtp_request_t * req, evhtp_res code) {
+void
+evhtp_request_suspend(evhtp_request_t * req) {
+    evhtp_log_debug("enter");
+    return htp_conn_suspend(evhtp_request_get_conn(req));
+}
+
+void
+evhtp_request_resume(evhtp_request_t * req) {
+    evhtp_log_debug("enter");
+    return htp_conn_resume(evhtp_request_get_conn(req));
+}
+
+event_t *
+evhtp_request_get_resume_ev(evhtp_request_t * req) {
+    evhtp_log_debug("enter");
+    return req->conn->resume_ev;
+}
+
+int
+evhtp_request_keepalive(evhtp_request_t * req, evhtp_res code) {
     evhtp_conn_t * conn = req->conn;
 
     evhtp_log_debug("enter");
-    if (http_should_keep_alive(conn->parser) == 0) {
+    if (htparser_should_keep_alive(conn->parser) == 0) {
         /* parsed request doesn't even support keep-alive */
         return 0;
     }
@@ -1081,7 +1365,7 @@
 }
 
 static void
-htp_resp_fini_cb(evbev_t * bev, void * arg) {
+htp_resp_fini_cb(evbev_t * bev __unused__, void * arg) {
     evhtp_request_t * req;
     evhtp_conn_t    * conn;
     int               keepalive;
@@ -1100,7 +1384,7 @@
 }
 
 static void
-htp_resp_err_cb(evbev_t * bev, short events, void * arg) {
+htp_resp_err_cb(evbev_t * bev __unused__, short events, void * arg) {
     evhtp_request_t * req;
     evhtp_conn_t    * conn;
 
@@ -1113,7 +1397,7 @@
 }
 
 static void
-htp_stream_fini_cb(evbev_t * bev, void * arg) {
+htp_stream_fini_cb(evbev_t * bev __unused__, void * arg) {
     evhtp_request_t * req;
     evhtp_conn_t    * conn;
     evbuf_t         * buf;
@@ -1145,7 +1429,15 @@
 }
 
 void
-evhtp_send_reply(evhtp_request_t * req, evhtp_res code, const char * r, evbuf_t * b) {
+evhtp_completed_reply(evhtp_request_t * req, evhtp_res code) {
+    req->keepalive = evhtp_request_keepalive(req, code);
+
+    bufferevent_setwatermark(req->conn->bev, EV_WRITE, 1, 0);
+    bufferevent_setcb(req->conn->bev, NULL, htp_resp_fini_cb, htp_resp_err_cb, req);
+}
+
+void
+evhtp_send_reply(evhtp_request_t * req, evhtp_res code, const char * r __unused__, evbuf_t * b) {
     evhtp_conn_t * conn;
     evbuf_t      * obuf;
 
@@ -1153,7 +1445,7 @@
 
     conn           = req->conn;
     obuf           = evhtp_request_get_output(req);
-    req->keepalive = htp_should_keep_alive(req, code);
+    req->keepalive = evhtp_request_keepalive(req, code);
 
     assert(obuf != NULL);
 
@@ -1166,9 +1458,6 @@
     htp_set_crlf_buf(obuf);
     htp_set_body_buf(obuf, b);
 
-
-    bufferevent_disable(conn->bev, EV_READ);
-    bufferevent_enable(conn->bev, EV_WRITE);
     bufferevent_setwatermark(conn->bev, EV_WRITE, 1, 0);
     bufferevent_setcb(conn->bev, NULL, htp_resp_fini_cb, htp_resp_err_cb, req);
     bufferevent_write_buffer(conn->bev, obuf);
@@ -1187,7 +1476,7 @@
     assert(obuf != NULL);
 
     if (req->proto == EVHTP_PROTO_1_1) {
-        req->keepalive = htp_should_keep_alive(req, code);
+        req->keepalive = evhtp_request_keepalive(req, code);
 
         if (!evhtp_hdr_find(&req->headers_out, _HTP_TRANSENC)) {
             evhtp_hdr_add(&req->headers_out, evhtp_hdr_new(_HTP_TRANSENC, _HTP_DEFCHUNKED));
@@ -1212,9 +1501,6 @@
     req->stream_cb    = cb;
     req->stream_cbarg = arg;
 
-
-    bufferevent_disable(conn->bev, EV_READ);
-    bufferevent_enable(conn->bev, EV_WRITE);
     bufferevent_setwatermark(conn->bev, EV_WRITE, 1, 0);
     bufferevent_setcb(conn->bev, NULL, htp_stream_fini_cb, htp_resp_err_cb, req);
     bufferevent_write_buffer(conn->bev, obuf);
@@ -1248,14 +1534,15 @@
 }
 
 int
-evhtp_conn_set_flags(evhtp_conn_t * conn, evhtp_cflags flags) {
+evhtp_set_connection_flags(evhtp_conn_t * conn, evhtp_cflags flags) {
     evhtp_log_debug("enter");
-    conn->flags |= flags;
+
+    conn->flags = flags;
     return 0;
 }
 
 int
-evhtp_set_hook(evhtp_conn_t * conn, evhtp_hook_type type, void * cb, void * cbarg) {
+evhtp_set_connection_hook(evhtp_conn_t * conn, evhtp_hook_type type, void * cb, void * cbarg) {
     evhtp_log_debug("enter");
     if (conn->hooks == NULL) {
         conn->hooks = calloc(sizeof(evhtp_hooks_t), sizeof(char));
@@ -1281,6 +1568,7 @@
             htp_conn_hook_set(conn, _on_expect, cb, cbarg);
             break;
         case EVHTP_HOOK_COMPLETE:
+            htp_conn_hook_set(conn, _fini, cb, cbarg);
             break;
         default:
             return -1;
@@ -1290,6 +1578,42 @@
 }
 
 int
+evhtp_set_callback_hook(evhtp_callback_t * callback, evhtp_hook_type type, void * cb, void * cbarg) {
+    evhtp_log_debug("enter");
+
+    if (callback->hooks == NULL) {
+        callback->hooks = calloc(sizeof(evhtp_hooks_t), sizeof(char));
+    }
+
+    switch (type) {
+        case EVHTP_HOOK_HDRS_READ:
+            htp_callback_hook_set(callback, _hdrs, cb, cbarg);
+            break;
+        case EVHTP_HOOK_HDR_READ:
+            htp_callback_hook_set(callback, _hdr, cb, cbarg);
+            break;
+        case EVHTP_HOOK_ON_EXPECT:
+            htp_callback_hook_set(callback, _on_expect, cb, cbarg);
+            break;
+        case EVHTP_HOOK_READ:
+            htp_callback_hook_set(callback, _read, cb, cbarg);
+            break;
+        case EVHTP_HOOK_COMPLETE:
+            htp_callback_hook_set(callback, _fini, cb, cbarg);
+            break;
+        case EVHTP_HOOK_URI_READ:
+        case EVHTP_HOOK_PATH_READ:
+        /* the point of per-callback_t hooks is to already know where it's
+         * supposed to go, and path_read is where the proper callback is
+         * found. So we can't actually use this */
+        default:
+            return -1;
+    } /* switch */
+
+    return 0;
+}
+
+evhtp_callback_t *
 evhtp_set_cb(evhtp_t * htp, const char * uri, evhtp_callback_cb cb, void * cbarg) {
     evhtp_callback_t * htp_cb;
 
@@ -1297,24 +1621,20 @@
 
     if (htp->callbacks == NULL) {
         htp->callbacks = htp_callbacks_new(1024);
-    } else {
-        if (htp_callbacks_find_callback(htp->callbacks, uri)) {
-            return -1;
-        }
     }
 
     if (!(htp_cb = htp_callback_new(uri, callback_type_uri, cb, cbarg))) {
-        return -1;
+        return NULL;
     }
 
-    if (!htp_callbacks_add_callback(htp->callbacks, htp_cb)) {
-        return -1;
+    if (htp_callbacks_add_callback(htp->callbacks, htp_cb) != 0) {
+        return NULL;
     }
 
-    return 0;
+    return htp_cb;
 }
 
-int
+evhtp_callback_t *
 evhtp_set_regex_cb(evhtp_t * htp, const char * pat, evhtp_callback_cb cb, void * arg) {
     evhtp_callback_t * htp_cb;
 
@@ -1325,14 +1645,14 @@
     }
 
     if (!(htp_cb = htp_callback_new(pat, callback_type_regex, cb, arg))) {
-        return -1;
+        return NULL;
     }
 
-    if (!htp_callbacks_add_callback(htp->callbacks, htp_cb)) {
-        return -1;
+    if (htp_callbacks_add_callback(htp->callbacks, htp_cb) != 0) {
+        return NULL;
     }
 
-    return 0;
+    return htp_cb;
 }
 
 void
@@ -1344,9 +1664,12 @@
 
 void
 evhtp_bind_socket(evhtp_t * htp, const char * baddr, uint16_t port) {
-    struct sockaddr_in sin = { 0 };
+    struct sockaddr_in sin;
 
     evhtp_log_debug("enter");
+
+    memset(&sin, 0, sizeof(sin));
+
     sin.sin_family      = AF_INET;
     sin.sin_port        = htons(port);
     sin.sin_addr.s_addr = inet_addr(baddr);
@@ -1360,8 +1683,13 @@
 }
 
 void
+evhtp_set_pre_accept_cb(evhtp_t * htp, evhtp_pre_accept cb, void * cbarg) {
+    htp->pre_accept_cb    = cb;
+    htp->pre_accept_cbarg = cbarg;
+}
+
+void
 evhtp_set_post_accept_cb(evhtp_t * htp, evhtp_post_accept cb, void * cbarg) {
-    evhtp_log_debug("enter");
     htp->post_accept_cb    = cb;
     htp->post_accept_cbarg = cbarg;
 }
@@ -1426,6 +1754,8 @@
         return;
     }
 
+    htp_run_finished_hook(req->conn);
+
     if (req->path) {
         free(req->path);
     }
@@ -1569,6 +1899,21 @@
     return 0;
 }
 
+char
+evhtp_request_get_major(evhtp_request_t * request) {
+    return request->major;
+}
+
+char
+evhtp_request_get_minor(evhtp_request_t * request) {
+    return request->minor;
+}
+
+evbev_t *
+evhtp_request_get_bev(evhtp_request_t * request) {
+    return evhtp_conn_get_bev(request->conn);
+}
+
 const char *
 evhtp_request_get_path(evhtp_request_t * request) {
     return (const char *)request->path;
@@ -1629,6 +1974,11 @@
     return request->cb;
 }
 
+void
+evhtp_request_set_cbarg(evhtp_request_t * request, void * arg) {
+    request->cbarg = arg;
+}
+
 void *
 evhtp_request_get_cbarg(evhtp_request_t * request) {
     return request->cbarg;
@@ -1636,12 +1986,12 @@
 
 const char *
 evhtp_request_method_str(evhtp_request_t * request) {
-    return evhtp_method_str(request->method);
+    return htparser_get_methodstr(request->conn->parser);
 }
 
-int64_t
+uint64_t
 evhtp_request_content_length(evhtp_request_t * request) {
-    return request->conn->parser->content_length;
+    return htparser_get_content_length(request->conn->parser);
 }
 
 int
@@ -1649,11 +1999,14 @@
     return evhtp_conn_is_ssl(evhtp_request_get_conn(request));
 }
 
+#if 0
 const char *
 evhtp_method_str(evhtp_method method) {
-    return http_method_str(method);
+    return htparser_get_methodstr(method);
 }
 
+#endif
+
 evbase_t *
 evhtp_request_get_evbase(evhtp_request_t * request) {
     evhtp_log_debug("enter");
@@ -1717,6 +2070,8 @@
     }
 
     request->conn       = conn;
+    request->prev_state = htp_parse_s_nil;
+    request->curr_state = htp_parse_s_nil;
     request->buffer_in  = evbuffer_new();
     request->buffer_out = evbuffer_new();
 
@@ -1759,6 +2114,11 @@
     return conn->ssl;
 }
 
+evbev_t *
+evhtp_conn_get_bev(evhtp_conn_t * conn) {
+    return conn->bev;
+}
+
 int
 evhtp_is_ssl(evhtp_t * htp) {
     return htp->ssl_ctx ? 1 : 0;
@@ -1796,7 +2156,7 @@
 }
 
 static void
-htp_ssl_scache_builtin_expire(int fd, short what, void * arg) {
+htp_ssl_scache_builtin_expire(int fd __unused__, short what __unused__, void * arg) {
     htp_scache_ent_t * ent;
     htp_scache_t     * scache;
 
@@ -1882,7 +2242,7 @@
 }
 
 void *
-evhtp_ssl_scache_builtin_init(evhtp_t * htp) {
+evhtp_ssl_scache_builtin_init(evhtp_t * htp __unused__) {
     htp_scache_t * scache;
 
     scache = malloc(sizeof(htp_scache_t));
@@ -2015,7 +2375,7 @@
 }
 
 static void
-htp_ssl_thr_lock(int mode, int type, const char * file, int line) {
+htp_ssl_thr_lock(int mode, int type, const char * file __unused__, int line __unused__) {
     if (type < ssl_num_locks) {
         if (mode & CRYPTO_LOCK) {
             pthread_mutex_lock(ssl_locks[type]);
@@ -2025,6 +2385,12 @@
     }
 }
 
+void
+evhtp_disable_pausing(evhtp_t * htp) {
+    evhtp_log_debug("enter");
+    htp->suspend_enabled = 0;
+}
+
 int
 evhtp_use_threads(evhtp_t * htp, int nthreads) {
     evhtp_log_debug("enter");
@@ -2081,25 +2447,31 @@
         return NULL;
     }
 
-    htp->server_name               = _HTP_DEFSERVER;
-    htp->psets.on_message_begin    = htp_start_cb;
-    htp->psets.on_path             = htp_path_cb;
-    htp->psets.on_query_string     = htp_query_str_cb;
-    htp->psets.on_url              = htp_uri_cb;
-    htp->psets.on_fragment         = htp_fragment_cb;
-    htp->psets.on_header_field     = htp_header_key_cb;
-    htp->psets.on_header_value     = htp_header_val_cb;
-    htp->psets.on_headers_complete = htp_headers_complete_cb;
-    htp->psets.on_body             = htp_body_cb;
-    htp->psets.on_message_complete = htp_end_cb;
+    htp->server_name            = _HTP_DEFSERVER;
+    htp->psets.on_msg_begin     = htp_start_cb;
+    htp->psets.method           = NULL;       /* todo */
+    htp->psets.scheme           = NULL;       /* todo */
+    htp->psets.host             = NULL;       /* todo */
+    htp->psets.port             = NULL;       /* todo */
+    htp->psets.path             = htp_path_cb;
+    htp->psets.args             = NULL;       /* todo just gives me arguments */
+    htp->psets.uri              = htp_uri_cb; /* entire uri include path/args * / */
+    htp->psets.on_hdrs_begin    = NULL;       /* todo */
+    htp->psets.hdr_key          = htp_header_key_cb;
+    htp->psets.hdr_val          = htp_header_val_cb;
+    htp->psets.on_hdrs_complete = htp_headers_complete_cb;
+    htp->psets.on_new_chunk     = NULL;       /* todo */
+    htp->psets.body             = htp_body_cb;
+    htp->psets.on_msg_complete  = htp_end_cb;
 
-    htp->evbase = evbase ? : event_base_new();
+    htp->evbase          = evbase ? : event_base_new();
+    htp->suspend_enabled = 1;
 
     evhtp_set_gencb(htp, htp_default_404, htp);
     evhtp_log_debug("created new instance");
 
     return htp;
-}
+} /* evhtp_new */
 
 const char *
 evhtp_version(void) {
diff --git a/evhtp.h b/evhtp.h
index 5b64b04..0dbb4a2 100644
--- a/evhtp.h
+++ b/evhtp.h
@@ -6,7 +6,7 @@
 #endif
 
 #include <sys/queue.h>
-#include <http_parser.h>
+#include <htparse.h>
 #include <event.h>
 #include <event2/listener.h>
 
@@ -17,7 +17,13 @@
 #include <openssl/rand.h>
 #endif
 
-#define EVHTP_VERSION "0.3.6"
+#define EVHTP_VERSION "0.3.7"
+
+#if (__GNUC__ > 2 || ( __GNUC__ == 2 && __GNUC__MINOR__ > 4)) && (!defined(__STRICT_ANSI__) || __STRICT_ANSI__ == 0)
+#define __unused__    __attribute__((unused))
+#else
+#define __unused__
+#endif
 
 struct evhtp;
 struct evhtp_hdrs;
@@ -25,8 +31,9 @@
 struct evhtp_hooks;
 struct evhtp_conn;
 struct evhtp_request;
+struct evhtp_callback;
 
-typedef unsigned char         evhtp_cflags;
+typedef uint16_t              evhtp_cflags;
 typedef struct evbuffer       evbuf_t;
 typedef struct event          event_t;
 typedef struct evconnlistener evserv_t;
@@ -46,10 +53,11 @@
 typedef struct evhtp_hooks    evhtp_hooks_t;
 typedef struct evhtp_hdr      evhtp_hdr_t;
 typedef struct evhtp_hdrs     evhtp_hdrs_t;
+typedef struct evhtp_callback evhtp_callback_t;
 
 typedef uint16_t              evhtp_res;
 typedef enum evhtp_hook_type  evhtp_hook_type;
-typedef enum http_method      evhtp_method;
+typedef enum htp_method      evhtp_method;
 typedef enum evhtp_proto      evhtp_proto;
 
 typedef int (*evhtp_hdrs_iter_cb)(evhtp_hdr_t * hdr, void * arg);
@@ -63,73 +71,76 @@
 typedef evhtp_res (*evhtp_hook_uri)(evhtp_request_t *, const char *, void *);
 typedef evhtp_res (*evhtp_hook_read)(evhtp_request_t *, const char *, size_t, void *);
 typedef evhtp_res (*evhtp_hook_on_expect)(evhtp_request_t *, const char *, void *);
+typedef evhtp_res (*evhtp_hook_finished)(evhtp_request_t *, void *);
 typedef evhtp_res (*evhtp_stream_cb)(evhtp_request_t *, void *);
 
-#define EVHTP_CLOSE_ON_EXPECT_ERR (1 << 1)
-#define EVHTP_CLOSE_ON_100        (1 << 2)
-#define EVHTP_CLOSE_ON_200        (1 << 3)
-#define EVHTP_CLOSE_ON_300        (1 << 4)
-#define EVHTP_CLOSE_ON_400        (1 << 5)
-#define EVHTP_CLOSE_ON_500        (1 << 6)
+#define EVHTP_FLAG_CLOSE_ON_EXPECT_ERR (1 << 1)
+#define EVHTP_FLAG_CLOSE_ON_100        (1 << 2)
+#define EVHTP_FLAG_CLOSE_ON_200        (1 << 3)
+#define EVHTP_FLAG_CLOSE_ON_300        (1 << 4)
+#define EVHTP_FLAG_CLOSE_ON_400        (1 << 5)
+#define EVHTP_FLAG_CLOSE_ON_500        (1 << 6)
 
-#define EVHTP_RES_SCREWEDUP       1
-#define EVHTP_RES_DONE            2
+#define EVHTP_RES_ERROR                0
+#define EVHTP_RES_SCREWEDUP            1
+#define EVHTP_RES_DONE                 2
+#define EVHTP_RES_PAUSE                3
 
-#define EVHTP_RES_100             100
-#define EVHTP_RES_CONTINUE        100
-#define EVHTP_RES_SWITCH_PROTO    101
-#define EVHTP_RES_PROCESSING      102
-#define EVHTP_RES_URI_TOOLONG     122
+#define EVHTP_RES_100                  100
+#define EVHTP_RES_CONTINUE             100
+#define EVHTP_RES_SWITCH_PROTO         101
+#define EVHTP_RES_PROCESSING           102
+#define EVHTP_RES_URI_TOOLONG          122
 
-#define EVHTP_RES_200             200
-#define EVHTP_RES_OK              200
-#define EVHTP_RES_CREATED         201
-#define EVHTP_RES_ACCEPTED        202
-#define EVHTP_RES_NAUTHINFO       203
-#define EVHTP_RES_NOCONTENT       204
-#define EVHTP_RES_RSTCONTENT      205
-#define EVHTP_RES_PARTIAL         206
-#define EVHTP_RES_MSTATUS         207
-#define EVHTP_RES_IMUSED          226
+#define EVHTP_RES_200                  200
+#define EVHTP_RES_OK                   200
+#define EVHTP_RES_CREATED              201
+#define EVHTP_RES_ACCEPTED             202
+#define EVHTP_RES_NAUTHINFO            203
+#define EVHTP_RES_NOCONTENT            204
+#define EVHTP_RES_RSTCONTENT           205
+#define EVHTP_RES_PARTIAL              206
+#define EVHTP_RES_MSTATUS              207
+#define EVHTP_RES_IMUSED               226
 
-#define EVHTP_RES_300             300
-#define EVHTP_RES_MCHOICE         300
-#define EVHTP_RES_MOVEDPERM       301
-#define EVHTP_RES_FOUND           302
-#define EVHTP_RES_SEEOTHER        303
-#define EVHTP_RES_NOTMOD          304
-#define EVHTP_RES_USEPROXY        305
-#define EVHTP_RES_SWITCHPROXY     306
-#define EVHTP_RES_TMPREDIR        307
+#define EVHTP_RES_300                  300
+#define EVHTP_RES_MCHOICE              300
+#define EVHTP_RES_MOVEDPERM            301
+#define EVHTP_RES_FOUND                302
+#define EVHTP_RES_SEEOTHER             303
+#define EVHTP_RES_NOTMOD               304
+#define EVHTP_RES_USEPROXY             305
+#define EVHTP_RES_SWITCHPROXY          306
+#define EVHTP_RES_TMPREDIR             307
 
-#define EVHTP_RES_400             400
-#define EVHTP_RES_BADREQ          400
-#define EVHTP_RES_UNAUTH          401
-#define EVHTP_RES_PAYREQ          402
-#define EVHTP_RES_FORBIDDEN       403
-#define EVHTP_RES_NOTFOUND        404
-#define EVHTP_RES_METHNALLOWED    405
-#define EVHTP_RES_NACCEPTABLE     406
-#define EVHTP_RES_PROXYAUTHREQ    407
-#define EVHTP_RES_TIMEOUT         408
-#define EVHTP_RES_CONFLICT        409
-#define EVHTP_RES_GONE            410
-#define EVHTP_RES_LENREQ          411
-#define EVHTP_RES_PRECONDFAIL     412
-#define EVHTP_RES_ENTOOLARGE      413
-#define EVHTP_RES_URITOOLARGE     414
-#define EVHTP_RES_UNSUPPORTED     415
-#define EVHTP_RES_RANGENOTSC      416
-#define EVHTP_RES_EXPECTFAIL      417
+#define EVHTP_RES_400                  400
+#define EVHTP_RES_BADREQ               400
+#define EVHTP_RES_UNAUTH               401
+#define EVHTP_RES_PAYREQ               402
+#define EVHTP_RES_FORBIDDEN            403
+#define EVHTP_RES_NOTFOUND             404
+#define EVHTP_RES_METHNALLOWED         405
+#define EVHTP_RES_NACCEPTABLE          406
+#define EVHTP_RES_PROXYAUTHREQ         407
+#define EVHTP_RES_TIMEOUT              408
+#define EVHTP_RES_CONFLICT             409
+#define EVHTP_RES_GONE                 410
+#define EVHTP_RES_LENREQ               411
+#define EVHTP_RES_PRECONDFAIL          412
+#define EVHTP_RES_ENTOOLARGE           413
+#define EVHTP_RES_URITOOLARGE          414
+#define EVHTP_RES_UNSUPPORTED          415
+#define EVHTP_RES_RANGENOTSC           416
+#define EVHTP_RES_EXPECTFAIL           417
 
-#define EVHTP_RES_500             500
-#define EVHTP_RES_SERVERR         500
-#define EVHTP_RES_NOTIMPL         501
-#define EVHTP_RES_BADGATEWAY      502
-#define EVHTP_RES_SERVUNAVAIL     503
-#define EVHTP_RES_GWTIMEOUT       504
-#define EVHTP_RES_VERNSUPPORT     505
-#define EVHTP_RES_BWEXEED         509
+#define EVHTP_RES_500                  500
+#define EVHTP_RES_SERVERR              500
+#define EVHTP_RES_NOTIMPL              501
+#define EVHTP_RES_BADGATEWAY           502
+#define EVHTP_RES_SERVUNAVAIL          503
+#define EVHTP_RES_GWTIMEOUT            504
+#define EVHTP_RES_VERNSUPPORT          505
+#define EVHTP_RES_BWEXEED              509
 
 #ifndef DISABLE_SSL
 typedef SSL_SESSION evhtp_ssl_sess_t;
@@ -195,17 +206,22 @@
 
 evhtp_t          * evhtp_new(evbase_t *);
 
+void               evhtp_disable_pausing(evhtp_t *);
+void               evhtp_set_pre_accept_cb(evhtp_t *, evhtp_pre_accept, void *);
+void               evhtp_set_post_accept_cb(evhtp_t *, evhtp_post_accept, void *);
 int                evhtp_set_server_name(evhtp_t *, char *);
-int                evhtp_set_cb(evhtp_t *, const char *, evhtp_callback_cb, void *);
-int                evhtp_set_regex_cb(evhtp_t *, const char *, evhtp_callback_cb, void *);
+evhtp_callback_t * evhtp_set_cb(evhtp_t *, const char *, evhtp_callback_cb, void *);
+evhtp_callback_t * evhtp_set_regex_cb(evhtp_t *, const char *, evhtp_callback_cb, void *);
 void               evhtp_set_gencb(evhtp_t * htp, evhtp_callback_cb cb, void * cbarg);
 void               evhtp_bind_socket(evhtp_t *, const char *, uint16_t);
 
-int                evhtp_conn_set_flags(evhtp_conn_t *, evhtp_cflags);
+int                evhtp_set_connection_flags(evhtp_conn_t *, evhtp_cflags);
 evhtp_t          * evhtp_conn_get_htp(evhtp_conn_t *);
 evhtp_ssl_t      * evhtp_conn_get_ssl(evhtp_conn_t *);
+evbev_t          * evhtp_conn_get_bev(evhtp_conn_t *);
 int                evhtp_conn_is_ssl(evhtp_conn_t *);
 
+evbev_t          * evhtp_request_get_bev(evhtp_request_t *);
 evbuf_t          * evhtp_request_get_input(evhtp_request_t *);
 evbuf_t          * evhtp_request_get_output(evhtp_request_t *);
 evbase_t         * evhtp_request_get_evbase(evhtp_request_t *);
@@ -218,23 +234,28 @@
 evhtp_hdrs_t     * evhtp_request_get_headers_out(evhtp_request_t *);
 evhtp_callback_cb  evhtp_request_get_cb(evhtp_request_t *);
 void             * evhtp_request_get_cbarg(evhtp_request_t *);
+void               evhtp_request_set_cbarg(evhtp_request_t *, void *);
 int                evhtp_request_get_sock(evhtp_request_t *);
 const char       * evhtp_request_get_path(evhtp_request_t *);
 const char       * evhtp_request_get_uri(evhtp_request_t *);
 const char       * evhtp_request_method_str(evhtp_request_t *);
+uint64_t           evhtp_request_content_length(evhtp_request_t *);
 int                evhtp_request_get_matched_soff(evhtp_request_t *);
 int                evhtp_request_get_matched_eoff(evhtp_request_t *);
-int64_t            evhtp_request_get_content_length(evhtp_request_t *);
 int                evhtp_request_is_ssl(evhtp_request_t *);
+char               evhtp_request_get_major(evhtp_request_t *);
+char               evhtp_request_get_minor(evhtp_request_t *);
+void               evhtp_request_pause(evhtp_request_t *);
+void               evhtp_request_resume(evhtp_request_t *);
 
 evbase_t         * evhtp_get_evbase(evhtp_t *);
 evserv_t         * evhtp_get_listener(evhtp_t *);
 char             * evhtp_get_server_name(evhtp_t *);
 int                evhtp_is_ssl(evhtp_t *);
 
-int                evhtp_set_hook(evhtp_conn_t *, evhtp_hook_type, void * cb, void * arg);
+int                evhtp_set_callback_hook(evhtp_callback_t *, evhtp_hook_type, void *, void *);
+int                evhtp_set_connection_hook(evhtp_conn_t *, evhtp_hook_type, void * cb, void * arg);
 void               evhtp_set_pre_accept_cb(evhtp_t *, evhtp_pre_accept, void *);
-void               evhtp_set_post_accept_cb(evhtp_t *, evhtp_post_accept, void *);
 void               evhtp_send_reply(evhtp_request_t *, evhtp_res, const char *, evbuf_t *);
 void               evhtp_send_reply_stream(evhtp_request_t *, evhtp_res, evhtp_stream_cb, void *);
 void               evhtp_send_stream(evhtp_request_t *, evbuf_t *);
@@ -249,6 +270,8 @@
 const char       * evhtp_hdr_get_val(evhtp_hdr_t *);
 void               evhtp_hdr_add(evhtp_hdrs_t *, evhtp_hdr_t *);
 int                evhtp_hdrs_for_each(evhtp_hdrs_t *, evhtp_hdrs_iter_cb, void *);
+evhtp_hdr_t      * evhtp_hdr_key_add(evhtp_hdrs_t *, const char *, size_t);
+evhtp_hdr_t      * evhtp_hdr_val_add(evhtp_hdrs_t *, const char *, size_t);
 
 void               evhtp_free(evhtp_t *);
 void               evhtp_request_free(evhtp_request_t *);
diff --git a/evthr/Makefile b/evthr/Makefile
index 860f02c..925356e 100644
--- a/evthr/Makefile
+++ b/evthr/Makefile
@@ -1,8 +1,8 @@
-SRC      = evthr.c 
+SRC      = evthr.c
 OUT      = libevthr.a
 OBJ      = $(SRC:.c=.o)
 INCLUDES = -I.
-CFLAGS   += -Wall -ggdb
+CFLAGS   += -Wall -Wextra -ggdb
 LDFLAGS  += -ggdb
 CC       = gcc
 
diff --git a/evthr/evthr.c b/evthr/evthr.c
index 0c5267b..8c1bf8d 100644
--- a/evthr/evthr.c
+++ b/evthr/evthr.c
@@ -19,6 +19,12 @@
 
 #include "evthr.h"
 
+#if (__GNUC__ > 2 || ( __GNUC__ == 2 && __GNUC__MINOR__ > 4)) && (!defined(__STRICT_ANSI__) || __STRICT_ANSI__ == 0)
+#define __unused__   __attribute__((unused))
+#else
+#define __unused__
+#endif
+
 #define _EVTHR_MAGIC 0x4d52
 
 typedef struct evthr_cmd        evthr_cmd_t;
@@ -72,7 +78,7 @@
 }
 
 static void
-_evthr_read_cmd(int sock, short which, void * args) {
+_evthr_read_cmd(int sock, short __unused__ which, void * args) {
     evthr_t   * thread;
     evthr_cmd_t cmd;
     int         avail = 0;
@@ -166,7 +172,7 @@
 
     thread->evbase = event_base_new();
     thread->event  = event_new(thread->evbase, thread->rdr,
-        EV_READ | EV_PERSIST, _evthr_read_cmd, args);
+                               EV_READ | EV_PERSIST, _evthr_read_cmd, args);
 
     event_add(thread->event, NULL);
     event_base_loop(thread->evbase, 0);
@@ -182,7 +188,7 @@
 evthr_res
 evthr_defer(evthr_t * thread, evthr_cb cb, void * arg) {
     int         cur_backlog;
-    evthr_cmd_t cmd = { 0 };
+    evthr_cmd_t cmd;
 
     cur_backlog = evthr_get_backlog(thread);
 
@@ -211,7 +217,7 @@
 
 evthr_res
 evthr_stop(evthr_t * thread) {
-    evthr_cmd_t cmd = { 0 };
+    evthr_cmd_t cmd;
 
     cmd.magic = _EVTHR_MAGIC;
     cmd.cb    = NULL;
diff --git a/http_parser/.gitignore b/http_parser/.gitignore
deleted file mode 100644
index 73fe6a4..0000000
--- a/http_parser/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-tags
-*.o
-test
-test_g
diff --git a/http_parser/CONTRIBUTIONS b/http_parser/CONTRIBUTIONS
deleted file mode 100644
index 11ba31e..0000000
--- a/http_parser/CONTRIBUTIONS
+++ /dev/null
@@ -1,4 +0,0 @@
-Contributors must agree to the Contributor License Agreement before patches
-can be accepted.
-
-http://spreadsheets2.google.com/viewform?hl=en&formkey=dDJXOGUwbzlYaWM4cHN1MERwQS1CSnc6MQ
diff --git a/http_parser/LICENSE-MIT b/http_parser/LICENSE-MIT
deleted file mode 100644
index 40ebce2..0000000
--- a/http_parser/LICENSE-MIT
+++ /dev/null
@@ -1,19 +0,0 @@
-Copyright Joyent, Inc. and other Node contributors. 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. 
diff --git a/http_parser/Makefile b/http_parser/Makefile
deleted file mode 100644
index 2b945c1..0000000
--- a/http_parser/Makefile
+++ /dev/null
@@ -1,41 +0,0 @@
-OPT_DEBUG=-O0 -g -Wall -Wextra -Werror -I.
-OPT_FAST=-O3 -DHTTP_PARSER_STRICT=0 -I.
-
-CC?=gcc
-
-
-test: test_g
-	./test_g
-
-test_g: http_parser_g.o test_g.o
-	$(CC) $(OPT_DEBUG) http_parser_g.o test_g.o -o $@
-
-test_g.o: test.c http_parser.h Makefile
-	$(CC) $(OPT_DEBUG) -c test.c -o $@
-
-test.o: test.c http_parser.h Makefile
-	$(CC) $(OPT_FAST) -c test.c -o $@
-
-http_parser_g.o: http_parser.c http_parser.h Makefile
-	$(CC) $(OPT_DEBUG) -c http_parser.c -o $@
-
-test-valgrind: test_g
-	valgrind ./test_g
-
-http_parser.o: http_parser.c http_parser.h Makefile
-	$(CC) $(OPT_FAST) -c http_parser.c
-
-test_fast: http_parser.o test.c http_parser.h
-	$(CC) $(OPT_FAST) http_parser.o test.c -o $@
-
-test-run-timed: test_fast
-	while(true) do time ./test_fast > /dev/null; done
-
-
-tags: http_parser.c http_parser.h test.c
-	ctags $^
-
-clean:
-	rm -f *.o test test_fast test_g http_parser.tar tags
-
-.PHONY: clean package test-run test-run-timed test-valgrind
diff --git a/http_parser/README.md b/http_parser/README.md
deleted file mode 100644
index 72332fb..0000000
--- a/http_parser/README.md
+++ /dev/null
@@ -1,171 +0,0 @@
-HTTP Parser
-===========
-
-This is a parser for HTTP messages written in C. It parses both requests and
-responses. The parser is designed to be used in performance HTTP
-applications. It does not make any syscalls nor allocations, it does not
-buffer data, it can be interrupted at anytime. Depending on your
-architecture, it only requires about 40 bytes of data per message
-stream (in a web server that is per connection).
-
-Features:
-
-  * No dependencies
-  * Handles persistent streams (keep-alive).
-  * Decodes chunked encoding.
-  * Upgrade support
-  * Defends against buffer overflow attacks.
-
-The parser extracts the following information from HTTP messages:
-
-  * Header fields and values
-  * Content-Length
-  * Request method
-  * Response status code
-  * Transfer-Encoding
-  * HTTP version
-  * Request path, query string, fragment
-  * Message body
-
-
-Usage
------
-
-One `http_parser` object is used per TCP connection. Initialize the struct
-using `http_parser_init()` and set the callbacks. That might look something
-like this for a request parser:
-
-    http_parser_settings settings;
-    settings.on_path = my_path_callback;
-    settings.on_header_field = my_header_field_callback;
-    /* ... */
-
-    http_parser *parser = malloc(sizeof(http_parser));
-    http_parser_init(parser, HTTP_REQUEST);
-    parser->data = my_socket;
-
-When data is received on the socket execute the parser and check for errors.
-
-    size_t len = 80*1024, nparsed;
-    char buf[len];
-    ssize_t recved;
-
-    recved = recv(fd, buf, len, 0);
-
-    if (recved < 0) {
-      /* Handle error. */
-    }
-
-    /* Start up / continue the parser.
-     * Note we pass recved==0 to signal that EOF has been recieved.
-     */
-    nparsed = http_parser_execute(parser, &settings, buf, recved);
-
-    if (parser->upgrade) {
-      /* handle new protocol */
-    } else if (nparsed != recved) {
-      /* Handle error. Usually just close the connection. */
-    }
-
-HTTP needs to know where the end of the stream is. For example, sometimes
-servers send responses without Content-Length and expect the client to
-consume input (for the body) until EOF. To tell http_parser about EOF, give
-`0` as the forth parameter to `http_parser_execute()`. Callbacks and errors
-can still be encountered during an EOF, so one must still be prepared
-to receive them.
-
-Scalar valued message information such as `status_code`, `method`, and the
-HTTP version are stored in the parser structure. This data is only
-temporally stored in `http_parser` and gets reset on each new message. If
-this information is needed later, copy it out of the structure during the
-`headers_complete` callback.
-
-The parser decodes the transfer-encoding for both requests and responses
-transparently. That is, a chunked encoding is decoded before being sent to
-the on_body callback.
-
-
-The Special Problem of Upgrade
-------------------------------
-
-HTTP supports upgrading the connection to a different protocol. An
-increasingly common example of this is the Web Socket protocol which sends
-a request like
-
-        GET /demo HTTP/1.1
-        Upgrade: WebSocket
-        Connection: Upgrade
-        Host: example.com
-        Origin: http://example.com
-        WebSocket-Protocol: sample
-
-followed by non-HTTP data.
-
-(See http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 for more
-information the Web Socket protocol.)
-
-To support this, the parser will treat this as a normal HTTP message without a
-body. Issuing both on_headers_complete and on_message_complete callbacks. However
-http_parser_execute() will stop parsing at the end of the headers and return.
-
-The user is expected to check if `parser->upgrade` has been set to 1 after
-`http_parser_execute()` returns. Non-HTTP data begins at the buffer supplied
-offset by the return value of `http_parser_execute()`.
-
-
-Callbacks
----------
-
-During the `http_parser_execute()` call, the callbacks set in
-`http_parser_settings` will be executed. The parser maintains state and
-never looks behind, so buffering the data is not necessary. If you need to
-save certain data for later usage, you can do that from the callbacks.
-
-There are two types of callbacks:
-
-* notification `typedef int (*http_cb) (http_parser*);`
-    Callbacks: on_message_begin, on_headers_complete, on_message_complete.
-* data `typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);`
-    Callbacks: (requests only) on_path, on_query_string, on_uri, on_fragment,
-               (common) on_header_field, on_header_value, on_body;
-
-Callbacks must return 0 on success. Returning a non-zero value indicates
-error to the parser, making it exit immediately.
-
-In case you parse HTTP message in chunks (i.e. `read()` request line
-from socket, parse, read half headers, parse, etc) your data callbacks
-may be called more than once. Http-parser guarantees that data pointer is only
-valid for the lifetime of callback. You can also `read()` into a heap allocated
-buffer to avoid copying memory around if this fits your application.
-
-Reading headers may be a tricky task if you read/parse headers partially.
-Basically, you need to remember whether last header callback was field or value
-and apply following logic:
-
-    (on_header_field and on_header_value shortened to on_h_*)
-     ------------------------ ------------ --------------------------------------------
-    | State (prev. callback) | Callback   | Description/action                         |
-     ------------------------ ------------ --------------------------------------------
-    | nothing (first call)   | on_h_field | Allocate new buffer and copy callback data |
-    |                        |            | into it                                    |
-     ------------------------ ------------ --------------------------------------------
-    | value                  | on_h_field | New header started.                        |
-    |                        |            | Copy current name,value buffers to headers |
-    |                        |            | list and allocate new buffer for new name  |
-     ------------------------ ------------ --------------------------------------------
-    | field                  | on_h_field | Previous name continues. Reallocate name   |
-    |                        |            | buffer and append callback data to it      |
-     ------------------------ ------------ --------------------------------------------
-    | field                  | on_h_value | Value for current header started. Allocate |
-    |                        |            | new buffer and copy callback data to it    |
-     ------------------------ ------------ --------------------------------------------
-    | value                  | on_h_value | Value continues. Reallocate value buffer   |
-    |                        |            | and append callback data to it             |
-     ------------------------ ------------ --------------------------------------------
-
-
-See examples of reading in headers:
-
-* [partial example](http://gist.github.com/155877) in C
-* [from http-parser tests](http://github.com/ry/http-parser/blob/37a0ff8928fb0d83cec0d0d8909c5a4abcd221af/test.c#L403) in C
-* [from Node library](http://github.com/ry/node/blob/842eaf446d2fdcb33b296c67c911c32a0dabc747/src/http.js#L284) in Javascript
diff --git a/http_parser/http_parser.c b/http_parser/http_parser.c
deleted file mode 100644
index 0fe0e8f..0000000
--- a/http_parser/http_parser.c
+++ /dev/null
@@ -1,1644 +0,0 @@
-/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to
- * deal in the Software without restriction, including without limitation the
- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- * IN THE SOFTWARE.
- */
-#include <http_parser.h>
-#include <assert.h>
-#include <stddef.h>
-
-
-#ifndef MIN
-# define MIN(a,b) ((a) < (b) ? (a) : (b))
-#endif
-
-
-#define CALLBACK2(FOR)                                               \
-do {                                                                 \
-  if (settings->on_##FOR) {                                          \
-    if (0 != settings->on_##FOR(parser)) return (p - data);          \
-  }                                                                  \
-} while (0)
-
-
-#define MARK(FOR)                                                    \
-do {                                                                 \
-  FOR##_mark = p;                                                    \
-} while (0)
-
-#define CALLBACK_NOCLEAR(FOR)                                        \
-do {                                                                 \
-  if (FOR##_mark) {                                                  \
-    if (settings->on_##FOR) {                                        \
-      if (0 != settings->on_##FOR(parser,                            \
-                                 FOR##_mark,                         \
-                                 p - FOR##_mark))                    \
-      {                                                              \
-        return (p - data);                                           \
-      }                                                              \
-    }                                                                \
-  }                                                                  \
-} while (0)
-
-
-#define CALLBACK(FOR)                                                \
-do {                                                                 \
-  CALLBACK_NOCLEAR(FOR);                                             \
-  FOR##_mark = NULL;                                                 \
-} while (0)
-
-
-#define PROXY_CONNECTION "proxy-connection"
-#define CONNECTION "connection"
-#define CONTENT_LENGTH "content-length"
-#define TRANSFER_ENCODING "transfer-encoding"
-#define UPGRADE "upgrade"
-#define CHUNKED "chunked"
-#define KEEP_ALIVE "keep-alive"
-#define CLOSE "close"
-
-
-static const char *method_strings[] =
-  { "DELETE"
-  , "GET"
-  , "HEAD"
-  , "POST"
-  , "PUT"
-  , "CONNECT"
-  , "OPTIONS"
-  , "TRACE"
-  , "COPY"
-  , "LOCK"
-  , "MKCOL"
-  , "MOVE"
-  , "PROPFIND"
-  , "PROPPATCH"
-  , "UNLOCK"
-  , "REPORT"
-  , "MKACTIVITY"
-  , "CHECKOUT"
-  , "MERGE"
-  , "M-SEARCH"
-  , "NOTIFY"
-  , "SUBSCRIBE"
-  , "UNSUBSCRIBE"
-  };
-
-
-/* Tokens as defined by rfc 2616. Also lowercases them.
- *        token       = 1*<any CHAR except CTLs or separators>
- *     separators     = "(" | ")" | "<" | ">" | "@"
- *                    | "," | ";" | ":" | "\" | <">
- *                    | "/" | "[" | "]" | "?" | "="
- *                    | "{" | "}" | SP | HT
- */
-static const char tokens[256] = {
-/*   0 nul    1 soh    2 stx    3 etx    4 eot    5 enq    6 ack    7 bel  */
-        0,       0,       0,       0,       0,       0,       0,       0,
-/*   8 bs     9 ht    10 nl    11 vt    12 np    13 cr    14 so    15 si   */
-        0,       0,       0,       0,       0,       0,       0,       0,
-/*  16 dle   17 dc1   18 dc2   19 dc3   20 dc4   21 nak   22 syn   23 etb */
-        0,       0,       0,       0,       0,       0,       0,       0,
-/*  24 can   25 em    26 sub   27 esc   28 fs    29 gs    30 rs    31 us  */
-        0,       0,       0,       0,       0,       0,       0,       0,
-/*  32 sp    33  !    34  "    35  #    36  $    37  %    38  &    39  '  */
-       ' ',      '!',     '"',     '#',     '$',     '%',     '&',    '\'',
-/*  40  (    41  )    42  *    43  +    44  ,    45  -    46  .    47  /  */
-        0,       0,      '*',     '+',      0,      '-',     '.',     '/',
-/*  48  0    49  1    50  2    51  3    52  4    53  5    54  6    55  7  */
-       '0',     '1',     '2',     '3',     '4',     '5',     '6',     '7',
-/*  56  8    57  9    58  :    59  ;    60  <    61  =    62  >    63  ?  */
-       '8',     '9',      0,       0,       0,       0,       0,       0,
-/*  64  @    65  A    66  B    67  C    68  D    69  E    70  F    71  G  */
-        0,      'a',     'b',     'c',     'd',     'e',     'f',     'g',
-/*  72  H    73  I    74  J    75  K    76  L    77  M    78  N    79  O  */
-       'h',     'i',     'j',     'k',     'l',     'm',     'n',     'o',
-/*  80  P    81  Q    82  R    83  S    84  T    85  U    86  V    87  W  */
-       'p',     'q',     'r',     's',     't',     'u',     'v',     'w',
-/*  88  X    89  Y    90  Z    91  [    92  \    93  ]    94  ^    95  _  */
-       'x',     'y',     'z',      0,       0,       0,      '^',     '_',
-/*  96  `    97  a    98  b    99  c   100  d   101  e   102  f   103  g  */
-       '`',     'a',     'b',     'c',     'd',     'e',     'f',     'g',
-/* 104  h   105  i   106  j   107  k   108  l   109  m   110  n   111  o  */
-       'h',     'i',     'j',     'k',     'l',     'm',     'n',     'o',
-/* 112  p   113  q   114  r   115  s   116  t   117  u   118  v   119  w  */
-       'p',     'q',     'r',     's',     't',     'u',     'v',     'w',
-/* 120  x   121  y   122  z   123  {   124  |   125  }   126  ~   127 del */
-       'x',     'y',     'z',      0,      '|',     '}',     '~',       0 };
-
-
-static const int8_t unhex[256] =
-  {-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
-  };
-
-
-static const uint8_t normal_url_char[256] = {
-/*   0 nul    1 soh    2 stx    3 etx    4 eot    5 enq    6 ack    7 bel  */
-        0,       0,       0,       0,       0,       0,       0,       0,
-/*   8 bs     9 ht    10 nl    11 vt    12 np    13 cr    14 so    15 si   */
-        0,       0,       0,       0,       0,       0,       0,       0,
-/*  16 dle   17 dc1   18 dc2   19 dc3   20 dc4   21 nak   22 syn   23 etb */
-        0,       0,       0,       0,       0,       0,       0,       0,
-/*  24 can   25 em    26 sub   27 esc   28 fs    29 gs    30 rs    31 us  */
-        0,       0,       0,       0,       0,       0,       0,       0,
-/*  32 sp    33  !    34  "    35  #    36  $    37  %    38  &    39  '  */
-        0,       1,       1,       0,       1,       1,       1,       1,
-/*  40  (    41  )    42  *    43  +    44  ,    45  -    46  .    47  /  */
-        1,       1,       1,       1,       1,       1,       1,       1,
-/*  48  0    49  1    50  2    51  3    52  4    53  5    54  6    55  7  */
-        1,       1,       1,       1,       1,       1,       1,       1,
-/*  56  8    57  9    58  :    59  ;    60  <    61  =    62  >    63  ?  */
-        1,       1,       1,       1,       1,       1,       1,       0,
-/*  64  @    65  A    66  B    67  C    68  D    69  E    70  F    71  G  */
-        1,       1,       1,       1,       1,       1,       1,       1,
-/*  72  H    73  I    74  J    75  K    76  L    77  M    78  N    79  O  */
-        1,       1,       1,       1,       1,       1,       1,       1,
-/*  80  P    81  Q    82  R    83  S    84  T    85  U    86  V    87  W  */
-        1,       1,       1,       1,       1,       1,       1,       1,
-/*  88  X    89  Y    90  Z    91  [    92  \    93  ]    94  ^    95  _  */
-        1,       1,       1,       1,       1,       1,       1,       1,
-/*  96  `    97  a    98  b    99  c   100  d   101  e   102  f   103  g  */
-        1,       1,       1,       1,       1,       1,       1,       1,
-/* 104  h   105  i   106  j   107  k   108  l   109  m   110  n   111  o  */
-        1,       1,       1,       1,       1,       1,       1,       1,
-/* 112  p   113  q   114  r   115  s   116  t   117  u   118  v   119  w  */
-        1,       1,       1,       1,       1,       1,       1,       1,
-/* 120  x   121  y   122  z   123  {   124  |   125  }   126  ~   127 del */
-        1,       1,       1,       1,       1,       1,       1,       0,
-
-/* Remainder of non-ASCII range are accepted as-is to support implicitly UTF-8
-   encoded paths. This is out of spec, but clients generate this and most other
-   HTTP servers support it. We should, too. */
-
-        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,
-        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,
-        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 };
-
-
-enum state
-  { s_dead = 1 /* important that this is > 0 */
-
-  , s_start_req_or_res
-  , s_res_or_resp_H
-  , s_start_res
-  , s_res_H
-  , s_res_HT
-  , s_res_HTT
-  , s_res_HTTP
-  , s_res_first_http_major
-  , s_res_http_major
-  , s_res_first_http_minor
-  , s_res_http_minor
-  , s_res_first_status_code
-  , s_res_status_code
-  , s_res_status
-  , s_res_line_almost_done
-
-  , s_start_req
-
-  , s_req_method
-  , s_req_spaces_before_url
-  , s_req_schema
-  , s_req_schema_slash
-  , s_req_schema_slash_slash
-  , s_req_host
-  , s_req_port
-  , s_req_path
-  , s_req_query_string_start
-  , s_req_query_string
-  , s_req_fragment_start
-  , s_req_fragment
-  , s_req_http_start
-  , s_req_http_H
-  , s_req_http_HT
-  , s_req_http_HTT
-  , s_req_http_HTTP
-  , s_req_first_http_major
-  , s_req_http_major
-  , s_req_first_http_minor
-  , s_req_http_minor
-  , s_req_line_almost_done
-
-  , s_header_field_start
-  , s_header_field
-  , s_header_value_start
-  , s_header_value
-
-  , s_header_almost_done
-
-  , s_chunk_size_start
-  , s_chunk_size
-  , s_chunk_parameters
-  , s_chunk_size_almost_done
-  
-  , s_headers_almost_done
-  /* Important: 's_headers_almost_done' must be the last 'header' state. All
-   * states beyond this must be 'body' states. It is used for overflow
-   * checking. See the PARSING_HEADER() macro.
-   */
-
-  , s_chunk_data
-  , s_chunk_data_almost_done
-  , s_chunk_data_done
-
-  , s_body_identity
-  , s_body_identity_eof
-  };
-
-
-#define PARSING_HEADER(state) (state <= s_headers_almost_done)
-
-
-enum header_states
-  { h_general = 0
-  , h_C
-  , h_CO
-  , h_CON
-
-  , h_matching_connection
-  , h_matching_proxy_connection
-  , h_matching_content_length
-  , h_matching_transfer_encoding
-  , h_matching_upgrade
-
-  , h_connection
-  , h_content_length
-  , h_transfer_encoding
-  , h_upgrade
-
-  , h_matching_transfer_encoding_chunked
-  , h_matching_connection_keep_alive
-  , h_matching_connection_close
-
-  , h_transfer_encoding_chunked
-  , h_connection_keep_alive
-  , h_connection_close
-  };
-
-
-enum flags
-  { F_CHUNKED               = 1 << 0
-  , F_CONNECTION_KEEP_ALIVE = 1 << 1
-  , F_CONNECTION_CLOSE      = 1 << 2
-  , F_TRAILING              = 1 << 3
-  , F_UPGRADE               = 1 << 4
-  , F_SKIPBODY              = 1 << 5
-  };
-
-
-#define CR '\r'
-#define LF '\n'
-#define LOWER(c) (unsigned char)(c | 0x20)
-#define TOKEN(c) tokens[(unsigned char)c]
-
-
-#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res)
-
-
-#if HTTP_PARSER_STRICT
-# define STRICT_CHECK(cond) if (cond) goto error
-# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead)
-#else
-# define STRICT_CHECK(cond)
-# define NEW_MESSAGE() start_state
-#endif
-
-
-size_t http_parser_execute (http_parser *parser,
-                            const http_parser_settings *settings,
-                            const char *data,
-                            size_t len)
-{
-  char c, ch;
-  const char *p = data, *pe;
-  int64_t to_read;
-
-  enum state state = (enum state) parser->state;
-  enum header_states header_state = (enum header_states) parser->header_state;
-  uint64_t index = parser->index;
-  uint64_t nread = parser->nread;
-
-  if (len == 0) {
-    switch (state) {
-      case s_body_identity_eof:
-        CALLBACK2(message_complete);
-        return 0;
-
-      case s_dead:
-      case s_start_req_or_res:
-      case s_start_res:
-      case s_start_req:
-        return 0;
-
-      default:
-        return 1; // error
-    }
-  }
-
-  /* technically we could combine all of these (except for url_mark) into one
-     variable, saving stack space, but it seems more clear to have them
-     separated. */
-  const char *header_field_mark = 0;
-  const char *header_value_mark = 0;
-  const char *fragment_mark = 0;
-  const char *query_string_mark = 0;
-  const char *path_mark = 0;
-  const char *url_mark = 0;
-
-  if (state == s_header_field)
-    header_field_mark = data;
-  if (state == s_header_value)
-    header_value_mark = data;
-  if (state == s_req_fragment)
-    fragment_mark = data;
-  if (state == s_req_query_string)
-    query_string_mark = data;
-  if (state == s_req_path)
-    path_mark = data;
-  if (state == s_req_path || state == s_req_schema || state == s_req_schema_slash
-      || state == s_req_schema_slash_slash || state == s_req_port
-      || state == s_req_query_string_start || state == s_req_query_string
-      || state == s_req_host
-      || state == s_req_fragment_start || state == s_req_fragment)
-    url_mark = data;
-
-  for (p=data, pe=data+len; p != pe; p++) {
-    ch = *p;
-
-    if (PARSING_HEADER(state)) {
-      ++nread;
-      /* Buffer overflow attack */
-      if (nread > HTTP_MAX_HEADER_SIZE) goto error;
-    }
-
-    switch (state) {
-
-      case s_dead:
-        /* this state is used after a 'Connection: close' message
-         * the parser will error out if it reads another message
-         */
-        goto error;
-
-      case s_start_req_or_res:
-      {
-        if (ch == CR || ch == LF)
-          break;
-        parser->flags = 0;
-        parser->content_length = -1;
-
-        CALLBACK2(message_begin);
-
-        if (ch == 'H')
-          state = s_res_or_resp_H;
-        else {
-          parser->type = HTTP_REQUEST;
-          goto start_req_method_assign;
-        }
-        break;
-      }
-
-      case s_res_or_resp_H:
-        if (ch == 'T') {
-          parser->type = HTTP_RESPONSE;
-          state = s_res_HT;
-        } else {
-          if (ch != 'E') goto error;
-          parser->type = HTTP_REQUEST;
-          parser->method = HTTP_HEAD;
-          index = 2;
-          state = s_req_method;
-        }
-        break;
-
-      case s_start_res:
-      {
-        parser->flags = 0;
-        parser->content_length = -1;
-
-        CALLBACK2(message_begin);
-
-        switch (ch) {
-          case 'H':
-            state = s_res_H;
-            break;
-
-          case CR:
-          case LF:
-            break;
-
-          default:
-            goto error;
-        }
-        break;
-      }
-
-      case s_res_H:
-        STRICT_CHECK(ch != 'T');
-        state = s_res_HT;
-        break;
-
-      case s_res_HT:
-        STRICT_CHECK(ch != 'T');
-        state = s_res_HTT;
-        break;
-
-      case s_res_HTT:
-        STRICT_CHECK(ch != 'P');
-        state = s_res_HTTP;
-        break;
-
-      case s_res_HTTP:
-        STRICT_CHECK(ch != '/');
-        state = s_res_first_http_major;
-        break;
-
-      case s_res_first_http_major:
-        if (ch < '1' || ch > '9') goto error;
-        parser->http_major = ch - '0';
-        state = s_res_http_major;
-        break;
-
-      /* major HTTP version or dot */
-      case s_res_http_major:
-      {
-        if (ch == '.') {
-          state = s_res_first_http_minor;
-          break;
-        }
-
-        if (ch < '0' || ch > '9') goto error;
-
-        parser->http_major *= 10;
-        parser->http_major += ch - '0';
-
-        if (parser->http_major > 999) goto error;
-        break;
-      }
-
-      /* first digit of minor HTTP version */
-      case s_res_first_http_minor:
-        if (ch < '0' || ch > '9') goto error;
-        parser->http_minor = ch - '0';
-        state = s_res_http_minor;
-        break;
-
-      /* minor HTTP version or end of request line */
-      case s_res_http_minor:
-      {
-        if (ch == ' ') {
-          state = s_res_first_status_code;
-          break;
-        }
-
-        if (ch < '0' || ch > '9') goto error;
-
-        parser->http_minor *= 10;
-        parser->http_minor += ch - '0';
-
-        if (parser->http_minor > 999) goto error;
-        break;
-      }
-
-      case s_res_first_status_code:
-      {
-        if (ch < '0' || ch > '9') {
-          if (ch == ' ') {
-            break;
-          }
-          goto error;
-        }
-        parser->status_code = ch - '0';
-        state = s_res_status_code;
-        break;
-      }
-
-      case s_res_status_code:
-      {
-        if (ch < '0' || ch > '9') {
-          switch (ch) {
-            case ' ':
-              state = s_res_status;
-              break;
-            case CR:
-              state = s_res_line_almost_done;
-              break;
-            case LF:
-              state = s_header_field_start;
-              break;
-            default:
-              goto error;
-          }
-          break;
-        }
-
-        parser->status_code *= 10;
-        parser->status_code += ch - '0';
-
-        if (parser->status_code > 999) goto error;
-        break;
-      }
-
-      case s_res_status:
-        /* the human readable status. e.g. "NOT FOUND"
-         * we are not humans so just ignore this */
-        if (ch == CR) {
-          state = s_res_line_almost_done;
-          break;
-        }
-
-        if (ch == LF) {
-          state = s_header_field_start;
-          break;
-        }
-        break;
-
-      case s_res_line_almost_done:
-        STRICT_CHECK(ch != LF);
-        state = s_header_field_start;
-        break;
-
-      case s_start_req:
-      {
-        if (ch == CR || ch == LF)
-          break;
-        parser->flags = 0;
-        parser->content_length = -1;
-
-        CALLBACK2(message_begin);
-
-        if (ch < 'A' || 'Z' < ch) goto error;
-
-      start_req_method_assign:
-        parser->method = (enum http_method) 0;
-        index = 1;
-        switch (ch) {
-          case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break;
-          case 'D': parser->method = HTTP_DELETE; break;
-          case 'G': parser->method = HTTP_GET; break;
-          case 'H': parser->method = HTTP_HEAD; break;
-          case 'L': parser->method = HTTP_LOCK; break;
-          case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH */ break;
-          case 'N': parser->method = HTTP_NOTIFY; break;
-          case 'O': parser->method = HTTP_OPTIONS; break;
-          case 'P': parser->method = HTTP_POST; /* or PROPFIND or PROPPATCH or PUT */ break;
-          case 'R': parser->method = HTTP_REPORT; break;
-          case 'S': parser->method = HTTP_SUBSCRIBE; break;
-          case 'T': parser->method = HTTP_TRACE; break;
-          case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break;
-          default: goto error;
-        }
-        state = s_req_method;
-        break;
-      }
-
-      case s_req_method:
-      {
-        if (ch == '\0')
-          goto error;
-
-        const char *matcher = method_strings[parser->method];
-        if (ch == ' ' && matcher[index] == '\0') {
-          state = s_req_spaces_before_url;
-        } else if (ch == matcher[index]) {
-          ; /* nada */
-        } else if (parser->method == HTTP_CONNECT) {
-          if (index == 1 && ch == 'H') {
-            parser->method = HTTP_CHECKOUT;
-          } else if (index == 2  && ch == 'P') {
-            parser->method = HTTP_COPY;
-          }
-        } else if (parser->method == HTTP_MKCOL) {
-          if (index == 1 && ch == 'O') {
-            parser->method = HTTP_MOVE;
-          } else if (index == 1 && ch == 'E') {
-            parser->method = HTTP_MERGE;
-          } else if (index == 1 && ch == '-') {
-            parser->method = HTTP_MSEARCH;
-          } else if (index == 2 && ch == 'A') {
-            parser->method = HTTP_MKACTIVITY;
-          }
-        } else if (index == 1 && parser->method == HTTP_POST && ch == 'R') {
-          parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */
-        } else if (index == 1 && parser->method == HTTP_POST && ch == 'U') {
-          parser->method = HTTP_PUT;
-        } else if (index == 2 && parser->method == HTTP_UNLOCK && ch == 'S') {
-          parser->method = HTTP_UNSUBSCRIBE;
-        } else if (index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') {
-          parser->method = HTTP_PROPPATCH;
-        } else {
-          goto error;
-        }
-
-        ++index;
-        break;
-      }
-      case s_req_spaces_before_url:
-      {
-        if (ch == ' ') break;
-
-        if (ch == '/' || ch == '*') {
-          MARK(url);
-          MARK(path);
-          state = s_req_path;
-          break;
-        }
-
-        c = LOWER(ch);
-
-        if (c >= 'a' && c <= 'z') {
-          MARK(url);
-          state = s_req_schema;
-          break;
-        }
-
-        goto error;
-      }
-
-      case s_req_schema:
-      {
-        c = LOWER(ch);
-
-        if (c >= 'a' && c <= 'z') break;
-
-        if (ch == ':') {
-          state = s_req_schema_slash;
-          break;
-        } else if (ch == '.') {
-          state = s_req_host;
-          break;
-        } else if ('0' <= ch && ch <= '9') {
-          state = s_req_host;
-          break;
-        }
-
-        goto error;
-      }
-
-      case s_req_schema_slash:
-        STRICT_CHECK(ch != '/');
-        state = s_req_schema_slash_slash;
-        break;
-
-      case s_req_schema_slash_slash:
-        STRICT_CHECK(ch != '/');
-        state = s_req_host;
-        break;
-
-      case s_req_host:
-      {
-        c = LOWER(ch);
-        if (c >= 'a' && c <= 'z') break;
-        if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') break;
-        switch (ch) {
-          case ':':
-            state = s_req_port;
-            break;
-          case '/':
-            MARK(path);
-            state = s_req_path;
-            break;
-          case ' ':
-            /* The request line looks like:
-             *   "GET http://foo.bar.com HTTP/1.1"
-             * That is, there is no path.
-             */
-            CALLBACK(url);
-            state = s_req_http_start;
-            break;
-          case '?':
-            state = s_req_query_string_start;
-            break;
-          default:
-            goto error;
-        }
-        break;
-      }
-
-      case s_req_port:
-      {
-        if (ch >= '0' && ch <= '9') break;
-        switch (ch) {
-          case '/':
-            MARK(path);
-            state = s_req_path;
-            break;
-          case ' ':
-            /* The request line looks like:
-             *   "GET http://foo.bar.com:1234 HTTP/1.1"
-             * That is, there is no path.
-             */
-            CALLBACK(url);
-            state = s_req_http_start;
-            break;
-          case '?':
-            state = s_req_query_string_start;
-            break;
-          default:
-            goto error;
-        }
-        break;
-      }
-
-      case s_req_path:
-      {
-        if (normal_url_char[(unsigned char)ch]) break;
-
-        switch (ch) {
-          case ' ':
-            CALLBACK(url);
-            CALLBACK(path);
-            state = s_req_http_start;
-            break;
-          case CR:
-            CALLBACK(url);
-            CALLBACK(path);
-            parser->http_major = 0;
-            parser->http_minor = 9;
-            state = s_req_line_almost_done;
-            break;
-          case LF:
-            CALLBACK(url);
-            CALLBACK(path);
-            parser->http_major = 0;
-            parser->http_minor = 9;
-            state = s_header_field_start;
-            break;
-          case '?':
-            CALLBACK(path);
-            state = s_req_query_string_start;
-            break;
-          case '#':
-            CALLBACK(path);
-            state = s_req_fragment_start;
-            break;
-          default:
-            goto error;
-        }
-        break;
-      }
-
-      case s_req_query_string_start:
-      {
-        if (normal_url_char[(unsigned char)ch]) {
-          MARK(query_string);
-          state = s_req_query_string;
-          break;
-        }
-
-        switch (ch) {
-          case '?':
-            break; /* XXX ignore extra '?' ... is this right? */
-          case ' ':
-            CALLBACK(url);
-            state = s_req_http_start;
-            break;
-          case CR:
-            CALLBACK(url);
-            parser->http_major = 0;
-            parser->http_minor = 9;
-            state = s_req_line_almost_done;
-            break;
-          case LF:
-            CALLBACK(url);
-            parser->http_major = 0;
-            parser->http_minor = 9;
-            state = s_header_field_start;
-            break;
-          case '#':
-            state = s_req_fragment_start;
-            break;
-          default:
-            goto error;
-        }
-        break;
-      }
-
-      case s_req_query_string:
-      {
-        if (normal_url_char[(unsigned char)ch]) break;
-
-        switch (ch) {
-          case '?':
-            /* allow extra '?' in query string */
-            break;
-          case ' ':
-            CALLBACK(url);
-            CALLBACK(query_string);
-            state = s_req_http_start;
-            break;
-          case CR:
-            CALLBACK(url);
-            CALLBACK(query_string);
-            parser->http_major = 0;
-            parser->http_minor = 9;
-            state = s_req_line_almost_done;
-            break;
-          case LF:
-            CALLBACK(url);
-            CALLBACK(query_string);
-            parser->http_major = 0;
-            parser->http_minor = 9;
-            state = s_header_field_start;
-            break;
-          case '#':
-            CALLBACK(query_string);
-            state = s_req_fragment_start;
-            break;
-          default:
-            goto error;
-        }
-        break;
-      }
-
-      case s_req_fragment_start:
-      {
-        if (normal_url_char[(unsigned char)ch]) {
-          MARK(fragment);
-          state = s_req_fragment;
-          break;
-        }
-
-        switch (ch) {
-          case ' ':
-            CALLBACK(url);
-            state = s_req_http_start;
-            break;
-          case CR:
-            CALLBACK(url);
-            parser->http_major = 0;
-            parser->http_minor = 9;
-            state = s_req_line_almost_done;
-            break;
-          case LF:
-            CALLBACK(url);
-            parser->http_major = 0;
-            parser->http_minor = 9;
-            state = s_header_field_start;
-            break;
-          case '?':
-            MARK(fragment);
-            state = s_req_fragment;
-            break;
-          case '#':
-            break;
-          default:
-            goto error;
-        }
-        break;
-      }
-
-      case s_req_fragment:
-      {
-        if (normal_url_char[(unsigned char)ch]) break;
-
-        switch (ch) {
-          case ' ':
-            CALLBACK(url);
-            CALLBACK(fragment);
-            state = s_req_http_start;
-            break;
-          case CR:
-            CALLBACK(url);
-            CALLBACK(fragment);
-            parser->http_major = 0;
-            parser->http_minor = 9;
-            state = s_req_line_almost_done;
-            break;
-          case LF:
-            CALLBACK(url);
-            CALLBACK(fragment);
-            parser->http_major = 0;
-            parser->http_minor = 9;
-            state = s_header_field_start;
-            break;
-          case '?':
-          case '#':
-            break;
-          default:
-            goto error;
-        }
-        break;
-      }
-
-      case s_req_http_start:
-        switch (ch) {
-          case 'H':
-            state = s_req_http_H;
-            break;
-          case ' ':
-            break;
-          default:
-            goto error;
-        }
-        break;
-
-      case s_req_http_H:
-        STRICT_CHECK(ch != 'T');
-        state = s_req_http_HT;
-        break;
-
-      case s_req_http_HT:
-        STRICT_CHECK(ch != 'T');
-        state = s_req_http_HTT;
-        break;
-
-      case s_req_http_HTT:
-        STRICT_CHECK(ch != 'P');
-        state = s_req_http_HTTP;
-        break;
-
-      case s_req_http_HTTP:
-        STRICT_CHECK(ch != '/');
-        state = s_req_first_http_major;
-        break;
-
-      /* first digit of major HTTP version */
-      case s_req_first_http_major:
-        if (ch < '1' || ch > '9') goto error;
-        parser->http_major = ch - '0';
-        state = s_req_http_major;
-        break;
-
-      /* major HTTP version or dot */
-      case s_req_http_major:
-      {
-        if (ch == '.') {
-          state = s_req_first_http_minor;
-          break;
-        }
-
-        if (ch < '0' || ch > '9') goto error;
-
-        parser->http_major *= 10;
-        parser->http_major += ch - '0';
-
-        if (parser->http_major > 999) goto error;
-        break;
-      }
-
-      /* first digit of minor HTTP version */
-      case s_req_first_http_minor:
-        if (ch < '0' || ch > '9') goto error;
-        parser->http_minor = ch - '0';
-        state = s_req_http_minor;
-        break;
-
-      /* minor HTTP version or end of request line */
-      case s_req_http_minor:
-      {
-        if (ch == CR) {
-          state = s_req_line_almost_done;
-          break;
-        }
-
-        if (ch == LF) {
-          state = s_header_field_start;
-          break;
-        }
-
-        /* XXX allow spaces after digit? */
-
-        if (ch < '0' || ch > '9') goto error;
-
-        parser->http_minor *= 10;
-        parser->http_minor += ch - '0';
-
-        if (parser->http_minor > 999) goto error;
-        break;
-      }
-
-      /* end of request line */
-      case s_req_line_almost_done:
-      {
-        if (ch != LF) goto error;
-        state = s_header_field_start;
-        break;
-      }
-
-      case s_header_field_start:
-      {
-        if (ch == CR) {
-          state = s_headers_almost_done;
-          break;
-        }
-
-        if (ch == LF) {
-          /* they might be just sending \n instead of \r\n so this would be
-           * the second \n to denote the end of headers*/
-          state = s_headers_almost_done;
-          goto headers_almost_done;
-        }
-
-        c = TOKEN(ch);
-
-        if (!c) goto error;
-
-        MARK(header_field);
-
-        index = 0;
-        state = s_header_field;
-
-        switch (c) {
-          case 'c':
-            header_state = h_C;
-            break;
-
-          case 'p':
-            header_state = h_matching_proxy_connection;
-            break;
-
-          case 't':
-            header_state = h_matching_transfer_encoding;
-            break;
-
-          case 'u':
-            header_state = h_matching_upgrade;
-            break;
-
-          default:
-            header_state = h_general;
-            break;
-        }
-        break;
-      }
-
-      case s_header_field:
-      {
-        c = TOKEN(ch);
-
-        if (c) {
-          switch (header_state) {
-            case h_general:
-              break;
-
-            case h_C:
-              index++;
-              header_state = (c == 'o' ? h_CO : h_general);
-              break;
-
-            case h_CO:
-              index++;
-              header_state = (c == 'n' ? h_CON : h_general);
-              break;
-
-            case h_CON:
-              index++;
-              switch (c) {
-                case 'n':
-                  header_state = h_matching_connection;
-                  break;
-                case 't':
-                  header_state = h_matching_content_length;
-                  break;
-                default:
-                  header_state = h_general;
-                  break;
-              }
-              break;
-
-            /* connection */
-
-            case h_matching_connection:
-              index++;
-              if (index > sizeof(CONNECTION)-1
-                  || c != CONNECTION[index]) {
-                header_state = h_general;
-              } else if (index == sizeof(CONNECTION)-2) {
-                header_state = h_connection;
-              }
-              break;
-
-            /* proxy-connection */
-
-            case h_matching_proxy_connection:
-              index++;
-              if (index > sizeof(PROXY_CONNECTION)-1
-                  || c != PROXY_CONNECTION[index]) {
-                header_state = h_general;
-              } else if (index == sizeof(PROXY_CONNECTION)-2) {
-                header_state = h_connection;
-              }
-              break;
-
-            /* content-length */
-
-            case h_matching_content_length:
-              index++;
-              if (index > sizeof(CONTENT_LENGTH)-1
-                  || c != CONTENT_LENGTH[index]) {
-                header_state = h_general;
-              } else if (index == sizeof(CONTENT_LENGTH)-2) {
-                header_state = h_content_length;
-              }
-              break;
-
-            /* transfer-encoding */
-
-            case h_matching_transfer_encoding:
-              index++;
-              if (index > sizeof(TRANSFER_ENCODING)-1
-                  || c != TRANSFER_ENCODING[index]) {
-                header_state = h_general;
-              } else if (index == sizeof(TRANSFER_ENCODING)-2) {
-                header_state = h_transfer_encoding;
-              }
-              break;
-
-            /* upgrade */
-
-            case h_matching_upgrade:
-              index++;
-              if (index > sizeof(UPGRADE)-1
-                  || c != UPGRADE[index]) {
-                header_state = h_general;
-              } else if (index == sizeof(UPGRADE)-2) {
-                header_state = h_upgrade;
-              }
-              break;
-
-            case h_connection:
-            case h_content_length:
-            case h_transfer_encoding:
-            case h_upgrade:
-              if (ch != ' ') header_state = h_general;
-              break;
-
-            default:
-              assert(0 && "Unknown header_state");
-              break;
-          }
-          break;
-        }
-
-        if (ch == ':') {
-          CALLBACK(header_field);
-          state = s_header_value_start;
-          break;
-        }
-
-        if (ch == CR) {
-          state = s_header_almost_done;
-          CALLBACK(header_field);
-          break;
-        }
-
-        if (ch == LF) {
-          CALLBACK(header_field);
-          state = s_header_field_start;
-          break;
-        }
-
-        goto error;
-      }
-
-      case s_header_value_start:
-      {
-        if (ch == ' ') break;
-
-        MARK(header_value);
-
-        state = s_header_value;
-        index = 0;
-
-        c = LOWER(ch);
-
-        if (ch == CR) {
-          CALLBACK(header_value);
-          header_state = h_general;
-          state = s_header_almost_done;
-          break;
-        }
-
-        if (ch == LF) {
-          CALLBACK(header_value);
-          state = s_header_field_start;
-          break;
-        }
-
-        switch (header_state) {
-          case h_upgrade:
-            parser->flags |= F_UPGRADE;
-            header_state = h_general;
-            break;
-
-          case h_transfer_encoding:
-            /* looking for 'Transfer-Encoding: chunked' */
-            if ('c' == c) {
-              header_state = h_matching_transfer_encoding_chunked;
-            } else {
-              header_state = h_general;
-            }
-            break;
-
-          case h_content_length:
-            if (ch < '0' || ch > '9') goto error;
-            parser->content_length = ch - '0';
-            break;
-
-          case h_connection:
-            /* looking for 'Connection: keep-alive' */
-            if (c == 'k') {
-              header_state = h_matching_connection_keep_alive;
-            /* looking for 'Connection: close' */
-            } else if (c == 'c') {
-              header_state = h_matching_connection_close;
-            } else {
-              header_state = h_general;
-            }
-            break;
-
-          default:
-            header_state = h_general;
-            break;
-        }
-        break;
-      }
-
-      case s_header_value:
-      {
-        c = LOWER(ch);
-
-        if (ch == CR) {
-          CALLBACK(header_value);
-          state = s_header_almost_done;
-          break;
-        }
-
-        if (ch == LF) {
-          CALLBACK(header_value);
-          goto header_almost_done;
-        }
-
-        switch (header_state) {
-          case h_general:
-            break;
-
-          case h_connection:
-          case h_transfer_encoding:
-            assert(0 && "Shouldn't get here.");
-            break;
-
-          case h_content_length:
-            if (ch == ' ') break;
-            if (ch < '0' || ch > '9') goto error;
-            parser->content_length *= 10;
-            parser->content_length += ch - '0';
-            break;
-
-          /* Transfer-Encoding: chunked */
-          case h_matching_transfer_encoding_chunked:
-            index++;
-            if (index > sizeof(CHUNKED)-1
-                || c != CHUNKED[index]) {
-              header_state = h_general;
-            } else if (index == sizeof(CHUNKED)-2) {
-              header_state = h_transfer_encoding_chunked;
-            }
-            break;
-
-          /* looking for 'Connection: keep-alive' */
-          case h_matching_connection_keep_alive:
-            index++;
-            if (index > sizeof(KEEP_ALIVE)-1
-                || c != KEEP_ALIVE[index]) {
-              header_state = h_general;
-            } else if (index == sizeof(KEEP_ALIVE)-2) {
-              header_state = h_connection_keep_alive;
-            }
-            break;
-
-          /* looking for 'Connection: close' */
-          case h_matching_connection_close:
-            index++;
-            if (index > sizeof(CLOSE)-1 || c != CLOSE[index]) {
-              header_state = h_general;
-            } else if (index == sizeof(CLOSE)-2) {
-              header_state = h_connection_close;
-            }
-            break;
-
-          case h_transfer_encoding_chunked:
-          case h_connection_keep_alive:
-          case h_connection_close:
-            if (ch != ' ') header_state = h_general;
-            break;
-
-          default:
-            state = s_header_value;
-            header_state = h_general;
-            break;
-        }
-        break;
-      }
-
-      case s_header_almost_done:
-      header_almost_done:
-      {
-        STRICT_CHECK(ch != LF);
-
-        state = s_header_field_start;
-
-        switch (header_state) {
-          case h_connection_keep_alive:
-            parser->flags |= F_CONNECTION_KEEP_ALIVE;
-            break;
-          case h_connection_close:
-            parser->flags |= F_CONNECTION_CLOSE;
-            break;
-          case h_transfer_encoding_chunked:
-            parser->flags |= F_CHUNKED;
-            break;
-          default:
-            break;
-        }
-        break;
-      }
-
-      case s_headers_almost_done:
-      headers_almost_done:
-      {
-        STRICT_CHECK(ch != LF);
-
-        if (parser->flags & F_TRAILING) {
-          /* End of a chunked request */
-          CALLBACK2(message_complete);
-          state = NEW_MESSAGE();
-          break;
-        }
-
-        nread = 0;
-
-        if (parser->flags & F_UPGRADE || parser->method == HTTP_CONNECT) {
-          parser->upgrade = 1;
-        }
-
-        /* Here we call the headers_complete callback. This is somewhat
-         * different than other callbacks because if the user returns 1, we
-         * will interpret that as saying that this message has no body. This
-         * is needed for the annoying case of recieving a response to a HEAD
-         * request.
-         */
-        if (settings->on_headers_complete) {
-          switch (settings->on_headers_complete(parser)) {
-            case 0:
-              break;
-
-            case 1:
-              parser->flags |= F_SKIPBODY;
-              break;
-
-            default:
-              parser->state = state;
-              return p - data; /* Error */
-          }
-        }
-
-        /* Exit, the rest of the connect is in a different protocol. */
-        if (parser->upgrade) {
-          CALLBACK2(message_complete);
-          return (p - data);
-        }
-
-        if (parser->flags & F_SKIPBODY) {
-          CALLBACK2(message_complete);
-          state = NEW_MESSAGE();
-        } else if (parser->flags & F_CHUNKED) {
-          /* chunked encoding - ignore Content-Length header */
-          state = s_chunk_size_start;
-        } else {
-          if (parser->content_length == 0) {
-            /* Content-Length header given but zero: Content-Length: 0\r\n */
-            CALLBACK2(message_complete);
-            state = NEW_MESSAGE();
-          } else if (parser->content_length > 0) {
-            /* Content-Length header given and non-zero */
-            state = s_body_identity;
-          } else {
-            if (parser->type == HTTP_REQUEST || http_should_keep_alive(parser)) {
-              /* Assume content-length 0 - read the next */
-              CALLBACK2(message_complete);
-              state = NEW_MESSAGE();
-            } else {
-              /* Read body until EOF */
-              state = s_body_identity_eof;
-            }
-          }
-        }
-
-        break;
-      }
-
-      case s_body_identity:
-        to_read = MIN(pe - p, (int64_t)parser->content_length);
-        if (to_read > 0) {
-          if (settings->on_body) settings->on_body(parser, p, to_read);
-          p += to_read - 1;
-          parser->content_length -= to_read;
-          if (parser->content_length == 0) {
-            CALLBACK2(message_complete);
-            state = NEW_MESSAGE();
-          }
-        }
-        break;
-
-      /* read until EOF */
-      case s_body_identity_eof:
-        to_read = pe - p;
-        if (to_read > 0) {
-          if (settings->on_body) settings->on_body(parser, p, to_read);
-          p += to_read - 1;
-        }
-        break;
-
-      case s_chunk_size_start:
-      {
-        assert(nread == 1);
-        assert(parser->flags & F_CHUNKED);
-
-        c = unhex[(unsigned char)ch];
-        if (c == -1) goto error;
-        parser->content_length = c;
-        state = s_chunk_size;
-        break;
-      }
-
-      case s_chunk_size:
-      {
-        assert(parser->flags & F_CHUNKED);
-
-        if (ch == CR) {
-          state = s_chunk_size_almost_done;
-          break;
-        }
-
-        c = unhex[(unsigned char)ch];
-
-        if (c == -1) {
-          if (ch == ';' || ch == ' ') {
-            state = s_chunk_parameters;
-            break;
-          }
-          goto error;
-        }
-
-        parser->content_length *= 16;
-        parser->content_length += c;
-        break;
-      }
-
-      case s_chunk_parameters:
-      {
-        assert(parser->flags & F_CHUNKED);
-        /* just ignore this shit. TODO check for overflow */
-        if (ch == CR) {
-          state = s_chunk_size_almost_done;
-          break;
-        }
-        break;
-      }
-
-      case s_chunk_size_almost_done:
-      {
-        assert(parser->flags & F_CHUNKED);
-        STRICT_CHECK(ch != LF);
-
-        nread = 0;
-
-        if (parser->content_length == 0) {
-          parser->flags |= F_TRAILING;
-          state = s_header_field_start;
-        } else {
-          state = s_chunk_data;
-        }
-        break;
-      }
-
-      case s_chunk_data:
-      {
-        assert(parser->flags & F_CHUNKED);
-
-        to_read = MIN(pe - p, (int64_t)(parser->content_length));
-
-        if (to_read > 0) {
-          if (settings->on_body) settings->on_body(parser, p, to_read);
-          p += to_read - 1;
-        }
-
-        if (to_read == parser->content_length) {
-          state = s_chunk_data_almost_done;
-        }
-
-        parser->content_length -= to_read;
-        break;
-      }
-
-      case s_chunk_data_almost_done:
-        assert(parser->flags & F_CHUNKED);
-        STRICT_CHECK(ch != CR);
-        state = s_chunk_data_done;
-        break;
-
-      case s_chunk_data_done:
-        assert(parser->flags & F_CHUNKED);
-        STRICT_CHECK(ch != LF);
-        state = s_chunk_size_start;
-        break;
-
-      default:
-        assert(0 && "unhandled state");
-        goto error;
-    }
-  }
-
-  CALLBACK_NOCLEAR(header_field);
-  CALLBACK_NOCLEAR(header_value);
-  CALLBACK_NOCLEAR(fragment);
-  CALLBACK_NOCLEAR(query_string);
-  CALLBACK_NOCLEAR(path);
-  CALLBACK_NOCLEAR(url);
-
-  parser->state = state;
-  parser->header_state = header_state;
-  parser->index = index;
-  parser->nread = nread;
-
-  return len;
-
-error:
-  parser->state = s_dead;
-  return (p - data);
-}
-
-
-int
-http_should_keep_alive (http_parser *parser)
-{
-  if (parser->http_major > 0 && parser->http_minor > 0) {
-    /* HTTP/1.1 */
-    if (parser->flags & F_CONNECTION_CLOSE) {
-      return 0;
-    } else {
-      return 1;
-    }
-  } else {
-    /* HTTP/1.0 or earlier */
-    if (parser->flags & F_CONNECTION_KEEP_ALIVE) {
-      return 1;
-    } else {
-      return 0;
-    }
-  }
-}
-
-
-const char * http_method_str (enum http_method m)
-{
-  return method_strings[m];
-}
-
-
-void
-http_parser_init (http_parser *parser, enum http_parser_type t)
-{
-  parser->type = t;
-  parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res));
-  parser->nread = 0;
-  parser->upgrade = 0;
-  parser->flags = 0;
-  parser->method = 0;
-}
diff --git a/http_parser/http_parser.h b/http_parser/http_parser.h
deleted file mode 100644
index 9c7a26d..0000000
--- a/http_parser/http_parser.h
+++ /dev/null
@@ -1,183 +0,0 @@
-/* Copyright Joyent, Inc. and other Node contributors. 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 http_parser_h
-#define http_parser_h
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#define HTTP_PARSER_VERSION_MAJOR 1
-#define HTTP_PARSER_VERSION_MINOR 0
-
-#include <sys/types.h>
-#if defined(_WIN32) && !defined(__MINGW32__)
-typedef __int8 int8_t;
-typedef unsigned __int8 uint8_t;
-typedef __int16 int16_t;
-typedef unsigned __int16 uint16_t;
-typedef __int32 int32_t;
-typedef unsigned __int32 uint32_t;
-typedef __int64 int64_t;
-typedef unsigned __int64 uint64_t;
-
-typedef unsigned int size_t;
-typedef int ssize_t;
-#else
-#include <stdint.h>
-#endif
-
-/* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run
- * faster
- */
-#ifndef HTTP_PARSER_STRICT
-# define HTTP_PARSER_STRICT 1
-#else
-# define HTTP_PARSER_STRICT 0
-#endif
-
-
-/* Maximium header size allowed */
-#define HTTP_MAX_HEADER_SIZE (80*1024)
-
-
-typedef struct http_parser http_parser;
-typedef struct http_parser_settings http_parser_settings;
-
-
-/* Callbacks should return non-zero to indicate an error. The parser will
- * then halt execution.
- *
- * The one exception is on_headers_complete. In a HTTP_RESPONSE parser
- * returning '1' from on_headers_complete will tell the parser that it
- * should not expect a body. This is used when receiving a response to a
- * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding:
- * chunked' headers that indicate the presence of a body.
- *
- * http_data_cb does not return data chunks. It will be call arbitrarally
- * many times for each string. E.G. you might get 10 callbacks for "on_path"
- * each providing just a few characters more data.
- */
-typedef int (*http_data_cb) (http_parser*, const char *at, size_t length);
-typedef int (*http_cb) (http_parser*);
-
-
-/* Request Methods */
-enum http_method
-  { HTTP_DELETE    = 0
-  , HTTP_GET
-  , HTTP_HEAD
-  , HTTP_POST
-  , HTTP_PUT
-  /* pathological */
-  , HTTP_CONNECT
-  , HTTP_OPTIONS
-  , HTTP_TRACE
-  /* webdav */
-  , HTTP_COPY
-  , HTTP_LOCK
-  , HTTP_MKCOL
-  , HTTP_MOVE
-  , HTTP_PROPFIND
-  , HTTP_PROPPATCH
-  , HTTP_UNLOCK
-  /* subversion */
-  , HTTP_REPORT
-  , HTTP_MKACTIVITY
-  , HTTP_CHECKOUT
-  , HTTP_MERGE
-  /* upnp */
-  , HTTP_MSEARCH
-  , HTTP_NOTIFY
-  , HTTP_SUBSCRIBE
-  , HTTP_UNSUBSCRIBE
-  };
-
-
-enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH };
-
-
-struct http_parser {
-  /** PRIVATE **/
-  unsigned char type : 2;
-  unsigned char flags : 6;
-  unsigned char state;
-  unsigned char header_state;
-  unsigned char index;
-
-  uint32_t nread;
-  int64_t content_length;
-
-  /** READ-ONLY **/
-  unsigned short http_major;
-  unsigned short http_minor;
-  unsigned short status_code; /* responses only */
-  unsigned char method;    /* requests only */
-
-  /* 1 = Upgrade header was present and the parser has exited because of that.
-   * 0 = No upgrade header present.
-   * Should be checked when http_parser_execute() returns in addition to
-   * error checking.
-   */
-  char upgrade;
-
-  /** PUBLIC **/
-  void *data; /* A pointer to get hook to the "connection" or "socket" object */
-};
-
-
-struct http_parser_settings {
-  http_cb      on_message_begin;
-  http_data_cb on_path;
-  http_data_cb on_query_string;
-  http_data_cb on_url;
-  http_data_cb on_fragment;
-  http_data_cb on_header_field;
-  http_data_cb on_header_value;
-  http_cb      on_headers_complete;
-  http_data_cb on_body;
-  http_cb      on_message_complete;
-};
-
-
-void http_parser_init(http_parser *parser, enum http_parser_type type);
-
-
-size_t http_parser_execute(http_parser *parser,
-                           const http_parser_settings *settings,
-                           const char *data,
-                           size_t len);
-
-
-/* If http_should_keep_alive() in the on_headers_complete or
- * on_message_complete callback returns true, then this will be should be
- * the last message on the connection.
- * If you are the server, respond with the "Connection: close" header.
- * If you are the client, close the connection.
- */
-int http_should_keep_alive(http_parser *parser);
-
-/* Returns a string version of the HTTP method. */
-const char *http_method_str(enum http_method);
-
-#ifdef __cplusplus
-}
-#endif
-#endif
diff --git a/http_parser/test.c b/http_parser/test.c
deleted file mode 100644
index 4a93163..0000000
--- a/http_parser/test.c
+++ /dev/null
@@ -1,1952 +0,0 @@
-/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to
- * deal in the Software without restriction, including without limitation the
- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- * IN THE SOFTWARE.
- */
-#include "http_parser.h"
-#include <stdlib.h>
-#include <assert.h>
-#include <stdio.h>
-#include <stdlib.h> /* rand */
-#include <string.h>
-#include <stdarg.h>
-
-#undef TRUE
-#define TRUE 1
-#undef FALSE
-#define FALSE 0
-
-#define MAX_HEADERS 13
-#define MAX_ELEMENT_SIZE 500
-
-#define MIN(a,b) ((a) < (b) ? (a) : (b))
-
-static http_parser *parser;
-
-struct message {
-  const char *name; // for debugging purposes
-  const char *raw;
-  enum http_parser_type type;
-  enum http_method method;
-  int status_code;
-  char request_path[MAX_ELEMENT_SIZE];
-  char request_url[MAX_ELEMENT_SIZE];
-  char fragment[MAX_ELEMENT_SIZE];
-  char query_string[MAX_ELEMENT_SIZE];
-  char body[MAX_ELEMENT_SIZE];
-  size_t body_size;
-  int num_headers;
-  enum { NONE=0, FIELD, VALUE } last_header_element;
-  char headers [MAX_HEADERS][2][MAX_ELEMENT_SIZE];
-  int should_keep_alive;
-
-  int upgrade;
-
-  unsigned short http_major;
-  unsigned short http_minor;
-
-  int message_begin_cb_called;
-  int headers_complete_cb_called;
-  int message_complete_cb_called;
-  int message_complete_on_eof;
-};
-
-static int currently_parsing_eof;
-
-static struct message messages[5];
-static int num_messages;
-
-/* * R E Q U E S T S * */
-const struct message requests[] =
-#define CURL_GET 0
-{ {.name= "curl get"
-  ,.type= HTTP_REQUEST
-  ,.raw= "GET /test HTTP/1.1\r\n"
-         "User-Agent: curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1\r\n"
-         "Host: 0.0.0.0=5000\r\n"
-         "Accept: */*\r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_GET
-  ,.query_string= ""
-  ,.fragment= ""
-  ,.request_path= "/test"
-  ,.request_url= "/test"
-  ,.num_headers= 3
-  ,.headers=
-    { { "User-Agent", "curl/7.18.0 (i486-pc-linux-gnu) libcurl/7.18.0 OpenSSL/0.9.8g zlib/1.2.3.3 libidn/1.1" }
-    , { "Host", "0.0.0.0=5000" }
-    , { "Accept", "*/*" }
-    }
-  ,.body= ""
-  }
-
-#define FIREFOX_GET 1
-, {.name= "firefox get"
-  ,.type= HTTP_REQUEST
-  ,.raw= "GET /favicon.ico HTTP/1.1\r\n"
-         "Host: 0.0.0.0=5000\r\n"
-         "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n"
-         "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
-         "Accept-Language: en-us,en;q=0.5\r\n"
-         "Accept-Encoding: gzip,deflate\r\n"
-         "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"
-         "Keep-Alive: 300\r\n"
-         "Connection: keep-alive\r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_GET
-  ,.query_string= ""
-  ,.fragment= ""
-  ,.request_path= "/favicon.ico"
-  ,.request_url= "/favicon.ico"
-  ,.num_headers= 8
-  ,.headers=
-    { { "Host", "0.0.0.0=5000" }
-    , { "User-Agent", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0" }
-    , { "Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8" }
-    , { "Accept-Language", "en-us,en;q=0.5" }
-    , { "Accept-Encoding", "gzip,deflate" }
-    , { "Accept-Charset", "ISO-8859-1,utf-8;q=0.7,*;q=0.7" }
-    , { "Keep-Alive", "300" }
-    , { "Connection", "keep-alive" }
-    }
-  ,.body= ""
-  }
-
-#define DUMBFUCK 2
-, {.name= "dumbfuck"
-  ,.type= HTTP_REQUEST
-  ,.raw= "GET /dumbfuck HTTP/1.1\r\n"
-         "aaaaaaaaaaaaa:++++++++++\r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_GET
-  ,.query_string= ""
-  ,.fragment= ""
-  ,.request_path= "/dumbfuck"
-  ,.request_url= "/dumbfuck"
-  ,.num_headers= 1
-  ,.headers=
-    { { "aaaaaaaaaaaaa",  "++++++++++" }
-    }
-  ,.body= ""
-  }
-
-#define FRAGMENT_IN_URI 3
-, {.name= "fragment in url"
-  ,.type= HTTP_REQUEST
-  ,.raw= "GET /forums/1/topics/2375?page=1#posts-17408 HTTP/1.1\r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_GET
-  ,.query_string= "page=1"
-  ,.fragment= "posts-17408"
-  ,.request_path= "/forums/1/topics/2375"
-  /* XXX request url does include fragment? */
-  ,.request_url= "/forums/1/topics/2375?page=1#posts-17408"
-  ,.num_headers= 0
-  ,.body= ""
-  }
-
-#define GET_NO_HEADERS_NO_BODY 4
-, {.name= "get no headers no body"
-  ,.type= HTTP_REQUEST
-  ,.raw= "GET /get_no_headers_no_body/world HTTP/1.1\r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE /* would need Connection: close */
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_GET
-  ,.query_string= ""
-  ,.fragment= ""
-  ,.request_path= "/get_no_headers_no_body/world"
-  ,.request_url= "/get_no_headers_no_body/world"
-  ,.num_headers= 0
-  ,.body= ""
-  }
-
-#define GET_ONE_HEADER_NO_BODY 5
-, {.name= "get one header no body"
-  ,.type= HTTP_REQUEST
-  ,.raw= "GET /get_one_header_no_body HTTP/1.1\r\n"
-         "Accept: */*\r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE /* would need Connection: close */
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_GET
-  ,.query_string= ""
-  ,.fragment= ""
-  ,.request_path= "/get_one_header_no_body"
-  ,.request_url= "/get_one_header_no_body"
-  ,.num_headers= 1
-  ,.headers=
-    { { "Accept" , "*/*" }
-    }
-  ,.body= ""
-  }
-
-#define GET_FUNKY_CONTENT_LENGTH 6
-, {.name= "get funky content length body hello"
-  ,.type= HTTP_REQUEST
-  ,.raw= "GET /get_funky_content_length_body_hello HTTP/1.0\r\n"
-         "conTENT-Length: 5\r\n"
-         "\r\n"
-         "HELLO"
-  ,.should_keep_alive= FALSE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 0
-  ,.method= HTTP_GET
-  ,.query_string= ""
-  ,.fragment= ""
-  ,.request_path= "/get_funky_content_length_body_hello"
-  ,.request_url= "/get_funky_content_length_body_hello"
-  ,.num_headers= 1
-  ,.headers=
-    { { "conTENT-Length" , "5" }
-    }
-  ,.body= "HELLO"
-  }
-
-#define POST_IDENTITY_BODY_WORLD 7
-, {.name= "post identity body world"
-  ,.type= HTTP_REQUEST
-  ,.raw= "POST /post_identity_body_world?q=search#hey HTTP/1.1\r\n"
-         "Accept: */*\r\n"
-         "Transfer-Encoding: identity\r\n"
-         "Content-Length: 5\r\n"
-         "\r\n"
-         "World"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_POST
-  ,.query_string= "q=search"
-  ,.fragment= "hey"
-  ,.request_path= "/post_identity_body_world"
-  ,.request_url= "/post_identity_body_world?q=search#hey"
-  ,.num_headers= 3
-  ,.headers=
-    { { "Accept", "*/*" }
-    , { "Transfer-Encoding", "identity" }
-    , { "Content-Length", "5" }
-    }
-  ,.body= "World"
-  }
-
-#define POST_CHUNKED_ALL_YOUR_BASE 8
-, {.name= "post - chunked body: all your base are belong to us"
-  ,.type= HTTP_REQUEST
-  ,.raw= "POST /post_chunked_all_your_base HTTP/1.1\r\n"
-         "Transfer-Encoding: chunked\r\n"
-         "\r\n"
-         "1e\r\nall your base are belong to us\r\n"
-         "0\r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_POST
-  ,.query_string= ""
-  ,.fragment= ""
-  ,.request_path= "/post_chunked_all_your_base"
-  ,.request_url= "/post_chunked_all_your_base"
-  ,.num_headers= 1
-  ,.headers=
-    { { "Transfer-Encoding" , "chunked" }
-    }
-  ,.body= "all your base are belong to us"
-  }
-
-#define TWO_CHUNKS_MULT_ZERO_END 9
-, {.name= "two chunks ; triple zero ending"
-  ,.type= HTTP_REQUEST
-  ,.raw= "POST /two_chunks_mult_zero_end HTTP/1.1\r\n"
-         "Transfer-Encoding: chunked\r\n"
-         "\r\n"
-         "5\r\nhello\r\n"
-         "6\r\n world\r\n"
-         "000\r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_POST
-  ,.query_string= ""
-  ,.fragment= ""
-  ,.request_path= "/two_chunks_mult_zero_end"
-  ,.request_url= "/two_chunks_mult_zero_end"
-  ,.num_headers= 1
-  ,.headers=
-    { { "Transfer-Encoding", "chunked" }
-    }
-  ,.body= "hello world"
-  }
-
-#define CHUNKED_W_TRAILING_HEADERS 10
-, {.name= "chunked with trailing headers. blech."
-  ,.type= HTTP_REQUEST
-  ,.raw= "POST /chunked_w_trailing_headers HTTP/1.1\r\n"
-         "Transfer-Encoding: chunked\r\n"
-         "\r\n"
-         "5\r\nhello\r\n"
-         "6\r\n world\r\n"
-         "0\r\n"
-         "Vary: *\r\n"
-         "Content-Type: text/plain\r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_POST
-  ,.query_string= ""
-  ,.fragment= ""
-  ,.request_path= "/chunked_w_trailing_headers"
-  ,.request_url= "/chunked_w_trailing_headers"
-  ,.num_headers= 3
-  ,.headers=
-    { { "Transfer-Encoding",  "chunked" }
-    , { "Vary", "*" }
-    , { "Content-Type", "text/plain" }
-    }
-  ,.body= "hello world"
-  }
-
-#define CHUNKED_W_BULLSHIT_AFTER_LENGTH 11
-, {.name= "with bullshit after the length"
-  ,.type= HTTP_REQUEST
-  ,.raw= "POST /chunked_w_bullshit_after_length HTTP/1.1\r\n"
-         "Transfer-Encoding: chunked\r\n"
-         "\r\n"
-         "5; ihatew3;whatthefuck=aretheseparametersfor\r\nhello\r\n"
-         "6; blahblah; blah\r\n world\r\n"
-         "0\r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_POST
-  ,.query_string= ""
-  ,.fragment= ""
-  ,.request_path= "/chunked_w_bullshit_after_length"
-  ,.request_url= "/chunked_w_bullshit_after_length"
-  ,.num_headers= 1
-  ,.headers=
-    { { "Transfer-Encoding", "chunked" }
-    }
-  ,.body= "hello world"
-  }
-
-#define WITH_QUOTES 12
-, {.name= "with quotes"
-  ,.type= HTTP_REQUEST
-  ,.raw= "GET /with_\"stupid\"_quotes?foo=\"bar\" HTTP/1.1\r\n\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_GET
-  ,.query_string= "foo=\"bar\""
-  ,.fragment= ""
-  ,.request_path= "/with_\"stupid\"_quotes"
-  ,.request_url= "/with_\"stupid\"_quotes?foo=\"bar\""
-  ,.num_headers= 0
-  ,.headers= { }
-  ,.body= ""
-  }
-
-#define APACHEBENCH_GET 13
-/* The server receiving this request SHOULD NOT wait for EOF
- * to know that content-length == 0.
- * How to represent this in a unit test? message_complete_on_eof
- * Compare with NO_CONTENT_LENGTH_RESPONSE.
- */
-, {.name = "apachebench get"
-  ,.type= HTTP_REQUEST
-  ,.raw= "GET /test HTTP/1.0\r\n"
-         "Host: 0.0.0.0:5000\r\n"
-         "User-Agent: ApacheBench/2.3\r\n"
-         "Accept: */*\r\n\r\n"
-  ,.should_keep_alive= FALSE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 0
-  ,.method= HTTP_GET
-  ,.query_string= ""
-  ,.fragment= ""
-  ,.request_path= "/test"
-  ,.request_url= "/test"
-  ,.num_headers= 3
-  ,.headers= { { "Host", "0.0.0.0:5000" }
-             , { "User-Agent", "ApacheBench/2.3" }
-             , { "Accept", "*/*" }
-             }
-  ,.body= ""
-  }
-
-#define QUERY_URL_WITH_QUESTION_MARK_GET 14
-/* Some clients include '?' characters in query strings.
- */
-, {.name = "query url with question mark"
-  ,.type= HTTP_REQUEST
-  ,.raw= "GET /test.cgi?foo=bar?baz HTTP/1.1\r\n\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_GET
-  ,.query_string= "foo=bar?baz"
-  ,.fragment= ""
-  ,.request_path= "/test.cgi"
-  ,.request_url= "/test.cgi?foo=bar?baz"
-  ,.num_headers= 0
-  ,.headers= {}
-  ,.body= ""
-  }
-
-#define PREFIX_NEWLINE_GET 15
-/* Some clients, especially after a POST in a keep-alive connection,
- * will send an extra CRLF before the next request
- */
-, {.name = "newline prefix get"
-  ,.type= HTTP_REQUEST
-  ,.raw= "\r\nGET /test HTTP/1.1\r\n\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_GET
-  ,.query_string= ""
-  ,.fragment= ""
-  ,.request_path= "/test"
-  ,.request_url= "/test"
-  ,.num_headers= 0
-  ,.headers= { }
-  ,.body= ""
-  }
-
-#define UPGRADE_REQUEST 16
-, {.name = "upgrade request"
-  ,.type= HTTP_REQUEST
-  ,.raw= "GET /demo HTTP/1.1\r\n"
-         "Host: example.com\r\n"
-         "Connection: Upgrade\r\n"
-         "Sec-WebSocket-Key2: 12998 5 Y3 1  .P00\r\n"
-         "Sec-WebSocket-Protocol: sample\r\n"
-         "Upgrade: WebSocket\r\n"
-         "Sec-WebSocket-Key1: 4 @1  46546xW%0l 1 5\r\n"
-         "Origin: http://example.com\r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_GET
-  ,.query_string= ""
-  ,.fragment= ""
-  ,.request_path= "/demo"
-  ,.request_url= "/demo"
-  ,.num_headers= 7
-  ,.upgrade=1
-  ,.headers= { { "Host", "example.com" }
-             , { "Connection", "Upgrade" }
-             , { "Sec-WebSocket-Key2", "12998 5 Y3 1  .P00" }
-             , { "Sec-WebSocket-Protocol", "sample" }
-             , { "Upgrade", "WebSocket" }
-             , { "Sec-WebSocket-Key1", "4 @1  46546xW%0l 1 5" }
-             , { "Origin", "http://example.com" }
-             }
-  ,.body= ""
-  }
-
-#define CONNECT_REQUEST 17
-, {.name = "connect request"
-  ,.type= HTTP_REQUEST
-  ,.raw= "CONNECT home0.netscape.com:443 HTTP/1.0\r\n"
-         "User-agent: Mozilla/1.1N\r\n"
-         "Proxy-authorization: basic aGVsbG86d29ybGQ=\r\n"
-         "\r\n"
-  ,.should_keep_alive= FALSE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 0
-  ,.method= HTTP_CONNECT
-  ,.query_string= ""
-  ,.fragment= ""
-  ,.request_path= ""
-  ,.request_url= "home0.netscape.com:443"
-  ,.num_headers= 2
-  ,.upgrade=1
-  ,.headers= { { "User-agent", "Mozilla/1.1N" }
-             , { "Proxy-authorization", "basic aGVsbG86d29ybGQ=" }
-             }
-  ,.body= ""
-  }
-
-#define REPORT_REQ 18
-, {.name= "report request"
-  ,.type= HTTP_REQUEST
-  ,.raw= "REPORT /test HTTP/1.1\r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_REPORT
-  ,.query_string= ""
-  ,.fragment= ""
-  ,.request_path= "/test"
-  ,.request_url= "/test"
-  ,.num_headers= 0
-  ,.headers= {}
-  ,.body= ""
-  }
-
-#define NO_HTTP_VERSION 19
-, {.name= "request with no http version"
-  ,.type= HTTP_REQUEST
-  ,.raw= "GET /\r\n"
-         "\r\n"
-  ,.should_keep_alive= FALSE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 0
-  ,.http_minor= 9
-  ,.method= HTTP_GET
-  ,.query_string= ""
-  ,.fragment= ""
-  ,.request_path= "/"
-  ,.request_url= "/"
-  ,.num_headers= 0
-  ,.headers= {}
-  ,.body= ""
-  }
-
-#define MSEARCH_REQ 20
-, {.name= "m-search request"
-  ,.type= HTTP_REQUEST
-  ,.raw= "M-SEARCH * HTTP/1.1\r\n"
-         "HOST: 239.255.255.250:1900\r\n"
-         "MAN: \"ssdp:discover\"\r\n"
-         "ST: \"ssdp:all\"\r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_MSEARCH
-  ,.query_string= ""
-  ,.fragment= ""
-  ,.request_path= "*"
-  ,.request_url= "*"
-  ,.num_headers= 3
-  ,.headers= { { "HOST", "239.255.255.250:1900" }
-             , { "MAN", "\"ssdp:discover\"" }
-             , { "ST", "\"ssdp:all\"" }
-             }
-  ,.body= ""
-  }
-
-#define UTF8_PATH_REQ 21
-, {.name= "utf-8 path request"
-  ,.type= HTTP_REQUEST
-  ,.raw= "GET /δ¶/δt/pope?q=1#narf HTTP/1.1\r\n"
-         "Host: github.com\r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_GET
-  ,.query_string= "q=1"
-  ,.fragment= "narf"
-  ,.request_path= "/δ¶/δt/pope"
-  ,.request_url= "/δ¶/δt/pope?q=1#narf"
-  ,.num_headers= 1
-  ,.headers= { {"Host", "github.com" }
-             }
-  ,.body= ""
-  }
-
-#define QUERY_TERMINATED_HOST 22
-, {.name= "host terminated by a query string"
-  ,.type= HTTP_REQUEST
-  ,.raw= "GET http://hypnotoad.org?hail=all HTTP/1.1\r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_GET
-  ,.query_string= "hail=all"
-  ,.fragment= ""
-  ,.request_path= ""
-  ,.request_url= "http://hypnotoad.org?hail=all"
-  ,.num_headers= 0
-  ,.headers= { }
-  ,.body= ""
-  }
-
-#define QUERY_TERMINATED_HOSTPORT 23
-, {.name= "host:port terminated by a query string"
-  ,.type= HTTP_REQUEST
-  ,.raw= "GET http://hypnotoad.org:1234?hail=all HTTP/1.1\r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_GET
-  ,.query_string= "hail=all"
-  ,.fragment= ""
-  ,.request_path= ""
-  ,.request_url= "http://hypnotoad.org:1234?hail=all"
-  ,.num_headers= 0
-  ,.headers= { }
-  ,.body= ""
-  }
-
-#define SPACE_TERMINATED_HOSTPORT 24
-, {.name= "host:port terminated by a space"
-  ,.type= HTTP_REQUEST
-  ,.raw= "GET http://hypnotoad.org:1234 HTTP/1.1\r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.method= HTTP_GET
-  ,.query_string= ""
-  ,.fragment= ""
-  ,.request_path= ""
-  ,.request_url= "http://hypnotoad.org:1234"
-  ,.num_headers= 0
-  ,.headers= { }
-  ,.body= ""
-  }
-
-, {.name= NULL } /* sentinel */
-};
-
-/* * R E S P O N S E S * */
-const struct message responses[] =
-#define GOOGLE_301 0
-{ {.name= "google 301"
-  ,.type= HTTP_RESPONSE
-  ,.raw= "HTTP/1.1 301 Moved Permanently\r\n"
-         "Location: http://www.google.com/\r\n"
-         "Content-Type: text/html; charset=UTF-8\r\n"
-         "Date: Sun, 26 Apr 2009 11:11:49 GMT\r\n"
-         "Expires: Tue, 26 May 2009 11:11:49 GMT\r\n"
-         "X-$PrototypeBI-Version: 1.6.0.3\r\n" /* $ char in header field */
-         "Cache-Control: public, max-age=2592000\r\n"
-         "Server: gws\r\n"
-         "Content-Length:  219  \r\n"
-         "\r\n"
-         "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n"
-         "<TITLE>301 Moved</TITLE></HEAD><BODY>\n"
-         "<H1>301 Moved</H1>\n"
-         "The document has moved\n"
-         "<A HREF=\"http://www.google.com/\">here</A>.\r\n"
-         "</BODY></HTML>\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.status_code= 301
-  ,.num_headers= 8
-  ,.headers=
-    { { "Location", "http://www.google.com/" }
-    , { "Content-Type", "text/html; charset=UTF-8" }
-    , { "Date", "Sun, 26 Apr 2009 11:11:49 GMT" }
-    , { "Expires", "Tue, 26 May 2009 11:11:49 GMT" }
-    , { "X-$PrototypeBI-Version", "1.6.0.3" }
-    , { "Cache-Control", "public, max-age=2592000" }
-    , { "Server", "gws" }
-    , { "Content-Length", "219  " }
-    }
-  ,.body= "<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n"
-          "<TITLE>301 Moved</TITLE></HEAD><BODY>\n"
-          "<H1>301 Moved</H1>\n"
-          "The document has moved\n"
-          "<A HREF=\"http://www.google.com/\">here</A>.\r\n"
-          "</BODY></HTML>\r\n"
-  }
-
-#define NO_CONTENT_LENGTH_RESPONSE 1
-/* The client should wait for the server's EOF. That is, when content-length
- * is not specified, and "Connection: close", the end of body is specified
- * by the EOF.
- * Compare with APACHEBENCH_GET
- */
-, {.name= "no content-length response"
-  ,.type= HTTP_RESPONSE
-  ,.raw= "HTTP/1.1 200 OK\r\n"
-         "Date: Tue, 04 Aug 2009 07:59:32 GMT\r\n"
-         "Server: Apache\r\n"
-         "X-Powered-By: Servlet/2.5 JSP/2.1\r\n"
-         "Content-Type: text/xml; charset=utf-8\r\n"
-         "Connection: close\r\n"
-         "\r\n"
-         "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
-         "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
-         "  <SOAP-ENV:Body>\n"
-         "    <SOAP-ENV:Fault>\n"
-         "       <faultcode>SOAP-ENV:Client</faultcode>\n"
-         "       <faultstring>Client Error</faultstring>\n"
-         "    </SOAP-ENV:Fault>\n"
-         "  </SOAP-ENV:Body>\n"
-         "</SOAP-ENV:Envelope>"
-  ,.should_keep_alive= FALSE
-  ,.message_complete_on_eof= TRUE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.status_code= 200
-  ,.num_headers= 5
-  ,.headers=
-    { { "Date", "Tue, 04 Aug 2009 07:59:32 GMT" }
-    , { "Server", "Apache" }
-    , { "X-Powered-By", "Servlet/2.5 JSP/2.1" }
-    , { "Content-Type", "text/xml; charset=utf-8" }
-    , { "Connection", "close" }
-    }
-  ,.body= "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
-          "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\">\n"
-          "  <SOAP-ENV:Body>\n"
-          "    <SOAP-ENV:Fault>\n"
-          "       <faultcode>SOAP-ENV:Client</faultcode>\n"
-          "       <faultstring>Client Error</faultstring>\n"
-          "    </SOAP-ENV:Fault>\n"
-          "  </SOAP-ENV:Body>\n"
-          "</SOAP-ENV:Envelope>"
-  }
-
-#define NO_HEADERS_NO_BODY_404 2
-, {.name= "404 no headers no body"
-  ,.type= HTTP_RESPONSE
-  ,.raw= "HTTP/1.1 404 Not Found\r\n\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.status_code= 404
-  ,.num_headers= 0
-  ,.headers= {}
-  ,.body_size= 0
-  ,.body= ""
-  }
-
-#define NO_REASON_PHRASE 3
-, {.name= "301 no response phrase"
-  ,.type= HTTP_RESPONSE
-  ,.raw= "HTTP/1.1 301\r\n\r\n"
-  ,.should_keep_alive = TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.status_code= 301
-  ,.num_headers= 0
-  ,.headers= {}
-  ,.body= ""
-  }
-
-#define TRAILING_SPACE_ON_CHUNKED_BODY 4
-, {.name="200 trailing space on chunked body"
-  ,.type= HTTP_RESPONSE
-  ,.raw= "HTTP/1.1 200 OK\r\n"
-         "Content-Type: text/plain\r\n"
-         "Transfer-Encoding: chunked\r\n"
-         "\r\n"
-         "25  \r\n"
-         "This is the data in the first chunk\r\n"
-         "\r\n"
-         "1C\r\n"
-         "and this is the second one\r\n"
-         "\r\n"
-         "0  \r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.status_code= 200
-  ,.num_headers= 2
-  ,.headers=
-    { {"Content-Type", "text/plain" }
-    , {"Transfer-Encoding", "chunked" }
-    }
-  ,.body_size = 37+28
-  ,.body =
-         "This is the data in the first chunk\r\n"
-         "and this is the second one\r\n"
-
-  }
-
-#define NO_CARRIAGE_RET 5
-, {.name="no carriage ret"
-  ,.type= HTTP_RESPONSE
-  ,.raw= "HTTP/1.1 200 OK\n"
-         "Content-Type: text/html; charset=utf-8\n"
-         "Connection: close\n"
-         "\n"
-         "these headers are from http://news.ycombinator.com/"
-  ,.should_keep_alive= FALSE
-  ,.message_complete_on_eof= TRUE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.status_code= 200
-  ,.num_headers= 2
-  ,.headers=
-    { {"Content-Type", "text/html; charset=utf-8" }
-    , {"Connection", "close" }
-    }
-  ,.body= "these headers are from http://news.ycombinator.com/"
-  }
-
-#define PROXY_CONNECTION 6
-, {.name="proxy connection"
-  ,.type= HTTP_RESPONSE
-  ,.raw= "HTTP/1.1 200 OK\r\n"
-         "Content-Type: text/html; charset=UTF-8\r\n"
-         "Content-Length: 11\r\n"
-         "Proxy-Connection: close\r\n"
-         "Date: Thu, 31 Dec 2009 20:55:48 +0000\r\n"
-         "\r\n"
-         "hello world"
-  ,.should_keep_alive= FALSE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.status_code= 200
-  ,.num_headers= 4
-  ,.headers=
-    { {"Content-Type", "text/html; charset=UTF-8" }
-    , {"Content-Length", "11" }
-    , {"Proxy-Connection", "close" }
-    , {"Date", "Thu, 31 Dec 2009 20:55:48 +0000"}
-    }
-  ,.body= "hello world"
-  }
-
-#define UNDERSTORE_HEADER_KEY 7
-  // shown by
-  // curl -o /dev/null -v "http://ad.doubleclick.net/pfadx/DARTSHELLCONFIGXML;dcmt=text/xml;"
-, {.name="underscore header key"
-  ,.type= HTTP_RESPONSE
-  ,.raw= "HTTP/1.1 200 OK\r\n"
-         "Server: DCLK-AdSvr\r\n"
-         "Content-Type: text/xml\r\n"
-         "Content-Length: 0\r\n"
-         "DCLK_imp: v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o\r\n\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.status_code= 200
-  ,.num_headers= 4
-  ,.headers=
-    { {"Server", "DCLK-AdSvr" }
-    , {"Content-Type", "text/xml" }
-    , {"Content-Length", "0" }
-    , {"DCLK_imp", "v7;x;114750856;0-0;0;17820020;0/0;21603567/21621457/1;;~okv=;dcmt=text/xml;;~cs=o" }
-    }
-  ,.body= ""
-  }
-
-#define BONJOUR_MADAME_FR 8
-/* The client should not merge two headers fields when the first one doesn't
- * have a value.
- */
-, {.name= "bonjourmadame.fr"
-  ,.type= HTTP_RESPONSE
-  ,.raw= "HTTP/1.0 301 Moved Permanently\r\n"
-         "Date: Thu, 03 Jun 2010 09:56:32 GMT\r\n"
-         "Server: Apache/2.2.3 (Red Hat)\r\n"
-         "Cache-Control: public\r\n"
-         "Pragma: \r\n"
-         "Location: http://www.bonjourmadame.fr/\r\n"
-         "Vary: Accept-Encoding\r\n"
-         "Content-Length: 0\r\n"
-         "Content-Type: text/html; charset=UTF-8\r\n"
-         "Connection: keep-alive\r\n"
-         "\r\n"
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 0
-  ,.status_code= 301
-  ,.num_headers= 9
-  ,.headers=
-    { { "Date", "Thu, 03 Jun 2010 09:56:32 GMT" }
-    , { "Server", "Apache/2.2.3 (Red Hat)" }
-    , { "Cache-Control", "public" }
-    , { "Pragma", "" }
-    , { "Location", "http://www.bonjourmadame.fr/" }
-    , { "Vary",  "Accept-Encoding" }
-    , { "Content-Length", "0" }
-    , { "Content-Type", "text/html; charset=UTF-8" }
-    , { "Connection", "keep-alive" }
-    }
-  ,.body= ""
-  }
-
-#define SPACE_IN_FIELD_RES 9
-/* Should handle spaces in header fields */
-, {.name= "field space"
-  ,.type= HTTP_RESPONSE
-  ,.raw= "HTTP/1.1 200 OK\r\n"
-         "Server: Microsoft-IIS/6.0\r\n"
-         "X-Powered-By: ASP.NET\r\n"
-         "en-US Content-Type: text/xml\r\n" /* this is the problem */
-         "Content-Type: text/xml\r\n"
-         "Content-Length: 16\r\n"
-         "Date: Fri, 23 Jul 2010 18:45:38 GMT\r\n"
-         "Connection: keep-alive\r\n"
-         "\r\n"
-         "<xml>hello</xml>" /* fake body */
-  ,.should_keep_alive= TRUE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.status_code= 200
-  ,.num_headers= 7
-  ,.headers=
-    { { "Server",  "Microsoft-IIS/6.0" }
-    , { "X-Powered-By", "ASP.NET" }
-    , { "en-US Content-Type", "text/xml" }
-    , { "Content-Type", "text/xml" }
-    , { "Content-Length", "16" }
-    , { "Date", "Fri, 23 Jul 2010 18:45:38 GMT" }
-    , { "Connection", "keep-alive" }
-    }
-  ,.body= "<xml>hello</xml>"
-  }
-
-
-#define RES_FIELD_UNDERSCORE 10
-/* Should handle spaces in header fields */
-, {.name= "field underscore"
-  ,.type= HTTP_RESPONSE
-  ,.raw= "HTTP/1.1 200 OK\r\n"
-         "Date: Tue, 28 Sep 2010 01:14:13 GMT\r\n"
-         "Server: Apache\r\n"
-         "Cache-Control: no-cache, must-revalidate\r\n"
-         "Expires: Mon, 26 Jul 1997 05:00:00 GMT\r\n"
-         ".et-Cookie: PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com\r\n"
-         "Vary: Accept-Encoding\r\n"
-         "_eep-Alive: timeout=45\r\n" /* semantic value ignored */
-         "_onnection: Keep-Alive\r\n" /* semantic value ignored */
-         "Transfer-Encoding: chunked\r\n"
-         "Content-Type: text/html\r\n"
-         "Connection: close\r\n"
-         "\r\n"
-         "0\r\n\r\n"
-  ,.should_keep_alive= FALSE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.status_code= 200
-  ,.num_headers= 11
-  ,.headers=
-    { { "Date", "Tue, 28 Sep 2010 01:14:13 GMT" }
-    , { "Server", "Apache" }
-    , { "Cache-Control", "no-cache, must-revalidate" }
-    , { "Expires", "Mon, 26 Jul 1997 05:00:00 GMT" }
-    , { ".et-Cookie", "PlaxoCS=1274804622353690521; path=/; domain=.plaxo.com" }
-    , { "Vary", "Accept-Encoding" }
-    , { "_eep-Alive", "timeout=45" }
-    , { "_onnection", "Keep-Alive" }
-    , { "Transfer-Encoding", "chunked" }
-    , { "Content-Type", "text/html" }
-    , { "Connection", "close" }
-    }
-  ,.body= ""
-  }
-
-#define NON_ASCII_IN_STATUS_LINE 11
-/* Should handle non-ASCII in status line */
-, {.name= "non-ASCII in status line"
-  ,.type= HTTP_RESPONSE
-  ,.raw= "HTTP/1.1 500 Oriëntatieprobleem\r\n"
-         "Date: Fri, 5 Nov 2010 23:07:12 GMT+2\r\n"
-         "Content-Length: 0\r\n"
-         "Connection: close\r\n"
-         "\r\n"
-  ,.should_keep_alive= FALSE
-  ,.message_complete_on_eof= FALSE
-  ,.http_major= 1
-  ,.http_minor= 1
-  ,.status_code= 500
-  ,.num_headers= 3
-  ,.headers=
-    { { "Date", "Fri, 5 Nov 2010 23:07:12 GMT+2" }
-    , { "Content-Length", "0" }
-    , { "Connection", "close" }
-    }
-  ,.body= ""
-  }
-
-
-, {.name= NULL } /* sentinel */
-};
-
-int
-request_path_cb (http_parser *p, const char *buf, size_t len)
-{
-  assert(p == parser);
-  strncat(messages[num_messages].request_path, buf, len);
-  return 0;
-}
-
-int
-request_url_cb (http_parser *p, const char *buf, size_t len)
-{
-  assert(p == parser);
-  strncat(messages[num_messages].request_url, buf, len);
-  return 0;
-}
-
-int
-query_string_cb (http_parser *p, const char *buf, size_t len)
-{
-  assert(p == parser);
-  strncat(messages[num_messages].query_string, buf, len);
-  return 0;
-}
-
-int
-fragment_cb (http_parser *p, const char *buf, size_t len)
-{
-  assert(p == parser);
-  strncat(messages[num_messages].fragment, buf, len);
-  return 0;
-}
-
-int
-header_field_cb (http_parser *p, const char *buf, size_t len)
-{
-  assert(p == parser);
-  struct message *m = &messages[num_messages];
-
-  if (m->last_header_element != FIELD)
-    m->num_headers++;
-
-  strncat(m->headers[m->num_headers-1][0], buf, len);
-
-  m->last_header_element = FIELD;
-
-  return 0;
-}
-
-int
-header_value_cb (http_parser *p, const char *buf, size_t len)
-{
-  assert(p == parser);
-  struct message *m = &messages[num_messages];
-
-  strncat(m->headers[m->num_headers-1][1], buf, len);
-
-  m->last_header_element = VALUE;
-
-  return 0;
-}
-
-int
-body_cb (http_parser *p, const char *buf, size_t len)
-{
-  assert(p == parser);
-  strncat(messages[num_messages].body, buf, len);
-  messages[num_messages].body_size += len;
- // printf("body_cb: '%s'\n", requests[num_messages].body);
-  return 0;
-}
-
-int
-count_body_cb (http_parser *p, const char *buf, size_t len)
-{
-  assert(p == parser);
-  assert(buf);
-  messages[num_messages].body_size += len;
-  return 0;
-}
-
-int
-message_begin_cb (http_parser *p)
-{
-  assert(p == parser);
-  messages[num_messages].message_begin_cb_called = TRUE;
-  return 0;
-}
-
-int
-headers_complete_cb (http_parser *p)
-{
-  assert(p == parser);
-  messages[num_messages].method = parser->method;
-  messages[num_messages].status_code = parser->status_code;
-  messages[num_messages].http_major = parser->http_major;
-  messages[num_messages].http_minor = parser->http_minor;
-  messages[num_messages].headers_complete_cb_called = TRUE;
-  messages[num_messages].should_keep_alive = http_should_keep_alive(parser);
-  return 0;
-}
-
-int
-message_complete_cb (http_parser *p)
-{
-  assert(p == parser);
-  if (messages[num_messages].should_keep_alive != http_should_keep_alive(parser))
-  {
-    fprintf(stderr, "\n\n *** Error http_should_keep_alive() should have same "
-                    "value in both on_message_complete and on_headers_complete "
-                    "but it doesn't! ***\n\n");
-    assert(0);
-    exit(1);
-  }
-  messages[num_messages].message_complete_cb_called = TRUE;
-
-  messages[num_messages].message_complete_on_eof = currently_parsing_eof;
-
-  num_messages++;
-  return 0;
-}
-
-static http_parser_settings settings =
-  {.on_message_begin = message_begin_cb
-  ,.on_header_field = header_field_cb
-  ,.on_header_value = header_value_cb
-  ,.on_path = request_path_cb
-  ,.on_url = request_url_cb
-  ,.on_fragment = fragment_cb
-  ,.on_query_string = query_string_cb
-  ,.on_body = body_cb
-  ,.on_headers_complete = headers_complete_cb
-  ,.on_message_complete = message_complete_cb
-  };
-
-static http_parser_settings settings_count_body =
-  {.on_message_begin = message_begin_cb
-  ,.on_header_field = header_field_cb
-  ,.on_header_value = header_value_cb
-  ,.on_path = request_path_cb
-  ,.on_url = request_url_cb
-  ,.on_fragment = fragment_cb
-  ,.on_query_string = query_string_cb
-  ,.on_body = count_body_cb
-  ,.on_headers_complete = headers_complete_cb
-  ,.on_message_complete = message_complete_cb
-  };
-
-static http_parser_settings settings_null =
-  {.on_message_begin = 0
-  ,.on_header_field = 0
-  ,.on_header_value = 0
-  ,.on_path = 0
-  ,.on_url = 0
-  ,.on_fragment = 0
-  ,.on_query_string = 0
-  ,.on_body = 0
-  ,.on_headers_complete = 0
-  ,.on_message_complete = 0
-  };
-
-void
-parser_init (enum http_parser_type type)
-{
-  num_messages = 0;
-
-  assert(parser == NULL);
-
-  parser = malloc(sizeof(http_parser));
-
-  http_parser_init(parser, type);
-
-  memset(&messages, 0, sizeof messages);
-
-}
-
-void
-parser_free ()
-{
-  assert(parser);
-  free(parser);
-  parser = NULL;
-}
-
-size_t parse (const char *buf, size_t len)
-{
-  size_t nparsed;
-  currently_parsing_eof = (len == 0);
-  nparsed = http_parser_execute(parser, &settings, buf, len);
-  return nparsed;
-}
-
-size_t parse_count_body (const char *buf, size_t len)
-{
-  size_t nparsed;
-  currently_parsing_eof = (len == 0);
-  nparsed = http_parser_execute(parser, &settings_count_body, buf, len);
-  return nparsed;
-}
-
-static inline int
-check_str_eq (const struct message *m,
-              const char *prop,
-              const char *expected,
-              const char *found) {
-  if (0 != strcmp(expected, found)) {
-    printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name);
-    printf("expected '%s'\n", expected);
-    printf("   found '%s'\n", found);
-    return 0;
-  }
-  return 1;
-}
-
-static inline int
-check_num_eq (const struct message *m,
-              const char *prop,
-              int expected,
-              int found) {
-  if (expected != found) {
-    printf("\n*** Error: %s in '%s' ***\n\n", prop, m->name);
-    printf("expected %d\n", expected);
-    printf("   found %d\n", found);
-    return 0;
-  }
-  return 1;
-}
-
-#define MESSAGE_CHECK_STR_EQ(expected, found, prop) \
-  if (!check_str_eq(expected, #prop, expected->prop, found->prop)) return 0
-
-#define MESSAGE_CHECK_NUM_EQ(expected, found, prop) \
-  if (!check_num_eq(expected, #prop, expected->prop, found->prop)) return 0
-
-
-int
-message_eq (int index, const struct message *expected)
-{
-  int i;
-  struct message *m = &messages[index];
-
-  MESSAGE_CHECK_NUM_EQ(expected, m, http_major);
-  MESSAGE_CHECK_NUM_EQ(expected, m, http_minor);
-
-  if (expected->type == HTTP_REQUEST) {
-    MESSAGE_CHECK_NUM_EQ(expected, m, method);
-  } else {
-    MESSAGE_CHECK_NUM_EQ(expected, m, status_code);
-  }
-
-  MESSAGE_CHECK_NUM_EQ(expected, m, should_keep_alive);
-  MESSAGE_CHECK_NUM_EQ(expected, m, message_complete_on_eof);
-
-  assert(m->message_begin_cb_called);
-  assert(m->headers_complete_cb_called);
-  assert(m->message_complete_cb_called);
-
-
-  MESSAGE_CHECK_STR_EQ(expected, m, request_path);
-  MESSAGE_CHECK_STR_EQ(expected, m, query_string);
-  MESSAGE_CHECK_STR_EQ(expected, m, fragment);
-  MESSAGE_CHECK_STR_EQ(expected, m, request_url);
-  if (expected->body_size) {
-    MESSAGE_CHECK_NUM_EQ(expected, m, body_size);
-  } else {
-    MESSAGE_CHECK_STR_EQ(expected, m, body);
-  }
-
-  MESSAGE_CHECK_NUM_EQ(expected, m, num_headers);
-
-  int r;
-  for (i = 0; i < m->num_headers; i++) {
-    r = check_str_eq(expected, "header field", expected->headers[i][0], m->headers[i][0]);
-    if (!r) return 0;
-    r = check_str_eq(expected, "header value", expected->headers[i][1], m->headers[i][1]);
-    if (!r) return 0;
-  }
-
-  return 1;
-}
-
-static void
-print_error (const char *raw, size_t error_location)
-{
-  fprintf(stderr, "\n*** parse error ***\n\n");
-
-  int this_line = 0, char_len = 0;
-  size_t i, j, len = strlen(raw), error_location_line = 0;
-  for (i = 0; i < len; i++) {
-    if (i == error_location) this_line = 1;
-    switch (raw[i]) {
-      case '\r':
-        char_len = 2;
-        fprintf(stderr, "\\r");
-        break;
-
-      case '\n':
-        char_len = 2;
-        fprintf(stderr, "\\n\n");
-
-        if (this_line) goto print;
-
-        error_location_line = 0;
-        continue;
-
-      default:
-        char_len = 1;
-        fputc(raw[i], stderr);
-        break;
-    }
-    if (!this_line) error_location_line += char_len;
-  }
-
-  fprintf(stderr, "[eof]\n");
-
- print:
-  for (j = 0; j < error_location_line; j++) {
-    fputc(' ', stderr);
-  }
-  fprintf(stderr, "^\n\nerror location: %u\n", (unsigned int)error_location);
-}
-
-
-void
-test_message (const struct message *message)
-{
-  size_t raw_len = strlen(message->raw);
-  size_t msg1len;
-  for (msg1len = 0; msg1len < raw_len; msg1len++) {
-    parser_init(message->type);
-
-    size_t read;
-    const char *msg1 = message->raw;
-    const char *msg2 = msg1 + msg1len;
-    size_t msg2len = raw_len - msg1len;
-
-    if (msg1len) {
-      read = parse(msg1, msg1len);
-
-      if (message->upgrade && parser->upgrade) goto test;
-
-      if (read != msg1len) {
-        print_error(msg1, read);
-        exit(1);
-      }
-    }
-
-
-    read = parse(msg2, msg2len);
-
-    if (message->upgrade && parser->upgrade) goto test;
-
-    if (read != msg2len) {
-      print_error(msg2, read);
-      exit(1);
-    }
-
-    read = parse(NULL, 0);
-
-    if (message->upgrade && parser->upgrade) goto test;
-
-    if (read != 0) {
-      print_error(message->raw, read);
-      exit(1);
-    }
-
-  test:
-
-    if (num_messages != 1) {
-      printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name);
-      exit(1);
-    }
-
-    if(!message_eq(0, message)) exit(1);
-
-    parser_free();
-  }
-}
-
-void
-test_message_count_body (const struct message *message)
-{
-  parser_init(message->type);
-
-  size_t read;
-  size_t l = strlen(message->raw);
-  size_t i, toread;
-  size_t chunk = 4024;
-
-  for (i = 0; i < l; i+= chunk) {
-    toread = MIN(l-i, chunk);
-    read = parse_count_body(message->raw + i, toread);
-    if (read != toread) {
-      print_error(message->raw, read);
-      exit(1);
-    }
-  }
-
-
-  read = parse_count_body(NULL, 0);
-  if (read != 0) {
-    print_error(message->raw, read);
-    exit(1);
-  }
-
-  if (num_messages != 1) {
-    printf("\n*** num_messages != 1 after testing '%s' ***\n\n", message->name);
-    exit(1);
-  }
-
-  if(!message_eq(0, message)) exit(1);
-
-  parser_free();
-}
-
-void
-test_simple (const char *buf, int should_pass)
-{
-  parser_init(HTTP_REQUEST);
-
-  size_t parsed;
-  int pass;
-  parsed = parse(buf, strlen(buf));
-  pass = (parsed == strlen(buf));
-  parsed = parse(NULL, 0);
-  pass &= (parsed == 0);
-
-  parser_free();
-
-  if (pass != should_pass) {
-    fprintf(stderr, "\n*** test_simple expected %s ***\n\n%s", should_pass ? "success" : "error", buf);
-    exit(1);
-  }
-}
-
-void
-test_header_overflow_error (int req)
-{
-  http_parser parser;
-  http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
-  size_t parsed;
-  const char *buf;
-  buf = req ? "GET / HTTP/1.1\r\n" : "HTTP/1.0 200 OK\r\n";
-  parsed = http_parser_execute(&parser, &settings_null, buf, strlen(buf));
-  assert(parsed == strlen(buf));
-
-  buf = "header-key: header-value\r\n";
-  int i;
-  for (i = 0; i < 10000; i++) {
-    if (http_parser_execute(&parser, &settings_null, buf, strlen(buf)) != strlen(buf)) {
-      //fprintf(stderr, "error found on iter %d\n", i);
-      return;
-    }
-  }
-
-  fprintf(stderr, "\n*** Error expected but none in header overflow test ***\n");
-  exit(1);
-}
-
-void
-test_no_overflow_long_body (int req, size_t length)
-{
-  http_parser parser;
-  http_parser_init(&parser, req ? HTTP_REQUEST : HTTP_RESPONSE);
-  size_t parsed;
-  size_t i;
-  char buf1[3000];
-  size_t buf1len = sprintf(buf1, "%s\r\nConnection: Keep-Alive\r\nContent-Length: %zu\r\n\r\n",
-      req ? "POST / HTTP/1.0" : "HTTP/1.0 200 OK", length);
-  parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len);
-  if (parsed != buf1len)
-    goto err;
-
-  for (i = 0; i < length; i++) {
-    char foo = 'a';
-    parsed = http_parser_execute(&parser, &settings_null, &foo, 1);
-    if (parsed != 1)
-      goto err;
-  }
-
-  parsed = http_parser_execute(&parser, &settings_null, buf1, buf1len);
-  if (parsed != buf1len) goto err;
-  return;
-
- err:
-  fprintf(stderr,
-          "\n*** error in test_no_overflow_long_body %s of length %zu ***\n",
-          req ? "REQUEST" : "RESPONSE",
-          length);
-  exit(1);
-}
-
-void
-test_multiple3 (const struct message *r1, const struct message *r2, const struct message *r3)
-{
-  int message_count = 1;
-  if (!r1->upgrade) {
-    message_count++;
-    if (!r2->upgrade) message_count++;
-  }
-  int has_upgrade = (message_count < 3 || r3->upgrade);
-
-  char total[ strlen(r1->raw)
-            + strlen(r2->raw)
-            + strlen(r3->raw)
-            + 1
-            ];
-  total[0] = '\0';
-
-  strcat(total, r1->raw);
-  strcat(total, r2->raw);
-  strcat(total, r3->raw);
-
-  parser_init(r1->type);
-
-  size_t read;
-
-  read = parse(total, strlen(total));
-
-  if (has_upgrade && parser->upgrade) goto test;
-
-  if (read != strlen(total)) {
-    print_error(total, read);
-    exit(1);
-  }
-
-  read = parse(NULL, 0);
-
-  if (has_upgrade && parser->upgrade) goto test;
-
-  if (read != 0) {
-    print_error(total, read);
-    exit(1);
-  }
-
-test:
-
-  if (message_count != num_messages) {
-    fprintf(stderr, "\n\n*** Parser didn't see 3 messages only %d *** \n", num_messages);
-    exit(1);
-  }
-
-  if (!message_eq(0, r1)) exit(1);
-  if (message_count > 1) {
-    if (!message_eq(1, r2)) exit(1);
-    if (message_count > 2) {
-      if (!message_eq(2, r3)) exit(1);
-    }
-  }
-
-  parser_free();
-}
-
-/* SCAN through every possible breaking to make sure the
- * parser can handle getting the content in any chunks that
- * might come from the socket
- */
-void
-test_scan (const struct message *r1, const struct message *r2, const struct message *r3)
-{
-  char total[80*1024] = "\0";
-  char buf1[80*1024] = "\0";
-  char buf2[80*1024] = "\0";
-  char buf3[80*1024] = "\0";
-
-  strcat(total, r1->raw);
-  strcat(total, r2->raw);
-  strcat(total, r3->raw);
-
-  size_t read;
-
-  int total_len = strlen(total);
-
-  int total_ops = 2 * (total_len - 1) * (total_len - 2) / 2;
-  int ops = 0 ;
-
-  size_t buf1_len, buf2_len, buf3_len;
-
-  int i,j,type_both;
-  for (type_both = 0; type_both < 2; type_both ++ ) {
-    for (j = 2; j < total_len; j ++ ) {
-      for (i = 1; i < j; i ++ ) {
-
-        if (ops % 1000 == 0)  {
-          printf("\b\b\b\b%3.0f%%", 100 * (float)ops /(float)total_ops);
-          fflush(stdout);
-        }
-        ops += 1;
-
-        parser_init(type_both ? HTTP_BOTH : r1->type);
-
-        buf1_len = i;
-        strncpy(buf1, total, buf1_len);
-        buf1[buf1_len] = 0;
-
-        buf2_len = j - i;
-        strncpy(buf2, total+i, buf2_len);
-        buf2[buf2_len] = 0;
-
-        buf3_len = total_len - j;
-        strncpy(buf3, total+j, buf3_len);
-        buf3[buf3_len] = 0;
-
-        read = parse(buf1, buf1_len);
-
-        if (r3->upgrade && parser->upgrade) goto test;
-
-        if (read != buf1_len) {
-          print_error(buf1, read);
-          goto error;
-        }
-
-        read = parse(buf2, buf2_len);
-
-        if (r3->upgrade && parser->upgrade) goto test;
-
-        if (read != buf2_len) {
-          print_error(buf2, read);
-          goto error;
-        }
-
-        read = parse(buf3, buf3_len);
-
-        if (r3->upgrade && parser->upgrade) goto test;
-
-        if (read != buf3_len) {
-          print_error(buf3, read);
-          goto error;
-        }
-
-        parse(NULL, 0);
-
-test:
-
-        if (3 != num_messages) {
-          fprintf(stderr, "\n\nParser didn't see 3 messages only %d\n", num_messages);
-          goto error;
-        }
-
-        if (!message_eq(0, r1)) {
-          fprintf(stderr, "\n\nError matching messages[0] in test_scan.\n");
-          goto error;
-        }
-
-        if (!message_eq(1, r2)) {
-          fprintf(stderr, "\n\nError matching messages[1] in test_scan.\n");
-          goto error;
-        }
-
-        if (!message_eq(2, r3)) {
-          fprintf(stderr, "\n\nError matching messages[2] in test_scan.\n");
-          goto error;
-        }
-
-        parser_free();
-      }
-    }
-  }
-  puts("\b\b\b\b100%");
-  return;
-
- error:
-  fprintf(stderr, "i=%d  j=%d\n", i, j);
-  fprintf(stderr, "buf1 (%u) %s\n\n", (unsigned int)buf1_len, buf1);
-  fprintf(stderr, "buf2 (%u) %s\n\n", (unsigned int)buf2_len , buf2);
-  fprintf(stderr, "buf3 (%u) %s\n", (unsigned int)buf3_len, buf3);
-  exit(1);
-}
-
-// user required to free the result
-// string terminated by \0
-char *
-create_large_chunked_message (int body_size_in_kb, const char* headers)
-{
-  int i;
-  size_t wrote = 0;
-  size_t headers_len = strlen(headers);
-  size_t bufsize = headers_len + (5+1024+2)*body_size_in_kb + 6;
-  char * buf = malloc(bufsize);
-
-  memcpy(buf, headers, headers_len);
-  wrote += headers_len;
-
-  for (i = 0; i < body_size_in_kb; i++) {
-    // write 1kb chunk into the body.
-    memcpy(buf + wrote, "400\r\n", 5);
-    wrote += 5;
-    memset(buf + wrote, 'C', 1024);
-    wrote += 1024;
-    strcpy(buf + wrote, "\r\n");
-    wrote += 2;
-  }
-
-  memcpy(buf + wrote, "0\r\n\r\n", 6);
-  wrote += 6;
-  assert(wrote == bufsize);
-
-  return buf;
-}
-
-
-int
-main (void)
-{
-  parser = NULL;
-  int i, j, k;
-  int request_count;
-  int response_count;
-
-  printf("sizeof(http_parser) = %u\n", (unsigned int)sizeof(http_parser));
-
-  for (request_count = 0; requests[request_count].name; request_count++);
-  for (response_count = 0; responses[response_count].name; response_count++);
-
-  //// OVERFLOW CONDITIONS
-
-  test_header_overflow_error(HTTP_REQUEST);
-  test_no_overflow_long_body(HTTP_REQUEST, 1000);
-  test_no_overflow_long_body(HTTP_REQUEST, 100000);
-
-  test_header_overflow_error(HTTP_RESPONSE);
-  test_no_overflow_long_body(HTTP_RESPONSE, 1000);
-  test_no_overflow_long_body(HTTP_RESPONSE, 100000);
-
-  //// RESPONSES
-
-  for (i = 0; i < response_count; i++) {
-    test_message(&responses[i]);
-  }
-
-  for (i = 0; i < response_count; i++) {
-    if (!responses[i].should_keep_alive) continue;
-    for (j = 0; j < response_count; j++) {
-      if (!responses[j].should_keep_alive) continue;
-      for (k = 0; k < response_count; k++) {
-        test_multiple3(&responses[i], &responses[j], &responses[k]);
-      }
-    }
-  }
-
-  test_message_count_body(&responses[NO_HEADERS_NO_BODY_404]);
-  test_message_count_body(&responses[TRAILING_SPACE_ON_CHUNKED_BODY]);
-
-  // test very large chunked response
-  {
-    char * msg = create_large_chunked_message(31337,
-      "HTTP/1.0 200 OK\r\n"
-      "Transfer-Encoding: chunked\r\n"
-      "Content-Type: text/plain\r\n"
-      "\r\n");
-    struct message large_chunked =
-      {.name= "large chunked"
-      ,.type= HTTP_RESPONSE
-      ,.raw= msg
-      ,.should_keep_alive= FALSE
-      ,.message_complete_on_eof= FALSE
-      ,.http_major= 1
-      ,.http_minor= 0
-      ,.status_code= 200
-      ,.num_headers= 2
-      ,.headers=
-        { { "Transfer-Encoding", "chunked" }
-        , { "Content-Type", "text/plain" }
-        }
-      ,.body_size= 31337*1024
-      };
-    test_message_count_body(&large_chunked);
-    free(msg);
-  }
-
-
-
-  printf("response scan 1/2      ");
-  test_scan( &responses[TRAILING_SPACE_ON_CHUNKED_BODY]
-           , &responses[NO_HEADERS_NO_BODY_404]
-           , &responses[NO_REASON_PHRASE]
-           );
-
-  printf("response scan 2/2      ");
-  test_scan( &responses[BONJOUR_MADAME_FR]
-           , &responses[UNDERSTORE_HEADER_KEY]
-           , &responses[NO_CARRIAGE_RET]
-           );
-
-  puts("responses okay");
-
-
-  /// REQUESTS
-
-  test_simple("hello world", 0);
-  test_simple("GET / HTP/1.1\r\n\r\n", 0);
-
-
-  test_simple("ASDF / HTTP/1.1\r\n\r\n", 0);
-  test_simple("PROPPATCHA / HTTP/1.1\r\n\r\n", 0);
-  test_simple("GETA / HTTP/1.1\r\n\r\n", 0);
-
-  // Well-formed but incomplete
-  test_simple("GET / HTTP/1.1\r\n"
-              "Content-Type: text/plain\r\n"
-              "Content-Length: 6\r\n"
-              "\r\n"
-              "fooba",
-              0);
-
-  static const char *all_methods[] = {
-    "DELETE",
-    "GET",
-    "HEAD",
-    "POST",
-    "PUT",
-    //"CONNECT", //CONNECT can't be tested like other methods, it's a tunnel
-    "OPTIONS",
-    "TRACE",
-    "COPY",
-    "LOCK",
-    "MKCOL",
-    "MOVE",
-    "PROPFIND",
-    "PROPPATCH",
-    "UNLOCK",
-    0 };
-  const char **this_method;
-  for (this_method = all_methods; *this_method; this_method++) {
-    char buf[200];
-    sprintf(buf, "%s / HTTP/1.1\r\n\r\n", *this_method);
-    test_simple(buf, 1);
-  }
-
-  const char *dumbfuck2 =
-    "GET / HTTP/1.1\r\n"
-    "X-SSL-Bullshit:   -----BEGIN CERTIFICATE-----\r\n"
-    "\tMIIFbTCCBFWgAwIBAgICH4cwDQYJKoZIhvcNAQEFBQAwcDELMAkGA1UEBhMCVUsx\r\n"
-    "\tETAPBgNVBAoTCGVTY2llbmNlMRIwEAYDVQQLEwlBdXRob3JpdHkxCzAJBgNVBAMT\r\n"
-    "\tAkNBMS0wKwYJKoZIhvcNAQkBFh5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMu\r\n"
-    "\tdWswHhcNMDYwNzI3MTQxMzI4WhcNMDcwNzI3MTQxMzI4WjBbMQswCQYDVQQGEwJV\r\n"
-    "\tSzERMA8GA1UEChMIZVNjaWVuY2UxEzARBgNVBAsTCk1hbmNoZXN0ZXIxCzAJBgNV\r\n"
-    "\tBAcTmrsogriqMWLAk1DMRcwFQYDVQQDEw5taWNoYWVsIHBhcmQYJKoZIhvcNAQEB\r\n"
-    "\tBQADggEPADCCAQoCggEBANPEQBgl1IaKdSS1TbhF3hEXSl72G9J+WC/1R64fAcEF\r\n"
-    "\tW51rEyFYiIeZGx/BVzwXbeBoNUK41OK65sxGuflMo5gLflbwJtHBRIEKAfVVp3YR\r\n"
-    "\tgW7cMA/s/XKgL1GEC7rQw8lIZT8RApukCGqOVHSi/F1SiFlPDxuDfmdiNzL31+sL\r\n"
-    "\t0iwHDdNkGjy5pyBSB8Y79dsSJtCW/iaLB0/n8Sj7HgvvZJ7x0fr+RQjYOUUfrePP\r\n"
-    "\tu2MSpFyf+9BbC/aXgaZuiCvSR+8Snv3xApQY+fULK/xY8h8Ua51iXoQ5jrgu2SqR\r\n"
-    "\twgA7BUi3G8LFzMBl8FRCDYGUDy7M6QaHXx1ZWIPWNKsCAwEAAaOCAiQwggIgMAwG\r\n"
-    "\tA1UdEwEB/wQCMAAwEQYJYIZIAYb4QgHTTPAQDAgWgMA4GA1UdDwEB/wQEAwID6DAs\r\n"
-    "\tBglghkgBhvhCAQ0EHxYdVUsgZS1TY2llbmNlIFVzZXIgQ2VydGlmaWNhdGUwHQYD\r\n"
-    "\tVR0OBBYEFDTt/sf9PeMaZDHkUIldrDYMNTBZMIGaBgNVHSMEgZIwgY+AFAI4qxGj\r\n"
-    "\tloCLDdMVKwiljjDastqooXSkcjBwMQswCQYDVQQGEwJVSzERMA8GA1UEChMIZVNj\r\n"
-    "\taWVuY2UxEjAQBgNVBAsTCUF1dGhvcml0eTELMAkGA1UEAxMCQ0ExLTArBgkqhkiG\r\n"
-    "\t9w0BCQEWHmNhLW9wZXJhdG9yQGdyaWQtc3VwcG9ydC5hYy51a4IBADApBgNVHRIE\r\n"
-    "\tIjAggR5jYS1vcGVyYXRvckBncmlkLXN1cHBvcnQuYWMudWswGQYDVR0gBBIwEDAO\r\n"
-    "\tBgwrBgEEAdkvAQEBAQYwPQYJYIZIAYb4QgEEBDAWLmh0dHA6Ly9jYS5ncmlkLXN1\r\n"
-    "\tcHBvcnQuYWMudmT4sopwqlBWsvcHViL2NybC9jYWNybC5jcmwwPQYJYIZIAYb4QgEDBDAWLmh0\r\n"
-    "\tdHA6Ly9jYS5ncmlkLXN1cHBvcnQuYWMudWsvcHViL2NybC9jYWNybC5jcmwwPwYD\r\n"
-    "\tVR0fBDgwNjA0oDKgMIYuaHR0cDovL2NhLmdyaWQt5hYy51ay9wdWIv\r\n"
-    "\tY3JsL2NhY3JsLmNybDANBgkqhkiG9w0BAQUFAAOCAQEAS/U4iiooBENGW/Hwmmd3\r\n"
-    "\tXCy6Zrt08YjKCzGNjorT98g8uGsqYjSxv/hmi0qlnlHs+k/3Iobc3LjS5AMYr5L8\r\n"
-    "\tUO7OSkgFFlLHQyC9JzPfmLCAugvzEbyv4Olnsr8hbxF1MbKZoQxUZtMVu29wjfXk\r\n"
-    "\thTeApBv7eaKCWpSp7MCbvgzm74izKhu3vlDk9w6qVrxePfGgpKPqfHiOoGhFnbTK\r\n"
-    "\twTC6o2xq5y0qZ03JonF7OJspEd3I5zKY3E+ov7/ZhW6DqT8UFvsAdjvQbXyhV8Eu\r\n"
-    "\tYhixw1aKEPzNjNowuIseVogKOLXxWI5vAi5HgXdS0/ES5gDGsABo4fqovUKlgop3\r\n"
-    "\tRA==\r\n"
-    "\t-----END CERTIFICATE-----\r\n"
-    "\r\n";
-  test_simple(dumbfuck2, 0);
-
-#if 0
-  // NOTE(Wed Nov 18 11:57:27 CET 2009) this seems okay. we just read body
-  // until EOF.
-  //
-  // no content-length
-  // error if there is a body without content length
-  const char *bad_get_no_headers_no_body = "GET /bad_get_no_headers_no_body/world HTTP/1.1\r\n"
-                                           "Accept: */*\r\n"
-                                           "\r\n"
-                                           "HELLO";
-  test_simple(bad_get_no_headers_no_body, 0);
-#endif
-  /* TODO sending junk and large headers gets rejected */
-
-
-  /* check to make sure our predefined requests are okay */
-  for (i = 0; requests[i].name; i++) {
-    test_message(&requests[i]);
-  }
-
-
-
-  for (i = 0; i < request_count; i++) {
-    if (!requests[i].should_keep_alive) continue;
-    for (j = 0; j < request_count; j++) {
-      if (!requests[j].should_keep_alive) continue;
-      for (k = 0; k < request_count; k++) {
-        test_multiple3(&requests[i], &requests[j], &requests[k]);
-      }
-    }
-  }
-
-  printf("request scan 1/4      ");
-  test_scan( &requests[GET_NO_HEADERS_NO_BODY]
-           , &requests[GET_ONE_HEADER_NO_BODY]
-           , &requests[GET_NO_HEADERS_NO_BODY]
-           );
-
-  printf("request scan 2/4      ");
-  test_scan( &requests[POST_CHUNKED_ALL_YOUR_BASE]
-           , &requests[POST_IDENTITY_BODY_WORLD]
-           , &requests[GET_FUNKY_CONTENT_LENGTH]
-           );
-
-  printf("request scan 3/4      ");
-  test_scan( &requests[TWO_CHUNKS_MULT_ZERO_END]
-           , &requests[CHUNKED_W_TRAILING_HEADERS]
-           , &requests[CHUNKED_W_BULLSHIT_AFTER_LENGTH]
-           );
-
-  printf("request scan 4/4      ");
-  test_scan( &requests[QUERY_URL_WITH_QUESTION_MARK_GET]
-           , &requests[PREFIX_NEWLINE_GET ]
-           , &requests[CONNECT_REQUEST]
-           );
-
-  puts("requests okay");
-
-  return 0;
-}
diff --git a/libhtparse/Makefile b/libhtparse/Makefile
new file mode 100644
index 0000000..8028cae
--- /dev/null
+++ b/libhtparse/Makefile
@@ -0,0 +1,23 @@
+SRC      = htparse.c 
+OUT      = libhtparse.a
+OBJ      = $(SRC:.c=.o)
+INCLUDES = -I.
+CFLAGS   += -ggdb -Wall -Wextra
+LDFLAGS  += 
+CC       = gcc
+
+.SUFFIXES: .c
+
+default: $(OUT)
+
+.c.o:
+	$(CC) $(INCLUDES) $(CFLAGS) -c $< -o $@
+
+$(OUT): $(OBJ)
+	ar rcs $(OUT) $(OBJ)
+
+test: $(OUT) test.c
+	$(CC) $(INCLUDES) $(CFLAGS) test.c -o test $(OUT)
+
+clean:
+	rm -f $(OBJ) $(OUT) test
diff --git a/libhtparse/htparse.c b/libhtparse/htparse.c
new file mode 100644
index 0000000..f285487
--- /dev/null
+++ b/libhtparse/htparse.c
@@ -0,0 +1,1488 @@
+#include <stdio.h>
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "htparse.h"
+
+#ifdef PARSER_DEBUG
+#define __QUOTE(x)                  # x
+#define  _QUOTE(x)                  __QUOTE(x)
+#define htparse_debug_strlen(x)     strlen(x)
+
+#define htparse_log_debug(fmt, ...) do {                                                           \
+        time_t      t  = time(NULL);                                                               \
+        struct tm * dm = localtime(&t);                                                            \
+                                                                                                   \
+        fprintf(stdout, "[%02d:%02d:%02d] htparse.c" _QUOTE(__LINE__) "]\t                %-26s: " \
+                fmt "\n", dm->tm_hour, dm->tm_min, dm->tm_sec, __func__, ## __VA_ARGS__);          \
+        fflush(stdout);                                                                            \
+} while (0)
+
+#else
+#define htparse_debug_strlen(x)     0
+#define htparse_log_debug(fmt, ...) do {} while (0)
+#endif
+
+#define PARSER_STACK_MAX 1024
+#define LF               (unsigned char)10
+#define CR               (unsigned char)13
+#define CRLF             "\x0d\x0a"
+
+typedef enum eval_hdr_val eval_hdr_val;
+typedef enum parser_flags parser_flags;
+typedef enum parser_state parser_state;
+
+enum eval_hdr_val {
+    eval_hdr_val_none = 0,
+    eval_hdr_val_connection,
+    eval_hdr_val_proxy_connection,
+    eval_hdr_val_content_length,
+    eval_hdr_val_transfer_encoding
+};
+
+enum parser_flags {
+    parser_flag_chunked               = 1 << 0,
+    parser_flag_connection_keep_alive = 1 << 1,
+    parser_flag_connection_close      = 1 << 2,
+    parser_flag_trailing              = 1 << 3,
+};
+
+enum parser_state {
+    s_reqline_start = 0,
+    s_reqline_method,
+    s_reqline_spaces_before_uri,
+    s_reqline_schema,
+    s_reqline_schema_slash,
+    s_reqline_schema_slash_slash,
+    s_reqline_host,
+    s_reqline_port,
+    s_reqline_after_slash_in_uri,
+    s_reqline_check_uri,
+    s_reqline_uri,
+    s_reqline_http_09,
+    s_reqline_http_H,
+    s_reqline_http_HT,
+    s_reqline_http_HTT,
+    s_reqline_http_HTTP,
+    s_reqline_first_major_digit,
+    s_reqline_major_digit,
+    s_reqline_first_minor_digit,
+    s_reqline_minor_digit,
+    s_reqline_spaces_after_digit,
+    s_reqline_almost_done,
+    s_reqline_done,
+    s_hdrline_start,
+    s_hdrline_hdr_almost_done,
+    s_hdrline_hdr_done,
+    s_hdrline_hdr_key,
+    s_hdrline_hdr_space_before_val,
+    s_hdrline_hdr_val,
+    s_hdrline_almost_done,
+    s_hdrline_done,
+    s_body_read,
+    s_chunk_size_start,
+    s_chunk_size,
+    s_chunk_size_almost_done,
+    s_chunk_data,
+    s_chunk_data_almost_done,
+    s_chunk_data_done
+};
+
+struct htparser {
+    htpparse_error error;
+    parser_state   state;
+    parser_flags   flags;
+    eval_hdr_val   heval;
+
+    htp_scheme scheme;
+    htp_method method;
+
+    unsigned char major;
+    unsigned char minor;
+    uint64_t      content_len;
+
+    char         buf[PARSER_STACK_MAX];
+    unsigned int buf_idx;
+
+    char * scheme_offset;
+    char * host_offset;
+    char * port_offset;
+    char * path_offset;
+    char * args_offset;
+
+    void * userdata;
+};
+
+static uint32_t     usual[] = {
+    0xffffdbfe,
+    0x7fff37d6,
+    0xffffffff,
+    0xffffffff,
+    0xffffffff,
+    0xffffffff,
+    0xffffffff,
+    0xffffffff
+};
+
+static int8_t       unhex[256] = {
+    -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
+};
+
+static const char * errstr_map[] = {
+    "htparse_error_none",
+    "htparse_error_too_big",
+    "htparse_error_invalid_method",
+    "htparse_error_invalid_requestline",
+    "htparse_error_invalid_schema",
+    "htparse_error_invalid_protocol",
+    "htparse_error_invalid_version",
+    "htparse_error_invalid_header",
+    "htparse_error_invalid_chunk_size",
+    "htparse_error_invalid_chunk",
+    "htparse_error_invalid_state",
+    "htparse_error_user",
+    "htparse_error_unknown"
+};
+
+static const char * method_strmap[] = {
+    "GET",
+    "HEAD",
+    "POST",
+    "PUT",
+    "DELETE",
+    "MKCOL",
+    "COPY",
+    "MOVE",
+    "OPTIONS",
+    "PROPFIND",
+    "PROPATCH",
+    "LOCK",
+    "UNLOCK",
+    "TRACE"
+};
+
+#define _MIN_READ(a, b) ((a) < (b) ? (a) : (b))
+
+#define _str3_cmp(m, c0, c1, c2, c3) \
+    *(uint32_t *)m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0)
+
+#define _str3Ocmp(m, c0, c1, c2, c3) \
+    *(uint32_t *)m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0)
+
+#define _str4cmp(m, c0, c1, c2, c3) \
+    *(uint32_t *)m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0)
+
+#define _str5cmp(m, c0, c1, c2, c3, c4)                          \
+    *(uint32_t *)m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \
+    && m[4] == c4
+
+#define _str6cmp(m, c0, c1, c2, c3, c4, c5)                      \
+    *(uint32_t *)m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \
+    && (((uint32_t *)m)[1] & 0xffff) == ((c5 << 8) | c4)
+
+#define _str7_cmp(m, c0, c1, c2, c3, c4, c5, c6, c7)             \
+    *(uint32_t *)m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \
+    && ((uint32_t *)m)[1] == ((c7 << 24) | (c6 << 16) | (c5 << 8) | c4)
+
+#define _str8cmp(m, c0, c1, c2, c3, c4, c5, c6, c7)              \
+    *(uint32_t *)m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0) \
+    && ((uint32_t *)m)[1] == ((c7 << 24) | (c6 << 16) | (c5 << 8) | c4)
+
+#define _str9cmp(m, c0, c1, c2, c3, c4, c5, c6, c7, c8)                 \
+    *(uint32_t *)m == ((c3 << 24) | (c2 << 16) | (c1 << 8) | c0)        \
+    && ((uint32_t *)m)[1] == ((c7 << 24) | (c6 << 16) | (c5 << 8) | c4) \
+    && m[8] == c8
+
+#define __HTPARSE_GENHOOK(__n)                                                    \
+    static inline int hook_ ## __n ## _run(htparser * p, htparse_hooks * hooks) { \
+        htparse_log_debug("enter");                                               \
+        if (hooks && (hooks)->__n) {                                              \
+            return (hooks)->__n(p);                                               \
+        }                                                                         \
+                                                                                  \
+        return 0;                                                                 \
+    }
+
+#define __HTPARSE_GENDHOOK(__n)                                                                             \
+    static inline int hook_ ## __n ## _run(htparser * p, htparse_hooks * hooks, const char * s, size_t l) { \
+        htparse_log_debug("enter");                                                                         \
+        if (hooks && (hooks)->__n) {                                                                        \
+            return (hooks)->__n(p, s, l);                                                                   \
+        }                                                                                                   \
+                                                                                                            \
+        return 0;                                                                                           \
+    }
+
+__HTPARSE_GENHOOK(on_msg_begin);
+__HTPARSE_GENHOOK(on_hdrs_begin);
+__HTPARSE_GENHOOK(on_hdrs_complete);
+__HTPARSE_GENHOOK(on_new_chunk);
+__HTPARSE_GENHOOK(on_msg_complete);
+
+__HTPARSE_GENDHOOK(method);
+__HTPARSE_GENDHOOK(scheme);
+__HTPARSE_GENDHOOK(host);
+__HTPARSE_GENDHOOK(port);
+__HTPARSE_GENDHOOK(path);
+__HTPARSE_GENDHOOK(args);
+__HTPARSE_GENDHOOK(uri);
+__HTPARSE_GENDHOOK(hdr_key);
+__HTPARSE_GENDHOOK(hdr_val);
+__HTPARSE_GENDHOOK(body);
+
+
+static inline uint64_t
+str_to_uint64(char * str, size_t n, int * err) {
+    uint64_t value;
+
+    if (n > 20) {
+        /* 18446744073709551615 is 20 bytes */
+        *err = 1;
+        return 0;
+    }
+
+    for (value = 0; n--; str++) {
+        if (!isdigit(*str)) {
+            *err = 1;
+            return 0;
+        }
+
+        value = value * 10 + (*str - '0');
+    }
+
+    return value;
+}
+
+static inline ssize_t
+_str_to_ssize_t(char * str, size_t n) {
+    ssize_t value;
+
+    if (n == 0) {
+        return -1;
+    }
+
+    for (value = 0; n--; str++) {
+        if (*str < '0' || *str > '9') {
+            return -1;
+        }
+
+        value = value * 10 + (*str - '0');
+    }
+
+    return value;
+}
+
+htpparse_error
+htparser_get_error(htparser * p) {
+    return p->error;
+}
+
+const char *
+htparser_get_strerror(htparser * p) {
+    htpparse_error e = htparser_get_error(p);
+
+    if (e > htparse_error_generic) {
+        return "htparse_no_such_error";
+    }
+
+    return errstr_map[e];
+}
+
+int
+htparser_should_keep_alive(htparser * p) {
+    if (p->major > 0 && p->minor > 0) {
+        if (p->flags & parser_flag_connection_close) {
+            return 0;
+        }
+
+        return 1;
+    }
+
+    if (p->flags & parser_flag_connection_keep_alive) {
+        return 1;
+    }
+
+    return 0;
+}
+
+htp_scheme
+htparser_get_scheme(htparser * p) {
+    return p->scheme;
+}
+
+htp_method
+htparser_get_method(htparser * p) {
+    return p->method;
+}
+
+const char *
+htparser_get_methodstr(htparser * p) {
+    if (p->method >= htp_method_UNKNOWN) {
+        return NULL;
+    }
+
+    return method_strmap[p->method];
+}
+
+unsigned char
+htparser_get_major(htparser * p) {
+    return p->major;
+}
+
+unsigned char
+htparser_get_minor(htparser * p) {
+    return p->minor;
+}
+
+void *
+htparser_get_userdata(htparser * p) {
+    return p->userdata;
+}
+
+void
+htparser_set_userdata(htparser * p, void * ud) {
+    p->userdata = ud;
+}
+
+uint64_t
+htparser_get_content_length(htparser * p) {
+    return p->content_len;
+}
+
+void
+htparser_init(htparser * p) {
+    memset(p, 0, sizeof(htparser));
+    p->error = htparse_error_none;
+}
+
+htparser *
+htparser_new(void) {
+    return malloc(sizeof(htparser));
+}
+
+size_t
+htparser_run(htparser * p, htparse_hooks * hooks, const char * data, size_t len) {
+    unsigned char ch;
+    char          c;
+    size_t        i;
+
+    htparse_log_debug("enter");
+
+    p->error = htparse_error_none;
+
+    for (i = 0; i < len; i++) {
+        int res;
+        int err;
+
+        ch = data[i];
+
+        htparse_log_debug("data[%d] = %c (%x)", i, isprint(ch) ? ch : ' ', ch);
+
+        if (p->buf_idx >= sizeof(p->buf)) {
+            p->error = htparse_error_too_big;
+            return i + 1;
+        }
+
+        switch (p->state) {
+            case s_reqline_start:
+                htparse_log_debug("s_reqline_start");
+
+                p->flags = 0;
+
+                switch (ch) {
+                    case CR:
+                    case LF:
+                        break;
+                }
+
+                if ((ch < 'A' || ch > 'Z') && ch != '_') {
+                    p->error = htparse_error_inval_reqline;
+                    return i + 1;
+                }
+
+                res = hook_on_msg_begin_run(p, hooks);
+
+                p->buf[p->buf_idx++] = ch;
+                p->buf[p->buf_idx]   = '\0';
+                p->state = s_reqline_method;
+
+                if (res) {
+                    p->error = htparse_error_user;
+                    return i + 1;
+                }
+
+                break;
+
+            case s_reqline_method:
+                htparse_log_debug("s_reqline_method");
+
+                if (ch == ' ') {
+                    char * m = p->buf;
+
+                    switch (p->buf_idx) {
+                        case 3:
+                            if (_str3_cmp(m, 'G', 'E', 'T', ' ')) {
+                                p->method = htp_method_GET;
+                                break;
+                            }
+
+                            if (_str3_cmp(m, 'P', 'U', 'T', ' ')) {
+                                p->method = htp_method_PUT;
+                                break;
+                            }
+
+                            break;
+                        case 4:
+                            if (m[1] == 'O') {
+                                if (_str3Ocmp(m, 'P', 'O', 'S', 'T')) {
+                                    p->method = htp_method_POST;
+                                    break;
+                                }
+
+                                if (_str3Ocmp(m, 'C', 'O', 'P', 'Y')) {
+                                    p->method = htp_method_COPY;
+                                    break;
+                                }
+
+                                if (_str3Ocmp(m, 'M', 'O', 'V', 'E')) {
+                                    p->method = htp_method_MOVE;
+                                    break;
+                                }
+
+                                if (_str3Ocmp(m, 'L', 'O', 'C', 'K')) {
+                                    p->method = htp_method_LOCK;
+                                    break;
+                                }
+                            } else {
+                                if (_str4cmp(m, 'H', 'E', 'A', 'D')) {
+                                    p->method = htp_method_HEAD;
+                                    break;
+                                }
+                            }
+                            break;
+                        case 5:
+                            if (_str5cmp(m, 'M', 'K', 'C', 'O', 'L')) {
+                                p->method = htp_method_MKCOL;
+                                break;
+                            }
+
+                            if (_str5cmp(m, 'T', 'R', 'A', 'C', 'E')) {
+                                p->method = htp_method_TRACE;
+                                break;
+                            }
+                            break;
+                        case 6:
+                            if (_str6cmp(m, 'D', 'E', 'L', 'E', 'T', 'E')) {
+                                p->method = htp_method_DELETE;
+                                break;
+                            }
+
+                            if (_str6cmp(m, 'U', 'N', 'L', 'O', 'C', 'K')) {
+                                p->method = htp_method_UNLOCK;
+                                break;
+                            }
+                            break;
+                        case 7:
+                            if (_str7_cmp(m, 'O', 'P', 'T', 'I', 'O', 'N', 'S', ' ')) {
+                                p->method = htp_method_OPTIONS;
+                            }
+
+                            break;
+                        case 8:
+                            if (_str8cmp(m, 'P', 'R', 'O', 'P', 'F', 'I', 'N', 'D')) {
+                                p->method = htp_method_PROPFIND;
+                            }
+
+                            break;
+
+                        case 9:
+                            if (_str9cmp(m, 'P', 'R', 'O', 'P', 'P', 'A', 'T', 'C', 'H')) {
+                                p->method = htp_method_PROPPATCH;
+                            }
+                            break;
+                    } /* switch */
+
+                    res        = hook_method_run(p, hooks, p->buf, p->buf_idx);
+                    p->buf_idx = 0;
+                    p->state   = s_reqline_spaces_before_uri;
+
+                    if (res) {
+                        p->error = htparse_error_user;
+                        return i + 1;
+                    }
+
+                    break;
+                }
+
+                if ((ch < 'A' || ch > 'Z') && ch != '_') {
+                    p->error = htparse_error_inval_method;
+                    return i + 1;
+                }
+
+                p->buf[p->buf_idx++] = ch;
+                p->buf[p->buf_idx]   = '\0';
+
+                break;
+            case s_reqline_spaces_before_uri:
+                htparse_log_debug("s_reqline_spaces_before_uri");
+
+                switch (ch) {
+                    case ' ':
+                        break;
+                    case '/':
+                        p->path_offset       = &p->buf[p->buf_idx];
+
+                        p->buf[p->buf_idx++] = ch;
+                        p->buf[p->buf_idx]   = '\0';
+                        p->state = s_reqline_after_slash_in_uri;
+                        break;
+                    default:
+                        c = (unsigned char)(ch | 0x20);
+
+                        if (c >= 'a' && c <= 'z') {
+                            p->scheme_offset     = &p->buf[p->buf_idx];
+                            p->buf[p->buf_idx++] = ch;
+                            p->buf[p->buf_idx]   = '\0';
+                            p->state = s_reqline_schema;
+                            break;
+                        }
+
+                        p->error = htparse_error_inval_reqline;
+                        return i + 1;
+                } /* switch */
+
+                break;
+            case s_reqline_schema:
+                htparse_log_debug("s_reqline_schema");
+
+                c = (unsigned char)(ch | 0x20);
+
+                if (c >= 'a' && c <= 'z') {
+                    p->buf[p->buf_idx++] = ch;
+                    p->buf[p->buf_idx]   = '\0';
+                    break;
+                }
+
+                switch (ch) {
+                    case ':':
+                        p->scheme = htp_scheme_unknown;
+
+                        switch (p->buf_idx) {
+                            case 3:
+                                if (_str3_cmp(p->scheme_offset, 'f', 't', 'p', ' ')) {
+                                    p->scheme = htp_scheme_ftp;
+                                    break;
+                                }
+
+                                if (_str3_cmp(p->scheme_offset, 'n', 'f', 's', ' ')) {
+                                    p->scheme = htp_scheme_nfs;
+                                    break;
+                                }
+
+                                break;
+                            case 4:
+                                if (_str4cmp(p->scheme_offset, 'h', 't', 't', 'p')) {
+                                    p->scheme = htp_scheme_http;
+                                    break;
+                                }
+                                break;
+                            case 5:
+                                if (_str5cmp(p->scheme_offset, 'h', 't', 't', 'p', 's')) {
+                                    p->scheme = htp_scheme_https;
+                                    break;
+                                }
+                                break;
+                        } /* switch */
+
+                        res = hook_scheme_run(p, hooks, p->scheme_offset, p->buf_idx);
+
+                        p->buf[p->buf_idx++] = ch;
+                        p->buf[p->buf_idx]   = '\0';
+
+                        p->state = s_reqline_schema_slash;
+
+                        if (res) {
+                            p->error = htparse_error_user;
+                            return i + 1;
+                        }
+
+                        break;
+                    default:
+                        p->error = htparse_error_inval_schema;
+                        return i + 1;
+                } /* switch */
+
+                break;
+            case s_reqline_schema_slash:
+                htparse_log_debug("s_reqline_schema_slash");
+
+                switch (ch) {
+                    case '/':
+                        p->buf[p->buf_idx++] = ch;
+                        p->buf[p->buf_idx]   = '\0';
+
+                        p->state = s_reqline_schema_slash_slash;
+                        break;
+                    default:
+                        p->error = htparse_error_inval_schema;
+                        return i + 1;
+                }
+                break;
+            case s_reqline_schema_slash_slash:
+                htparse_log_debug("s_reqline_schema_slash_slash");
+
+                switch (ch) {
+                    case '/':
+                        p->buf[p->buf_idx++] = ch;
+                        p->buf[p->buf_idx]   = '\0';
+                        p->host_offset       = &p->buf[p->buf_idx];
+
+                        p->state = s_reqline_host;
+                        break;
+                    default:
+                        p->error = htparse_error_inval_schema;
+                        return i + 1;
+                }
+                break;
+            case s_reqline_host:
+                c = (unsigned char)(ch | 0x20);
+
+                if (c >= 'a' && c <= 'z') {
+                    p->buf[p->buf_idx++] = ch;
+                    p->buf[p->buf_idx]   = '\0';
+                    break;
+                }
+
+                if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') {
+                    p->buf[p->buf_idx++] = ch;
+                    p->buf[p->buf_idx]   = '\0';
+                    break;
+                }
+
+                res = hook_host_run(p, hooks, p->host_offset, p->buf_idx);
+
+                switch (ch) {
+                    case ':':
+                        p->buf[p->buf_idx++] = ch;
+                        p->buf[p->buf_idx]   = '\0';
+
+                        p->port_offset       = &p->buf[p->buf_idx];
+
+                        p->state = s_reqline_port;
+                        break;
+                    case '/':
+                        p->path_offset       = &p->buf[p->buf_idx];
+
+                        p->buf[p->buf_idx++] = ch;
+                        p->buf[p->buf_idx]   = '\0';
+
+                        p->state = s_reqline_after_slash_in_uri;
+                        break;
+                    case ' ':
+                        /* p->buf should contain the whole uri */
+                        p->state = s_reqline_http_09;
+                        break;
+                    default:
+                        p->error = htparse_error_inval_schema;
+                        return i + 1;
+                } /* switch */
+
+                if (res) {
+                    p->error = htparse_error_user;
+                    return i + 1;
+                }
+
+                break;
+            case s_reqline_port:
+                res = 0;
+
+                if (ch >= '0' && ch <= '9') {
+                    p->buf[p->buf_idx++] = ch;
+                    p->buf[p->buf_idx]   = '\0';
+                    break;
+                }
+
+                res = hook_port_run(p, hooks, p->port_offset, p->buf_idx);
+
+                switch (ch) {
+                    case '/':
+                        p->buf[p->buf_idx++] = ch;
+                        p->buf[p->buf_idx]   = '\0';
+                        p->path_offset       = &p->buf[p->buf_idx - 1];
+
+                        p->state = s_reqline_after_slash_in_uri;
+                        break;
+                    case ' ':
+                        p->state   = s_reqline_http_09;
+                        p->buf_idx = 0;
+                        break;
+                    default:
+                        p->error = htparse_error_inval_reqline;
+                        return i + 1;
+                }
+
+                if (res) {
+                    p->error = htparse_error_user;
+                    return i + 1;
+                }
+
+                break;
+            case s_reqline_after_slash_in_uri:
+                htparse_log_debug("s_reqline_after_slash_in_uri");
+
+                res = 0;
+
+                if (usual[ch >> 5] & (1 << (ch & 0x1f))) {
+                    p->buf[p->buf_idx++] = ch;
+                    p->buf[p->buf_idx]   = '\0';
+                    p->state = s_reqline_check_uri;
+                    break;
+                }
+
+                switch (ch) {
+                    case ' ':
+                    {
+                        int r1 = hook_path_run(p, hooks, p->path_offset, p->buf_idx);
+                        int r2 = hook_uri_run(p, hooks, p->buf, p->buf_idx);
+
+                        p->state   = s_reqline_http_09;
+                        p->buf_idx = 0;
+
+                        if (r1 || r2) {
+                            res = 1;
+                        }
+                    }
+
+                    break;
+                    case CR:
+                        p->minor = 9;
+                        p->state = s_reqline_almost_done;
+                        break;
+                    case LF:
+                        p->minor = 9;
+                        p->state = s_hdrline_start;
+                        break;
+                    case '.':
+                    case '%':
+                    case '/':
+                    case '#':
+                        p->buf[p->buf_idx++] = ch;
+                        p->buf[p->buf_idx]   = '\0';
+                        p->state = s_reqline_uri;
+                        break;
+                    case '?':
+                        res = hook_path_run(p, hooks, p->buf, p->buf_idx);
+
+                        p->buf[p->buf_idx++] = ch;
+                        p->buf[p->buf_idx]   = '\0';
+                        p->args_offset       = &p->buf[p->buf_idx];
+                        p->state = s_reqline_uri;
+
+                        break;
+                    default:
+                        p->buf[p->buf_idx++] = ch;
+                        p->buf[p->buf_idx]   = '\0';
+
+                        p->state = s_reqline_check_uri;
+                        break;
+                } /* switch */
+
+                if (res) {
+                    p->error = htparse_error_user;
+                    return i + 1;
+                }
+
+                break;
+
+            case s_reqline_check_uri:
+                htparse_log_debug("s_reqline_check_uri");
+
+                res = 0;
+
+                if (usual[ch >> 5] & (1 << (ch & 0x1f))) {
+                    p->buf[p->buf_idx++] = ch;
+                    p->buf[p->buf_idx]   = '\0';
+                    break;
+                }
+
+                switch (ch) {
+                    case ' ':
+                    {
+                        int r1 = 0;
+                        int r2 = 0;
+
+                        if (p->args_offset) {
+                            r1 = hook_args_run(p, hooks, p->args_offset, p->buf_idx);
+                        } else {
+                            r1 = hook_path_run(p, hooks, p->buf, p->buf_idx);
+                        }
+
+                        r2         = hook_uri_run(p, hooks, p->buf, p->buf_idx);
+                        p->buf_idx = 0;
+                        p->state   = s_reqline_http_09;
+
+                        if (r1 || r2) {
+                            res = 1;
+                        }
+                    }
+                    break;
+                    case '/':
+                        p->buf[p->buf_idx++] = ch;
+                        p->buf[p->buf_idx]   = '\0';
+                        p->state = s_reqline_after_slash_in_uri;
+                        break;
+                    case CR:
+                        p->minor   = 9;
+                        p->buf_idx = 0;
+                        p->state   = s_reqline_almost_done;
+                        break;
+                    case LF:
+                        p->minor   = 9;
+                        p->buf_idx = 0;
+
+                        p->state   = s_hdrline_start;
+                        break;
+                    default:
+                        p->buf[p->buf_idx++] = ch;
+                        p->buf[p->buf_idx]   = '\0';
+
+                        if (ch == '?') {
+                            res = hook_path_run(p, hooks, p->buf, p->buf_idx - 1);
+                            p->args_offset = &p->buf[p->buf_idx];
+                        }
+
+                        p->state = s_reqline_uri;
+
+                        break;
+                } /* switch */
+
+                if (res) {
+                    p->error = htparse_error_user;
+                    return i + 1;
+                }
+
+                break;
+
+            case s_reqline_uri:
+                htparse_log_debug("s_reqline_uri");
+
+                res = 0;
+
+                if (usual[ch >> 5] & (1 << (ch & 0x1f))) {
+                    p->buf[p->buf_idx++] = ch;
+                    p->buf[p->buf_idx]   = '\0';
+                    break;
+                }
+
+                switch (ch) {
+                    case ' ':
+                    {
+                        int r1 = 0;
+                        int r2 = 0;
+
+                        if (p->args_offset) {
+                            r1 = hook_args_run(p, hooks, p->args_offset, p->buf_idx);
+                        }
+
+                        if (!r1) {
+                            r2 = hook_uri_run(p, hooks, p->buf, p->buf_idx);
+                        }
+
+                        p->buf_idx = 0;
+                        p->state   = s_reqline_http_09;
+
+                        if (r1 || r2) {
+                            res = 1;
+                        }
+                    }
+                    break;
+                    case CR:
+                        p->minor   = 9;
+                        p->buf_idx = 0;
+                        p->state   = s_reqline_almost_done;
+                        break;
+                    case LF:
+                        p->minor   = 9;
+                        p->buf_idx = 0;
+                        p->state   = s_hdrline_start;
+                        break;
+                    default:
+                        p->buf[p->buf_idx++] = ch;
+                        p->buf[p->buf_idx]   = '\0';
+                        break;
+                } /* switch */
+
+                if (res) {
+                    p->error = htparse_error_user;
+                    return i + 1;
+                }
+
+                break;
+
+            case s_reqline_http_09:
+                htparse_log_debug("s_reqline_http_09");
+
+                switch (ch) {
+                    case ' ':
+                        break;
+                    case CR:
+                        p->minor   = 9;
+                        p->buf_idx = 0;
+                        p->state   = s_reqline_almost_done;
+                        break;
+                    case LF:
+                        p->minor   = 9;
+                        p->buf_idx = 0;
+                        p->state   = s_hdrline_start;
+                        break;
+                    case 'H':
+                        p->buf_idx = 0;
+                        p->state   = s_reqline_http_H;
+                        break;
+                    default:
+                        p->error = htparse_error_inval_proto;
+                        return i + 1;
+                } /* switch */
+
+                break;
+            case s_reqline_http_H:
+                htparse_log_debug("s_reqline_http_09");
+
+                switch (ch) {
+                    case 'T':
+                        p->state = s_reqline_http_HT;
+                        break;
+                    default:
+                        p->error = htparse_error_inval_proto;
+                        return i + 1;
+                }
+                break;
+            case s_reqline_http_HT:
+                switch (ch) {
+                    case 'T':
+                        p->state = s_reqline_http_HTT;
+                        break;
+                    default:
+                        p->error = htparse_error_inval_proto;
+                        return i + 1;
+                }
+                break;
+            case s_reqline_http_HTT:
+                switch (ch) {
+                    case 'P':
+                        p->state = s_reqline_http_HTTP;
+                        break;
+                    default:
+                        p->error = htparse_error_inval_proto;
+                        return i + 1;
+                }
+                break;
+            case s_reqline_http_HTTP:
+                switch (ch) {
+                    case '/':
+                        p->state = s_reqline_first_major_digit;
+                        break;
+                    default:
+                        p->error = htparse_error_inval_proto;
+                        return i + 1;
+                }
+                break;
+            case s_reqline_first_major_digit:
+                if (ch < '1' || ch > '9') {
+                    p->error = htparse_error_inval_ver;
+                    return i + 1;
+                }
+
+                p->major = ch - '0';
+                p->state = s_reqline_major_digit;
+                break;
+            case s_reqline_major_digit:
+                if (ch == '.') {
+                    p->state = s_reqline_first_minor_digit;
+                    break;
+                }
+
+                if (ch < '0' || ch > '9') {
+                    p->error = htparse_error_inval_ver;
+                    return i + 1;
+                }
+
+                p->major = p->major * 10 + ch - '0';
+                break;
+            case s_reqline_first_minor_digit:
+                if (ch < '0' || ch > '9') {
+                    p->error = htparse_error_inval_ver;
+                    return i + 1;
+                }
+
+                p->minor = ch - '0';
+                p->state = s_reqline_minor_digit;
+                break;
+            case s_reqline_minor_digit:
+                switch (ch) {
+                    case ' ':
+                        p->state = s_reqline_spaces_after_digit;
+                        break;
+                    case CR:
+                        p->state = s_reqline_almost_done;
+                        break;
+                    case LF:
+                        p->state = s_hdrline_start;
+                        break;
+                    default:
+                        if (ch < '0' || ch > '9') {
+                            p->error = htparse_error_inval_ver;
+                            return i + 1;
+                        }
+
+                        p->minor = p->minor * 10 + ch - '0';
+                        break;
+                }
+                break;
+            case s_reqline_spaces_after_digit:
+                switch (ch) {
+                    case ' ':
+                        break;
+                    case CR:
+                        p->state = s_reqline_almost_done;
+                        break;
+                    case LF:
+                        p->state = s_hdrline_start;
+                        break;
+                    default:
+                        p->error = htparse_error_inval_ver;
+                        return i + 1;
+                }
+                break;
+
+            case s_reqline_almost_done:
+                switch (ch) {
+                    case LF:
+                        p->state = s_reqline_done;
+                        res      = hook_on_hdrs_begin_run(p, hooks);
+                        break;
+                    default:
+                        p->error = htparse_error_inval_reqline;
+                        return i + 1;
+                }
+                break;
+            case s_reqline_done:
+                switch (ch) {
+                    case CR:
+                        p->state = s_hdrline_almost_done;
+                        break;
+                    case LF:
+                        return i + 1;
+                    default:
+                        goto hdrline_start;
+                }
+                break;
+hdrline_start:
+            case s_hdrline_start:
+                htparse_log_debug("s_hdrline_start");
+
+                p->buf_idx = 0;
+
+                switch (ch) {
+                    case CR:
+                        p->state = s_hdrline_hdr_almost_done;
+                        break;
+                    case LF:
+                        p->state = s_hdrline_hdr_done;
+                        break;
+                    default:
+                        p->buf[p->buf_idx++] = ch;
+                        p->buf[p->buf_idx]   = '\0';
+
+                        p->state = s_hdrline_hdr_key;
+                        break;
+                }
+
+                break;
+            case s_hdrline_hdr_key:
+                htparse_log_debug("s_hdrline_hdr_key");
+
+                res = 0;
+                switch (ch) {
+                    case ':':
+                        res = hook_hdr_key_run(p, hooks, p->buf, p->buf_idx);
+
+                        /* figure out if the value of this header is valueable */
+                        p->heval = eval_hdr_val_none;
+
+                        switch (p->buf_idx + 1) {
+                            case 11:
+                                if (!strcasecmp(p->buf, "connection")) {
+                                    p->heval = eval_hdr_val_connection;
+                                }
+                                break;
+                            case 15:
+                                if (!strcasecmp(p->buf, "content-length")) {
+                                    p->heval = eval_hdr_val_content_length;
+                                }
+                                break;
+                            case 17:
+                                if (!strcasecmp(p->buf, "proxy-connection")) {
+                                    p->heval = eval_hdr_val_proxy_connection;
+                                }
+                                break;
+                            case 18:
+                                if (!strcasecmp(p->buf, "transfer-encoding")) {
+                                    p->heval = eval_hdr_val_transfer_encoding;
+                                }
+                                break;
+                        } /* switch */
+
+                        p->buf_idx = 0;
+                        p->state   = s_hdrline_hdr_space_before_val;
+
+                        break;
+                    case CR:
+                        p->state = s_hdrline_hdr_almost_done;
+                        break;
+                    case LF:
+                        p->state = s_hdrline_hdr_done;
+                        break;
+                    default:
+                        p->buf[p->buf_idx++] = ch;
+                        p->buf[p->buf_idx]   = '\0';
+                        break;
+                } /* switch */
+
+                if (res) {
+                    p->error = htparse_error_user;
+                    return i + 1;
+                }
+
+                break;
+            case s_hdrline_hdr_space_before_val:
+                htparse_log_debug("s_hdrline_hdr_space_before_val");
+
+                switch (ch) {
+                    case ' ':
+                        break;
+                    case CR:
+                    case LF:
+                        /* empty header value, is this legal? */
+                        p->error = htparse_error_inval_hdr;
+                        return i + 1;
+                    default:
+                        p->buf[p->buf_idx++] = ch;
+                        p->buf[p->buf_idx]   = '\0';
+                        p->state = s_hdrline_hdr_val;
+                        break;
+                }
+                break;
+            case s_hdrline_hdr_val:
+                htparse_log_debug("s_hdrline_hdr_val");
+                err = 0;
+                res = 0;
+
+                switch (ch) {
+                    case CR:
+                        res = hook_hdr_val_run(p, hooks, p->buf, p->buf_idx);
+
+                        switch (p->heval) {
+                            case eval_hdr_val_none:
+                                break;
+                            case eval_hdr_val_content_length:
+                                p->content_len = str_to_uint64(p->buf, p->buf_idx, &err);
+
+                                if (err == 1) {
+                                    p->error = htparse_error_too_big;
+                                    return i + 1;
+                                }
+
+                                break;
+                            case eval_hdr_val_connection:
+                                switch (p->buf[0]) {
+                                    case 'k':
+                                        if (_str9cmp((p->buf + 1),
+                                                     'e', 'e', 'p', '-', 'a', 'l', 'i', 'v', 'e')) {
+                                            p->flags |= parser_flag_connection_keep_alive;
+                                        }
+                                        break;
+                                    case 'c':
+                                        if (_str5cmp(p->buf,
+                                                     'c', 'l', 'o', 's', 'e')) {
+                                            p->flags |= parser_flag_connection_close;
+                                        }
+                                        break;
+                                }
+                                break;
+                            case eval_hdr_val_transfer_encoding:
+                                if (_str7_cmp(p->buf, 'c', 'h', 'u', 'n', 'k', 'e', 'd', '\0')) {
+                                    p->flags |= parser_flag_chunked;
+                                }
+
+                                break;
+                            default:
+                                break;
+                        } /* switch */
+
+                        p->state   = s_hdrline_hdr_almost_done;
+                        p->buf_idx = 0;
+
+                        break;
+                    case LF:
+                        p->state = s_hdrline_hdr_done;
+                        break;
+                    default:
+                        p->buf[p->buf_idx++] = ch;
+                        p->buf[p->buf_idx]   = '\0';
+                        break;
+                } /* switch */
+
+                if (res) {
+                    p->error = htparse_error_user;
+                    return i + 1;
+                }
+
+                break;
+            case s_hdrline_hdr_almost_done:
+                htparse_log_debug("s_hdrline_hdr_almost_done");
+
+                res = 0;
+                switch (ch) {
+                    case LF:
+                        if (p->flags & parser_flag_trailing) {
+                            res      = hook_on_msg_complete_run(p, hooks);
+                            p->state = s_reqline_start;
+                            break;
+                        }
+
+                        p->state = s_hdrline_hdr_done;
+                        break;
+                    default:
+                        p->error = htparse_error_inval_hdr;
+                        return i + 1;
+                }
+
+                if (res) {
+                    p->error = htparse_error_user;
+                    return i + 1;
+                }
+
+                break;
+            case s_hdrline_hdr_done:
+                htparse_log_debug("s_hdrline_hdr_done");
+
+                switch (ch) {
+                    case CR:
+                        p->state = s_hdrline_almost_done;
+                        break;
+                    case LF:
+                        /* got LFLF? is this valid? */
+                        return i + 1;
+                    default:
+                        p->buf_idx           = 0;
+                        p->buf[p->buf_idx++] = ch;
+                        p->buf[p->buf_idx]   = '\0';
+
+                        p->state = s_hdrline_hdr_key;
+                        break;
+                }
+                break;
+            case s_hdrline_almost_done:
+                htparse_log_debug("s_hdrline_almost_done");
+
+                res = 0;
+
+                switch (ch) {
+                    case LF:
+                        p->buf_idx = 0;
+
+                        res        = hook_on_hdrs_complete_run(p, hooks);
+
+                        if (!res) {
+                            if (p->flags & parser_flag_trailing) {
+                                res      = hook_on_msg_complete_run(p, hooks);
+                                p->state = s_reqline_start;
+                                break;
+                            }
+
+
+                            if (p->flags & parser_flag_chunked) {
+                                p->state = s_chunk_size_start;
+                                break;
+                            }
+
+                            if (p->content_len > 0) {
+                                res      = hook_on_new_chunk_run(p, hooks);
+                                p->state = s_body_read;
+                                break;
+                            }
+
+                            if (p->content_len <= 0) {
+                                res      = hook_on_msg_complete_run(p, hooks);
+                                p->state = s_reqline_start;
+                                break;
+                            }
+                        }
+
+                        p->state = s_hdrline_done;
+                        break;
+                    default:
+                        p->error = htparse_error_inval_hdr;
+                        return i + 1;
+                } /* switch */
+
+                if (res) {
+                    p->error = htparse_error_user;
+                    return i + 1;
+                }
+
+                break;
+
+            case s_chunk_size_start:
+                c = unhex[(unsigned char)ch];
+
+                if (c == -1) {
+                    p->error = htparse_error_inval_chunk_sz;
+                    return i + 1;
+                }
+
+                p->content_len = c;
+                p->state       = s_chunk_size;
+                break;
+            case s_chunk_size:
+                if (ch == CR) {
+                    p->state = s_chunk_size_almost_done;
+                    break;
+                }
+
+                c = unhex[(unsigned char)ch];
+
+                if (c == -1) {
+                    p->error = htparse_error_inval_chunk_sz;
+                    return i + 1;
+                }
+
+                p->content_len *= 16;
+                p->content_len += c;
+                break;
+
+            case s_chunk_size_almost_done:
+                res = 0;
+
+                if (ch != LF) {
+                    p->error = htparse_error_inval_chunk_sz;
+                    return i + 1;
+                }
+
+                if (p->content_len == 0) {
+                    p->flags |= parser_flag_trailing;
+                    p->state  = s_hdrline_start;
+                } else {
+                    res      = hook_on_new_chunk_run(p, hooks);
+
+                    p->state = s_chunk_data;
+                }
+
+                if (res) {
+                    p->error = htparse_error_user;
+                    return i + 1;
+                }
+
+                break;
+
+            case s_chunk_data:
+                res = 0;
+                {
+                    const char * pp      = &data[i];
+                    const char * pe      = (const char *)(data + len);
+                    size_t       to_read = _MIN_READ(pe - pp, p->content_len);
+
+                    if (to_read > 0) {
+                        res = hook_body_run(p, hooks, pp, to_read);
+
+                        i  += to_read - 1;
+                    }
+
+                    if (to_read == p->content_len) {
+                        p->state = s_chunk_data_almost_done;
+                    }
+
+                    p->content_len -= to_read;
+                }
+
+                if (res) {
+                    p->error = htparse_error_user;
+                    return i + 1;
+                }
+
+                break;
+
+            case s_chunk_data_almost_done:
+                if (ch != CR) {
+                    p->error = htparse_error_inval_chunk;
+                    return i + 1;
+                }
+
+                p->state = s_chunk_data_done;
+                break;
+
+            case s_chunk_data_done:
+                if (ch != LF) {
+                    p->error = htparse_error_inval_chunk;
+                    return i + 1;
+                }
+
+                p->state = s_chunk_size_start;
+                break;
+
+            case s_body_read:
+                res = 0;
+
+                {
+                    const char * pp      = &data[i];
+                    const char * pe      = (const char *)(data + len);
+                    size_t       to_read = _MIN_READ(pe - pp, p->content_len);
+
+                    htparse_log_debug("%zu", to_read);
+
+                    if (to_read > 0) {
+                        res = hook_body_run(p, hooks, pp, to_read);
+
+                        i  += to_read - 1;
+                        p->content_len -= to_read;
+
+                        if (p->content_len == 0) {
+                            res      = hook_on_msg_complete_run(p, hooks);
+
+                            p->state = s_reqline_start;
+                        }
+                    } else {
+                        res      = hook_on_msg_complete_run(p, hooks);
+                        p->state = s_reqline_start;
+                    }
+                }
+
+                if (res) {
+                    p->error = htparse_error_user;
+                    return i + 1;
+                }
+
+                break;
+
+            default:
+                printf("This is a silly state....\n");
+                p->error = htparse_error_inval_state;
+                return i + 1;
+        } /* switch */
+    }
+
+    return i;
+}         /* htparser_run */
+
diff --git a/libhtparse/htparse.h b/libhtparse/htparse.h
new file mode 100644
index 0000000..30cea67
--- /dev/null
+++ b/libhtparse/htparse.h
@@ -0,0 +1,99 @@
+#ifndef __HTPARSE_H__
+#define __HTPARSE_H__
+
+struct htparser;
+
+typedef struct htparser      htparser;
+typedef struct htparse_hooks htparse_hooks;
+
+typedef enum htp_scheme      htp_scheme;
+typedef enum htp_method      htp_method;
+typedef enum htpparse_error  htpparse_error;
+
+typedef int (*htparse_hook)(htparser *);
+typedef int (*htparse_data_hook)(htparser *, const char *, size_t);
+
+enum htp_scheme {
+    htp_scheme_none = 0,
+    htp_scheme_ftp,
+    htp_scheme_http,
+    htp_scheme_https,
+    htp_scheme_nfs,
+    htp_scheme_unknown
+};
+
+enum htp_method {
+    htp_method_GET = 0,
+    htp_method_HEAD,
+    htp_method_POST,
+    htp_method_PUT,
+    htp_method_DELETE,
+    htp_method_MKCOL,
+    htp_method_COPY,
+    htp_method_MOVE,
+    htp_method_OPTIONS,
+    htp_method_PROPFIND,
+    htp_method_PROPPATCH,
+    htp_method_LOCK,
+    htp_method_UNLOCK,
+    htp_method_TRACE,
+    htp_method_UNKNOWN
+};
+
+enum htpparse_error {
+    htparse_error_none = 0,
+    htparse_error_too_big,
+    htparse_error_inval_method,
+    htparse_error_inval_reqline,
+    htparse_error_inval_schema,
+    htparse_error_inval_proto,
+    htparse_error_inval_ver,
+    htparse_error_inval_hdr,
+    htparse_error_inval_chunk_sz,
+    htparse_error_inval_chunk,
+    htparse_error_inval_state,
+    htparse_error_user,
+    htparse_error_generic
+};
+
+
+struct htparse_hooks {
+    htparse_hook      on_msg_begin;
+    htparse_data_hook method;
+
+    htparse_data_hook scheme;       /* called if scheme is found */
+    htparse_data_hook host;         /* called if a host was in the request scheme */
+    htparse_data_hook port;         /* called if a port was in the request scheme */
+    htparse_data_hook path;         /* only the path of the uri */
+    htparse_data_hook args;         /* only the arguments of the uri */
+    htparse_data_hook uri;          /* the entire uri including path/args */
+
+    htparse_hook      on_hdrs_begin;
+    htparse_data_hook hdr_key;
+    htparse_data_hook hdr_val;
+    htparse_hook      on_hdrs_complete;
+
+    htparse_hook      on_new_chunk; /* called after parsed chunk octet */
+    htparse_data_hook body;
+
+    htparse_hook on_msg_complete;
+};
+
+
+size_t         htparser_run(htparser *, htparse_hooks *, const char *, size_t);
+int            htparser_should_keep_alive(htparser * p);
+htp_scheme     htparser_get_scheme(htparser *);
+htp_method     htparser_get_method(htparser *);
+const char   * htparser_get_methodstr(htparser *);
+unsigned char  htparser_get_major(htparser *);
+unsigned char  htparser_get_minor(htparser *);
+uint64_t       htparser_get_content_length(htparser *);
+htpparse_error htparser_get_error(htparser *);
+const char   * htparser_get_strerror(htparser *);
+void         * htparser_get_userdata(htparser *);
+void           htparser_set_userdata(htparser *, void *);
+void           htparser_init(htparser *);
+htparser     * htparser_new(void);
+
+#endif
+
diff --git a/libhtparse/test.c b/libhtparse/test.c
new file mode 100644
index 0000000..21de3d8
--- /dev/null
+++ b/libhtparse/test.c
@@ -0,0 +1,244 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <errno.h>
+
+#include "htparse.h"
+
+static int
+_on_msg_start(htparser * p) {
+    printf("START {\n");
+    return 0;
+}
+
+static int
+_on_msg_end(htparser * p) {
+    printf("}\n");
+    return 0;
+}
+
+static int
+_path(htparser * p, const char * data, size_t len) {
+    printf("\tpath = '%.*s'\n", (int)len, data);
+    return 0;
+}
+
+static int
+_method(htparser * p, const char * data, size_t len) {
+    printf("\tmethod = '%.*s'\n", (int)len, data);
+    return 0;
+}
+
+static int
+_uri(htparser * p, const char * data, size_t len) {
+    printf("\turi = '%.*s'\n", (int)len, data);
+    return 0;
+}
+
+static int
+_args(htparser * p, const char * data, size_t len) {
+    printf("\targs = '%.*s'\n", (int)len, data);
+    return 0;
+}
+
+static int
+_hdrs_end(htparser * p) {
+    printf("\t}\n");
+    return 0;
+}
+
+static int
+_hdrs_start(htparser * p) {
+    printf("\thdrs {\n");
+    return 0;
+}
+
+static int
+_hdr_key(htparser * p, const char * data, size_t len) {
+    printf("\t\thdr_key = '%.*s'\n", (int)len, data);
+    return 0;
+}
+
+static int
+_hdr_val(htparser * p, const char * data, size_t len) {
+    printf("\t\thdr_val = '%.*s'\n", (int)len, data);
+    return 0;
+}
+
+static int
+_read_body(htparser * p, const char * data, size_t len) {
+    printf("\t'%.*s'\n", (int)len, data);
+    return 0;
+}
+
+static int
+_on_new_chunk(htparser * p) {
+    printf("\t--payload--\n");
+    /* printf("..chunk..\n"); */
+    return 0;
+}
+
+static void
+_test(htparser * p, htparse_hooks * hooks, const char * l) {
+    printf("---- test ----\n");
+    printf("%zu, %s\n", strlen(l), l);
+
+    htparser_init(p);
+    printf("%zu == %zu\n", htparser_run(p, hooks, l, strlen(l)), strlen(l));
+
+    if (htparser_get_error(p)) {
+        printf("ERROR: %s\n", htparser_get_strerror(p));
+    }
+
+    printf("\n");
+}
+
+static void
+_test_fragments(htparser * p, htparse_hooks * hooks, const char ** fragments) {
+    int i = 0;
+
+    printf("---- test fragment ----\n");
+    htparser_init(p);
+
+    while (1) {
+        const char * l = fragments[i++];
+
+        if (l == NULL) {
+            break;
+        }
+
+        htparser_run(p, hooks, l, strlen(l));
+
+        if (htparser_get_error(p)) {
+            printf("ERROR: %s\n", htparser_get_strerror(p));
+        }
+    }
+
+    printf("\n");
+}
+
+static const char * test_fragment_1[] = {
+    "GET \0",
+    "  /fjdksf\0",
+    "jfkdslfds H\0",
+    "TTP/1.\0",
+    "1\r\0",
+    "\n\0",
+    "\r\0",
+    "\n\0",
+    NULL
+};
+
+static const char * test_fragment_2[] = {
+    "POST /\0",
+    "h?a=b HTTP/1.0\r\n\0",
+    "Content-Len\0",
+    "gth\0",
+    ": 1\0",
+    "0\r\n\0",
+    "\r\n\0",
+    "12345\0",
+    "67890\0",
+    NULL
+};
+
+static const char * test_chunk_fragment_1[] = {
+    "POST /stupid HTTP/1.1\r\n",
+    "Transfer-Encoding: chunked\r\n",
+    "\r\n",
+    "25\r\n",
+    "This is the data in the first chunk\r\n",
+    "\r\n",
+    "1C\r\n",
+    "and this is the second one\r\n",
+    "\r\n",
+    "3\r\n",
+    "con\r\n",
+    "8\r\n",
+    "sequence\r\n",
+    "0\r\n",
+    "\r\n",
+    NULL
+};
+
+static const char * test_chunk_fragment_2[] = {
+    "POST /stupid HTTP/1.1\r\n",
+    "Transfer-Encoding: chunked\r\n",
+    "\r\n",
+    "25\r\n",
+    "This is the data in the first chunk\r\n",
+    "\r\n",
+    "1C\r\n",
+    "and this is the second one\r\n",
+    "\r\n",
+    "3\r\n",
+    "c",
+    "on\r\n",
+    "8\r\n",
+    "sequence\r\n",
+    "0\r\n",
+    "\r\n",
+    "GET /foo?bar/baz? HTTP/1.0\r\n",
+    "Host: stupid.com\r\n",
+    "\r\n",
+    NULL
+};
+int
+main(int argc, char ** argv) {
+    htparser    * p     = htparser_new();
+    htparse_hooks hooks = {
+        .on_msg_begin     = _on_msg_start,
+        .method           = _method,
+        .scheme           = NULL,
+        .host             = NULL,
+        .port             = NULL,
+        .path             = _path,
+        .args             = _args,
+        .uri              = _uri,
+        .on_hdrs_begin    = _hdrs_start,
+        .hdr_key          = _hdr_key,
+        .hdr_val          = _hdr_val,
+        .on_hdrs_complete = _hdrs_end,
+        .on_new_chunk     = _on_new_chunk,
+        .body             = _read_body,
+        .on_msg_complete  = _on_msg_end
+    };
+
+    const char  * test_1 = "GET / HTTP/1.0\r\n\r\n";
+    const char  * test_2 = "GET /hi?a=b&c=d HTTP/1.1\r\n\r\n";
+    const char  * test_3 = "GET /hi/die/?a=b&c=d HTTP/1.1\r\n\r\n";
+    const char  * test_4 = "POST /fjdls HTTP/1.0\r\n"
+                           "Content-Length: 4\r\n"
+                           "\r\n"
+                           "abcd";
+    const char * test_7 = "POST /derp 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\0";
+    const char * test_8 = "GET /DIE HTTP/1.1\r\n"
+                          "HERP: DE\r\n"
+                          "\tRP\r\nthings:stuff\r\n\r\n";
+    const char * test_9 = "GET /big_content_len HTTP/1.1\r\n"
+                          "Content-Length: 18446744073709551615\r\n\r\n";
+
+    const char * test_fail = "GET /JF HfD]\r\n\r\n";
+
+    _test(p, &hooks, test_1);
+    _test(p, &hooks, test_2);
+    _test(p, &hooks, test_3);
+    _test(p, &hooks, test_4);
+    _test(p, &hooks, test_7);
+    _test(p, &hooks, test_8);
+    _test(p, &hooks, test_9);
+    _test(p, &hooks, test_fail);
+
+    _test_fragments(p, &hooks, test_fragment_1);
+    _test_fragments(p, &hooks, test_fragment_2);
+    _test_fragments(p, &hooks, test_chunk_fragment_1);
+    _test_fragments(p, &hooks, test_chunk_fragment_2);
+
+    return 0;
+} /* main */
+
diff --git a/test.c b/test.c
index 44e8247..b785d39 100644
--- a/test.c
+++ b/test.c
@@ -14,13 +14,14 @@
 };
 
 #ifndef DISABLE_EVTHR
-int      use_threads = 0;
-int      num_threads = 0;
+int       use_threads = 0;
+int       num_threads = 0;
 #endif
-char   * bind_addr   = "0.0.0.0";
-uint16_t bind_port   = 8081;
-char   * ssl_pem     = NULL;
-char   * ssl_ca      = NULL;
+char    * bind_addr   = "0.0.0.0";
+uint16_t  bind_port   = 8081;
+char    * ssl_pem     = NULL;
+char    * ssl_ca      = NULL;
+event_t * timer_ev    = NULL;
 
 struct _chunkarg {
     uint8_t           idx;
@@ -46,7 +47,7 @@
 }
 
 static void
-test_streaming(evhtp_request_t * req, void * arg) {
+test_streaming(evhtp_request_t * req, void * arg __unused__) {
     struct _chunkarg * carg = malloc(sizeof(struct _chunkarg));
 
     carg->idx = 0;
@@ -56,46 +57,92 @@
 }
 
 static void
-test_regex(evhtp_request_t * req, void * arg) {
+test_regex(evhtp_request_t * req, void * arg __unused__) {
     evhtp_send_reply(req, EVHTP_RES_OK, "REGEXOK", NULL);
 }
 
+int pause_count = 0;
+
 static void
-test_foo_cb(evhtp_request_t * req, void * arg) {
+test_pause_cb(evhtp_request_t * req, void * arg __unused__) {
+    struct evbuffer * b = evbuffer_new();
+
+    printf("test_pause_cb()\n");
+    evbuffer_add(b, "pause!\n", 7);
+    evhtp_send_reply(req, EVHTP_RES_OK, "HErP", b);
+    evbuffer_free(b);
+}
+
+static void
+test_resume_stuff(int sock __unused__, short which __unused__, void * arg) {
+    evhtp_request_t * req = arg;
+
+    printf("test_resume_stuff()\n");
+
+    evtimer_del(timer_ev);
+    evhtp_request_resume(req);
+}
+
+static evhtp_res
+test_pause_hdr_cb(evhtp_request_t * req, evhtp_hdr_t * hdr, void * arg __unused__) {
+    struct timeval tv;
+
+    if (timer_ev == NULL) {
+        timer_ev = evtimer_new(evhtp_request_get_evbase(req), test_resume_stuff, req);
+    }
+
+    printf("test_pause_hdr_cb() key = %s, val = %s\n",
+           evhtp_hdr_get_key(hdr), evhtp_hdr_get_val(hdr));
+
+
+    tv.tv_sec  = 1;
+    tv.tv_usec = 0;
+
+    if (pause_count++ <= 3) {
+        evtimer_add(timer_ev, &tv);
+        return EVHTP_RES_PAUSE;
+    }
+
+    return EVHTP_RES_OK;
+}
+
+static void
+test_foo_cb(evhtp_request_t * req, void * arg __unused__) {
     evhtp_send_reply(req, EVHTP_RES_OK, "OK", NULL);
 }
 
 static void
-test_500_cb(evhtp_request_t * req, void * arg) {
+test_500_cb(evhtp_request_t * req, void * arg __unused__) {
     evhtp_send_reply(req, EVHTP_RES_SERVERR, "no", NULL);
 }
 
 static void
-test_bar_cb(evhtp_request_t * req, void * arg) {
+test_bar_cb(evhtp_request_t * req, void * arg __unused__) {
     evhtp_send_reply(req, EVHTP_RES_OK, "OK", NULL);
 }
 
 static void
-test_default_cb(evhtp_request_t * req, void * arg) {
+test_default_cb(evhtp_request_t * req, void * arg __unused__) {
     struct evbuffer * b = evbuffer_new();
 
+    printf("test_default_cb\n");
     evbuffer_add_reference(b, "derp", 4, NULL, NULL);
     evhtp_send_reply(req, EVHTP_RES_OK, "Everything is fine", b);
     evbuffer_free(b);
 }
 
 static evhtp_res
-print_kv(evhtp_request_t * req, evhtp_hdr_t * hdr, void * arg) {
+print_kv(evhtp_request_t * req __unused__, evhtp_hdr_t * hdr __unused__, void * arg __unused__) {
     return EVHTP_RES_OK;
 }
 
 static evhtp_res
-print_kvs(evhtp_request_t * req, evhtp_hdrs_t * hdrs, void * arg) {
+print_kvs(evhtp_request_t * req __unused__, evhtp_hdrs_t * hdrs __unused__, void * arg __unused__) {
     return EVHTP_RES_OK;
 }
 
 static evhtp_res
-print_path(evhtp_request_t * req, const char * path, void * arg) {
+print_path(evhtp_request_t * req __unused__, const char * path __unused__, void * arg __unused__) {
 #if 0
     if (!strncmp(path, "/derp", 5)) {
         evhtp_set_close_on(req->conn, EVHTP_CLOSE_ON_200);
@@ -106,13 +153,14 @@
 }
 
 static evhtp_res
-print_uri(evhtp_request_t * req, const char * uri, void * arg) {
+print_uri(evhtp_request_t * req __unused__, const char * uri __unused__, void * arg __unused__) {
     return EVHTP_RES_OK;
 }
 
 static evhtp_res
-print_data(evhtp_request_t * req, const char * data, size_t len, void * arg) {
+print_data(evhtp_request_t * req, const char * data __unused__, size_t len, void * arg __unused__) {
     if (len) {
+        printf("%zu %.*s\n", len, len, data);
         evbuf_t * buf = evhtp_request_get_input(req);
         evbuffer_drain(buf, len);
     }
@@ -121,7 +169,7 @@
 }
 
 static evhtp_res
-inspect_expect(evhtp_request_t * req, const char * expct_str, void * arg) {
+inspect_expect(evhtp_request_t * req __unused__, const char * expct_str, void * arg __unused__) {
     if (strcmp(expct_str, "100-continue")) {
         printf("Inspecting expect failed!\n");
         return EVHTP_RES_EXPECTFAIL;
@@ -131,22 +179,41 @@
 }
 
 static evhtp_res
-set_my_handlers(evhtp_conn_t * conn, void * arg) {
+test_regex_hdrs_cb(evhtp_request_t * req __unused__, evhtp_hdrs_t * hdrs __unused__, void * arg __unused__) {
+    printf("Hi I'm here!\n");
+
+    return EVHTP_RES_OK;
+}
+
+static evhtp_res
+test_pre_accept(int fd __unused__, struct sockaddr * sin __unused__, int sl __unused__, void * arg) {
+    uint16_t port = *(uint16_t *)arg;
+
+    if (port > 8081) {
+        return EVHTP_RES_ERROR;
+    }
+
+    printf("%d\n", port);
+    return EVHTP_RES_OK;
+}
+
+static evhtp_res
+set_my_connection_handlers(evhtp_conn_t * conn, void * arg __unused__) {
     evhtp_cflags flags;
 
-    evhtp_set_hook(conn, EVHTP_HOOK_HDR_READ, print_kv, "foo");
-    evhtp_set_hook(conn, EVHTP_HOOK_HDRS_READ, print_kvs, "bar");
-    evhtp_set_hook(conn, EVHTP_HOOK_PATH_READ, print_path, "baz");
-    evhtp_set_hook(conn, EVHTP_HOOK_URI_READ, print_uri, "herp");
-    evhtp_set_hook(conn, EVHTP_HOOK_READ, print_data, "derp");
-    evhtp_set_hook(conn, EVHTP_HOOK_ON_EXPECT, inspect_expect, "bloop");
+    evhtp_set_connection_hook(conn, EVHTP_HOOK_HDR_READ, print_kv, "foo");
+    evhtp_set_connection_hook(conn, EVHTP_HOOK_HDRS_READ, print_kvs, "bar");
+    evhtp_set_connection_hook(conn, EVHTP_HOOK_PATH_READ, print_path, "baz");
+    evhtp_set_connection_hook(conn, EVHTP_HOOK_URI_READ, print_uri, "herp");
+    evhtp_set_connection_hook(conn, EVHTP_HOOK_READ, print_data, "derp");
+    evhtp_set_connection_hook(conn, EVHTP_HOOK_ON_EXPECT, inspect_expect, "bloop");
 
     flags =
-        EVHTP_CLOSE_ON_400 |
-        EVHTP_CLOSE_ON_500 |
-        EVHTP_CLOSE_ON_EXPECT_ERR;
+        EVHTP_FLAG_CLOSE_ON_400 |
+        EVHTP_FLAG_CLOSE_ON_500 |
+        EVHTP_FLAG_CLOSE_ON_EXPECT_ERR;
 
-    evhtp_conn_set_flags(conn, flags);
+    evhtp_set_connection_flags(conn, flags);
 
     return EVHTP_RES_OK;
 }
@@ -221,8 +288,15 @@
 
 int
 main(int argc, char ** argv) {
-    evbase_t * evbase = NULL;
-    evhtp_t  * htp    = NULL;
+    evbase_t         * evbase = NULL;
+    evhtp_t          * htp    = NULL;
+    evhtp_callback_t * cb_1   = NULL;
+    evhtp_callback_t * cb_2   = NULL;
+    evhtp_callback_t * cb_3   = NULL;
+    evhtp_callback_t * cb_4   = NULL;
+    evhtp_callback_t * cb_5   = NULL;
+    evhtp_callback_t * cb_6   = NULL;
+    evhtp_callback_t * cb_7   = NULL;
 
     if (parse_args(argc, argv) < 0) {
         exit(1);
@@ -238,15 +312,30 @@
     htp    = evhtp_new(evbase);
 
     evhtp_set_server_name(htp, "Hi there!");
-    evhtp_set_cb(htp, "/ref", test_default_cb, "fjdkls");
-    evhtp_set_cb(htp, "/foo", test_foo_cb, "bar");
-    evhtp_set_cb(htp, "/bar", test_bar_cb, "baz");
-    evhtp_set_cb(htp, "/500", test_500_cb, "500");
-    evhtp_set_cb(htp, "/stream", test_streaming, NULL);
-    evhtp_set_regex_cb(htp, "^/anything/.*", test_regex, NULL);
 
+    cb_1 = evhtp_set_cb(htp, "/ref", test_default_cb, "fjdkls");
+    cb_2 = evhtp_set_cb(htp, "/foo", test_foo_cb, "bar");
+    cb_3 = evhtp_set_cb(htp, "/bar", test_bar_cb, "baz");
+    cb_4 = evhtp_set_cb(htp, "/500", test_500_cb, "500");
+    cb_5 = evhtp_set_cb(htp, "/stream", test_streaming, NULL);
+    cb_6 = evhtp_set_regex_cb(htp, "^/anything/.*", test_regex, NULL);
+
+    /* setup a pausing test callback */
+    cb_7 = evhtp_set_cb(htp, "/pause", test_pause_cb, NULL);
+    evhtp_set_callback_hook(cb_7, EVHTP_HOOK_HDR_READ, test_pause_hdr_cb, NULL);
+
+
+    /* set a callback to set hooks specifically for the cb_6 callback */
+    evhtp_set_callback_hook(cb_6, EVHTP_HOOK_HDRS_READ, test_regex_hdrs_cb, NULL);
+
+    /* set a default request handler */
     evhtp_set_gencb(htp, test_default_cb, "foobarbaz");
-    evhtp_set_post_accept_cb(htp, set_my_handlers, NULL);
+
+    /* set a callback invoked before a connection is accepted */
+    evhtp_set_pre_accept_cb(htp, test_pre_accept, &bind_port);
+
+    /* set a callback to set per-connection hooks (via a post_accept cb) */
+    evhtp_set_post_accept_cb(htp, set_my_connection_handlers, NULL);
 
 #ifndef DISABLE_SSL
     if (ssl_pem != NULL) {