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) {