diff --git a/CMakeLists.txt b/CMakeLists.txt index d50b1c9..73627df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,3 +47,5 @@ endif () if (USED_LWIP OR VCPE_AGENT) ADD_SUBDIRECTORY(srcs/lwip) endif () + +ADD_SUBDIRECTORY(srcs/httpserver) diff --git a/config/agent.cfg b/config/vcpe.cfg similarity index 95% rename from config/agent.cfg rename to config/vcpe.cfg index 5848597..ca22cb0 100644 --- a/config/agent.cfg +++ b/config/vcpe.cfg @@ -65,5 +65,13 @@ application: ip = "192.168.100.1"; netmast = "255.255.0.0"; gw = "192.168.100.1"; + }; + + # http server config + http_svr: + { + listen_addr = "0.0.0.0"; + listen_port = 8000; + tcp_nodelay = true; } } \ No newline at end of file diff --git a/srcs/CMakeLists.txt b/srcs/CMakeLists.txt index 79d581c..7cfe382 100644 --- a/srcs/CMakeLists.txt +++ b/srcs/CMakeLists.txt @@ -19,11 +19,13 @@ LIST(APPEND COMMON_LIBS "-ldl -lpthread -lzlog -lm -luv -lzmq -luuid -lconfig") LIST(APPEND COMMON_LIBS "-lpthread") -INCLUDE_DIRECTORIES(include ./ ./include ./libs/include ./lwip/src/include ./lwip/src/arch_linux/include ${COMMON_INCLUDE}) +INCLUDE_DIRECTORIES(include ./ ./include ./libs/include ./lwip/src/include ./lwip/src/arch_linux/include + ./httpserver/include ${COMMON_INCLUDE}) SET(CMAKE_C_STANDARD 99) -FILE(GLOB VCPE_HEADS include/*.h include/uthash/*.h include/s2j/*.h) +FILE(GLOB VCPE_HEADS include/*.h include/uthash/*.h include/s2j/*.h + ./httpserver/include/*.h ./httpserver/src/haywire/*.h ./httpserver/src/haywire/configuration/*.h) if (USED_LWIP) AUX_SOURCE_DIRECTORY(pppoe VCPE_SRC) @@ -76,7 +78,7 @@ ADD_CUSTOM_COMMAND(TARGET ${PROJECT_TARGET} COMMAND ${CMAKE_COMMAND} -E copy_if_different "${PROJECT_SOURCE_DIR}/../config/opendhcp.ini" "${CMAKE_CURRENT_BINARY_DIR}/config/" COMMAND ${CMAKE_COMMAND} -E - copy_if_different "${PROJECT_SOURCE_DIR}/../config/agent.cfg" "${CMAKE_CURRENT_BINARY_DIR}/config/" + copy_if_different "${PROJECT_SOURCE_DIR}/../config/vcpe.cfg" "${CMAKE_CURRENT_BINARY_DIR}/config/" COMMAND ${CMAKE_COMMAND} -E copy_if_different "${PROJECT_SOURCE_DIR}/../config/zlog.conf" "${CMAKE_CURRENT_BINARY_DIR}/config/" COMMAND ${CMAKE_COMMAND} -E diff --git a/srcs/httpserver/CMakeLists.txt b/srcs/httpserver/CMakeLists.txt new file mode 100644 index 0000000..bad1824 --- /dev/null +++ b/srcs/httpserver/CMakeLists.txt @@ -0,0 +1,39 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 3.10 FATAL_ERROR) + +# ---------------------------------------- +# Haywire +# ---------------------------------------- +project(haywire C) +#set(CMAKE_BUILD_TYPE RelWithDebInfo) + +add_definitions(-std=gnu99) +#add_definitions(-mavx) +add_definitions(-msse4.1) +add_definitions(-pedantic) +add_definitions(-O3) +add_definitions(-Wall) +add_definitions(-Wextra) +add_definitions(-Wcast-align) +add_definitions(-w) + +if (UNIX) + add_definitions(-DUNIX) +endif (UNIX) + +INCLUDE_DIRECTORIES(./include ./src) + +file(GLOB_RECURSE HW_HEADS + ./src/haywire/*.h + ./src/haywire/configuration/*.h) + +AUX_SOURCE_DIRECTORY(src/haywire HW_SRC) +AUX_SOURCE_DIRECTORY(src/haywire/configuration HW_SRC) + +INCLUDE_DIRECTORIES(. ./include) + +#find_package(Threads REQUIRED) + +add_library(haywire STATIC ${HW_SRC} ${HW_HEADS}) +target_link_libraries (haywire -luv -pthread) + + diff --git a/srcs/httpserver/include/haywire.h b/srcs/httpserver/include/haywire.h new file mode 100644 index 0000000..371f782 --- /dev/null +++ b/srcs/httpserver/include/haywire.h @@ -0,0 +1,193 @@ +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +#ifdef _WIN32 + /* Windows - set up dll import/export decorators. */ + #ifdef BUILDING_HAYWIRE_SHARED + /* Building shared library. */ + #define HAYWIRE_EXTERN __declspec(dllexport) + #else + #ifdef USING_HAYWIRE_SHARED + /* Using shared library. */ + #define HAYWIRE_EXTERN __declspec(dllimport) + #else + /* Building static library. */ + #define HAYWIRE_EXTERN /* nothing */ + #endif + #endif + + #define HAYWIRE_CALLING_CONVENTION __cdecl +#else + /* Building static library. */ + #define HAYWIRE_EXTERN /* nothing */ + #define HAYWIRE_CALLING_CONVENTION /* nothing */ +#endif + +/* Informational 1xx */ +#define HTTP_STATUS_100 "100 Continue" +#define HTTP_STATUS_101 "101 Switching Protocols" +#define HTTP_STATUS_102 "102 Processing" +/* Successful 2xx */ +#define HTTP_STATUS_200 "200 OK" +#define HTTP_STATUS_201 "201 Created" +#define HTTP_STATUS_202 "202 Accepted" +#define HTTP_STATUS_203 "203 Non-Authoritative Information" +#define HTTP_STATUS_204 "204 No Content" +#define HTTP_STATUS_205 "205 Reset Content" +#define HTTP_STATUS_206 "206 Partial Content" +#define HTTP_STATUS_207 "207 Multi-Status" +/* Redirection 3xx */ +#define HTTP_STATUS_300 "300 Multiple Choices" +#define HTTP_STATUS_301 "301 Moved Permanently" +#define HTTP_STATUS_302 "302 Moved Temporarily" +#define HTTP_STATUS_303 "303 See Other" +#define HTTP_STATUS_304 "304 Not Modified" +#define HTTP_STATUS_305 "305 Use Proxy" +#define HTTP_STATUS_307 "307 Temporary Redirect" +/* Client Error 4xx */ +#define HTTP_STATUS_400 "400 Bad Request" +#define HTTP_STATUS_401 "401 Unauthorized" +#define HTTP_STATUS_402 "402 Payment Required" +#define HTTP_STATUS_403 "403 Forbidden" +#define HTTP_STATUS_404 "404 Not Found" +#define HTTP_STATUS_405 "405 Method Not Allowed" +#define HTTP_STATUS_406 "406 Not Acceptable" +#define HTTP_STATUS_407 "407 Proxy Authentication Required" +#define HTTP_STATUS_408 "408 Request Time-out" +#define HTTP_STATUS_409 "409 Conflict" +#define HTTP_STATUS_410 "410 Gone" +#define HTTP_STATUS_411 "411 Length Required" +#define HTTP_STATUS_412 "412 Precondition Failed" +#define HTTP_STATUS_413 "413 Request Entity Too Large" +#define HTTP_STATUS_414 "414 Request-URI Too Large" +#define HTTP_STATUS_415 "415 Unsupported Media Type" +#define HTTP_STATUS_416 "416 Requested Range Not Satisfiable" +#define HTTP_STATUS_417 "417 Expectation Failed" +#define HTTP_STATUS_418 "418 I'm a teapot" +#define HTTP_STATUS_422 "422 Unprocessable Entity" +#define HTTP_STATUS_423 "423 Locked" +#define HTTP_STATUS_424 "424 Failed Dependency" +#define HTTP_STATUS_425 "425 Unordered Collection" +#define HTTP_STATUS_426 "426 Upgrade Required" +#define HTTP_STATUS_428 "428 Precondition Required" +#define HTTP_STATUS_429 "429 Too Many Requests" +#define HTTP_STATUS_431 "431 Request Header Fields Too Large" +/* Server Error 5xx */ +#define HTTP_STATUS_500 "500 Internal Server Error" +#define HTTP_STATUS_501 "501 Not Implemented" +#define HTTP_STATUS_502 "502 Bad Gateway" +#define HTTP_STATUS_503 "503 Service Unavailable" +#define HTTP_STATUS_504 "504 Gateway Time-out" +#define HTTP_STATUS_505 "505 HTTP Version Not Supported" +#define HTTP_STATUS_506 "506 Variant Also Negotiates" +#define HTTP_STATUS_507 "507 Insufficient Storage" +#define HTTP_STATUS_509 "509 Bandwidth Limit Exceeded" +#define HTTP_STATUS_510 "510 Not Extended" +#define HTTP_STATUS_511 "511 Network Authentication Required" + +/* Request Methods */ +#define HW_HTTP_METHOD_MAP(XX) \ +XX(0, DELETE, DELETE) \ +XX(1, GET, GET) \ +XX(2, HEAD, HEAD) \ +XX(3, POST, POST) \ +XX(4, PUT, PUT) \ +/* pathological */ \ +XX(5, CONNECT, CONNECT) \ +XX(6, OPTIONS, OPTIONS) \ +XX(7, TRACE, TRACE) \ +/* webdav */ \ +XX(8, COPY, COPY) \ +XX(9, LOCK, LOCK) \ +XX(10, MKCOL, MKCOL) \ +XX(11, MOVE, MOVE) \ +XX(12, PROPFIND, PROPFIND) \ +XX(13, PROPPATCH, PROPPATCH) \ +XX(14, SEARCH, SEARCH) \ +XX(15, UNLOCK, UNLOCK) \ +/* subversion */ \ +XX(16, REPORT, REPORT) \ +XX(17, MKACTIVITY, MKACTIVITY) \ +XX(18, CHECKOUT, CHECKOUT) \ +XX(19, MERGE, MERGE) \ +/* upnp */ \ +XX(20, MSEARCH, M-SEARCH) \ +XX(21, NOTIFY, NOTIFY) \ +XX(22, SUBSCRIBE, SUBSCRIBE) \ +XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \ +/* RFC-5789 */ \ +XX(24, PATCH, PATCH) \ +XX(25, PURGE, PURGE) \ + +enum hw_http_method +{ +#define XX(num, name, string) HW_HTTP_##name = num, + HW_HTTP_METHOD_MAP(XX) +#undef XX +}; + +#define STRLENOF(s) strlen(s) +#define SETSTRING(s,val) s.value=val; s.length=STRLENOF(val) +#define APPENDSTRING(s,val) memcpy((char*)s->value + s->length, val, STRLENOF(val)); s->length+=STRLENOF(val) + +typedef void* hw_http_response; + +typedef struct +{ + char* value; + size_t length; +} hw_string; + +typedef struct +{ + char* http_listen_address; + unsigned int http_listen_port; + unsigned int thread_count; + char* balancer; + char* parser; + bool tcp_nodelay; + unsigned int listen_backlog; + unsigned int max_request_size; +} configuration; + +typedef struct +{ + unsigned short http_major; + unsigned short http_minor; + unsigned char method; + int keep_alive; + hw_string* url; + void* headers; + hw_string* body; + size_t body_length; + enum {OK, SIZE_EXCEEDED, BAD_REQUEST, INTERNAL_ERROR} state; +} http_request; + +typedef void (HAYWIRE_CALLING_CONVENTION *http_request_callback)(http_request* request, hw_http_response* response, void* user_data); +typedef void (HAYWIRE_CALLING_CONVENTION *http_response_complete_callback)(void* user_data); + +HAYWIRE_EXTERN int hw_init_from_config(char* configuration_filename); +HAYWIRE_EXTERN int hw_init_with_config(configuration* config); +HAYWIRE_EXTERN int hw_http_open(); +HAYWIRE_EXTERN void free_http_server(); +HAYWIRE_EXTERN void hw_http_add_route(char* route, http_request_callback callback, void* user_data); +HAYWIRE_EXTERN hw_string* hw_get_header(http_request* request, hw_string* key); + +HAYWIRE_EXTERN void hw_free_http_response(hw_http_response* response); +HAYWIRE_EXTERN void hw_set_http_version(hw_http_response* response, unsigned short major, unsigned short minor); +HAYWIRE_EXTERN void hw_set_response_status_code(hw_http_response* response, hw_string* status_code); +HAYWIRE_EXTERN void hw_set_response_header(hw_http_response* response, hw_string* name, hw_string* value); +HAYWIRE_EXTERN void hw_set_body(hw_http_response* response, hw_string* body); +HAYWIRE_EXTERN void hw_http_response_send(hw_http_response* response, void* user_data, http_response_complete_callback callback); + +HAYWIRE_EXTERN void hw_print_request_headers(http_request* request); + +#ifdef __cplusplus +} +#endif diff --git a/srcs/httpserver/src/haywire/configuration/configuration.c b/srcs/httpserver/src/haywire/configuration/configuration.c new file mode 100644 index 0000000..49bed76 --- /dev/null +++ b/srcs/httpserver/src/haywire/configuration/configuration.c @@ -0,0 +1,38 @@ +#include +#include "configuration.h" +#include "../hw_string.h" +#include "../khash.h" +#include "ini.h" + +KHASH_MAP_INIT_STR(route_hashes, char*) + +int configuration_handler(void* user, const char* section, const char* name, const char* value) +{ + configuration* config = (configuration*)user; + +#define MATCH(s, n) strcmp(section, s) == 0 && strcmp(name, n) == 0 + if (MATCH("http", "listen_address")) + { + config->http_listen_address = dupstr(value); + } + else if (MATCH("http", "listen_port")) + { + config->http_listen_port = atoi(value); + } + else + { + return 0; /* unknown section/name, error */ + } + return 1; +} + +configuration* load_configuration(const char* filename) +{ + configuration* config = malloc(sizeof(configuration)); + if (ini_parse(filename, configuration_handler, config) < 0) + { + dzlog_error("Can't load configuration\n"); + return NULL; + } + return config; +} diff --git a/srcs/httpserver/src/haywire/configuration/configuration.h b/srcs/httpserver/src/haywire/configuration/configuration.h new file mode 100644 index 0000000..8726f25 --- /dev/null +++ b/srcs/httpserver/src/haywire/configuration/configuration.h @@ -0,0 +1,4 @@ +#pragma once +#include "haywire.h" + +configuration* load_configuration(const char* filename); diff --git a/srcs/httpserver/src/haywire/configuration/ini.c b/srcs/httpserver/src/haywire/configuration/ini.c new file mode 100644 index 0000000..ee061fb --- /dev/null +++ b/srcs/httpserver/src/haywire/configuration/ini.c @@ -0,0 +1,150 @@ +/* inih -- simple .INI file parser + + inih is released under the New BSD license (see LICENSE.txt). Go to the project + home page for more info: + + http://code.google.com/p/inih/ + https://github.com/armon/hlld/tree/master/deps/inih + + */ + +#include +#include +#include + +#include "ini.h" + +#define MAX_LINE 200 +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace(*--p)) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && isspace(*s)) + s++; + return (char*)s; +} + +/* Return pointer to first char c or ';' comment in given string, or pointer to + null at end of string if neither found. ';' must be prefixed by a whitespace + character to register as a comment. */ +static char* find_char_or_comment(const char* s, char c) +{ + int was_whitespace = 0; + while (*s && *s != c && !(was_whitespace && *s == ';')) { + was_whitespace = isspace(*s); + s++; + } + return (char*)s; +} + +/* Version of strncpy that ensures dest (size bytes) is null-terminated. */ +static char* strncpy0(char* dest, const char* src, size_t size) +{ + strncpy(dest, src, size); + dest[size - 1] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, + int (*handler)(void*, const char*, const char*, + const char*), + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ + char line[MAX_LINE]; + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + + /* Scan through file line by line */ + while (fgets(line, sizeof(line), file) != NULL) { + lineno++; + start = lskip(rstrip(line)); + + if (*start == ';' || *start == '#') { + /* Per Python ConfigParser, allow '#' comments at start of line */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + /* Non-black line with leading whitespace, treat as continuation + of previous name's value (as per Python ConfigParser). */ + if (!handler(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_char_or_comment(start + 1, ']'); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start && *start != ';') { + /* Not a comment, must be a name[=:]value pair */ + end = find_char_or_comment(start, '='); + if (*end != '=') { + end = find_char_or_comment(start, ':'); + } + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = lskip(end + 1); + end = find_char_or_comment(value, '\0'); + if (*end == ';') + *end = '\0'; + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!handler(user, section, name, value) && !error) + error = lineno; + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ + error = lineno; + } + } + } + + return error; +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, + int (*handler)(void*, const char*, const char*, const char*), + void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} diff --git a/srcs/httpserver/src/haywire/configuration/ini.h b/srcs/httpserver/src/haywire/configuration/ini.h new file mode 100644 index 0000000..9b20211 --- /dev/null +++ b/srcs/httpserver/src/haywire/configuration/ini.h @@ -0,0 +1,56 @@ +/* inih -- simple .INI file parser + + inih is released under the New BSD license (see LICENSE.txt). Go to the project + home page for more info: + + http://code.google.com/p/inih/ + https://github.com/armon/hlld/tree/master/deps/inih + + */ + +#ifndef __INI_H__ +#define __INI_H__ + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + + /* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's ConfigParser. + + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), or -1 on file open error. + */ + int ini_parse(const char* filename, + int (*handler)(void* user, const char* section, + const char* name, const char* value), + void* user); + + /* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ + int ini_parse_file(FILE* file, + int (*handler)(void* user, const char* section, + const char* name, const char* value), + void* user); + + /* Nonzero to allow multi-line value parsing, in the style of Python's + ConfigParser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __INI_H__ */ diff --git a/srcs/httpserver/src/haywire/connection_consumer.c b/srcs/httpserver/src/haywire/connection_consumer.c new file mode 100644 index 0000000..40f64c1 --- /dev/null +++ b/srcs/httpserver/src/haywire/connection_consumer.c @@ -0,0 +1,128 @@ +#include +#include +#include "uv.h" +#include "connection_consumer.h" +#include "http_server.h" +#include "http_connection.h" +#include "http_response_cache.h" + +static bool tcp_nodelay; + +void ipc_read_cb(uv_stream_t* handle, ssize_t nread, const uv_buf_t* buf) +{ + int rc; + struct ipc_client_ctx* ctx; + uv_loop_t* loop; + uv_handle_type type; + uv_pipe_t* ipc_pipe; + + ipc_pipe = (uv_pipe_t*)handle; + ctx = container_of(ipc_pipe, struct ipc_client_ctx, ipc_pipe); + loop = ipc_pipe->loop; + + uv_pipe_pending_count(ipc_pipe); + type = uv_pipe_pending_type(ipc_pipe); + + if (type == UV_TCP) { + rc = uv_tcp_init(loop, (uv_tcp_t*) ctx->server_handle); + if (tcp_nodelay) { + rc = uv_tcp_nodelay((uv_tcp_t*) ctx->server_handle, 1); + } + } + else if (type == UV_NAMED_PIPE) + rc = uv_pipe_init(loop, (uv_pipe_t*) ctx->server_handle, 0); + + rc = uv_accept(handle, ctx->server_handle); + uv_close((uv_handle_t*) &ctx->ipc_pipe, NULL); +} + +void ipc_alloc_cb(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) +{ + struct ipc_client_ctx* ctx; + ctx = container_of(handle, struct ipc_client_ctx, ipc_pipe); + buf->base = ctx->scratch; + buf->len = sizeof(ctx->scratch); +} + +void ipc_connect_cb(uv_connect_t* req, int status) +{ + int rc; + struct ipc_client_ctx* ctx; + ctx = container_of(req, struct ipc_client_ctx, connect_req); + rc = uv_read_start((uv_stream_t*)&ctx->ipc_pipe, ipc_alloc_cb, ipc_read_cb); +} + +void connection_consumer_alloc(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buf) +{ + static char slab[32]; + buf->base = slab; + buf->len = sizeof(slab); +} + +void connection_consumer_new_connection(uv_stream_t* server_handle, int status) +{ + int rc = 0; + http_connection* connection = create_http_connection(); + http_parser_init(&connection->parser, HTTP_REQUEST); + + connection->parser.data = connection; + connection->stream.data = connection; + + rc = uv_tcp_init(server_handle->loop, &connection->stream); + + if (tcp_nodelay) { + rc = uv_tcp_nodelay((uv_tcp_t*)&connection->stream, 1); + } + + rc = uv_accept(server_handle, (uv_stream_t*)&connection->stream); + rc = uv_read_start((uv_stream_t*)&connection->stream, http_stream_on_alloc, http_stream_on_read); +} + +void connection_consumer_close(uv_async_t* handle, int status) +{ + struct server_ctx* ctx; + ctx = container_of(handle, struct server_ctx, async_handle); + uv_close((uv_handle_t*) &ctx->server_handle, NULL); + uv_close((uv_handle_t*) &ctx->async_handle, NULL); +} + +void get_listen_handle(uv_loop_t* loop, uv_stream_t* server_handle) +{ + int rc; + struct ipc_client_ctx ctx; + + ctx.server_handle = server_handle; + ctx.server_handle->data = "server handle"; + + rc = uv_pipe_init(loop, &ctx.ipc_pipe, 1); + uv_pipe_connect(&ctx.connect_req, &ctx.ipc_pipe, "HAYWIRE_CONNECTION_DISPATCH_PIPE_NAME", ipc_connect_cb); + rc = uv_run(loop, UV_RUN_DEFAULT); +} + +void connection_consumer_start(void *arg) +{ + int rc; + struct server_ctx *ctx; + uv_loop_t* loop; + + ctx = arg; + tcp_nodelay = ctx->tcp_nodelay; + loop = uv_loop_new(); + listener_event_loops[ctx->index] = *loop; + + http_request_cache_configure_listener(loop, &listener_async_handles[ctx->index]); + uv_barrier_wait(listeners_created_barrier); + + rc = uv_async_init(loop, &ctx->async_handle, connection_consumer_close); + uv_unref((uv_handle_t*) &ctx->async_handle); + + /* Wait until the main thread is ready. */ + uv_sem_wait(&ctx->semaphore); + get_listen_handle(loop, (uv_stream_t*) &ctx->server_handle); + uv_sem_post(&ctx->semaphore); + + rc = uv_listen((uv_stream_t*)&ctx->server_handle, ctx->listen_backlog, connection_consumer_new_connection); + rc = uv_run(loop, UV_RUN_DEFAULT); + + uv_loop_delete(loop); +} diff --git a/srcs/httpserver/src/haywire/connection_consumer.h b/srcs/httpserver/src/haywire/connection_consumer.h new file mode 100644 index 0000000..12a7507 --- /dev/null +++ b/srcs/httpserver/src/haywire/connection_consumer.h @@ -0,0 +1,50 @@ +#include + +#pragma once +#include "uv.h" + +#ifndef offsetof +#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER) +#endif + +#define container_of(ptr, type, member) ((type*)(((char*)(ptr)) - offsetof(type, member))) + + +union stream_handle2 +{ + uv_pipe_t pipe; + uv_tcp_t tcp; +}; + +typedef unsigned char handle_storage_t[sizeof(union stream_handle2)]; + +struct server_ctx +{ + int index; + handle_storage_t server_handle; + unsigned int num_connects; + uv_async_t async_handle; + uv_thread_t thread_id; + uv_sem_t semaphore; + bool tcp_nodelay; + unsigned int listen_backlog; +}; + +struct ipc_client_ctx +{ + uv_connect_t connect_req; + uv_stream_t* server_handle; + uv_pipe_t ipc_pipe; + char scratch[16]; +}; + +struct ipc_server_ctx +{ + handle_storage_t server_handle; + unsigned int num_connects; + uv_pipe_t ipc_pipe; + bool tcp_nodelay; +}; + +void connection_consumer_start(void *arg); +void connection_consumer_close(uv_async_t* handle, int status); diff --git a/srcs/httpserver/src/haywire/connection_dispatcher.c b/srcs/httpserver/src/haywire/connection_dispatcher.c new file mode 100644 index 0000000..79174c8 --- /dev/null +++ b/srcs/httpserver/src/haywire/connection_dispatcher.c @@ -0,0 +1,102 @@ +#include +#include +#include "uv.h" +#include "connection_dispatcher.h" +#include "connection_consumer.h" + +static struct sockaddr_in listen_addr; + +void ipc_close_cb(uv_handle_t* handle) +{ + struct ipc_peer_ctx* ctx; + ctx = container_of(handle, struct ipc_peer_ctx, peer_handle); + free(ctx); +} + +void ipc_write_cb(uv_write_t* req, int status) +{ + struct ipc_peer_ctx* ctx; + ctx = container_of(req, struct ipc_peer_ctx, write_req); + uv_close((uv_handle_t*) &ctx->peer_handle, ipc_close_cb); +} + +void ipc_connection_cb(uv_stream_t* ipc_pipe, int status) +{ + int rc; + struct ipc_server_ctx* sc; + struct ipc_peer_ctx* pc; + uv_loop_t* loop; + uv_buf_t buf; + + loop = ipc_pipe->loop; + buf = uv_buf_init("PING", 4); + sc = container_of(ipc_pipe, struct ipc_server_ctx, ipc_pipe); + pc = calloc(1, sizeof(*pc)); + //ASSERT(pc != NULL); + + if (ipc_pipe->type == UV_TCP) { + rc = uv_tcp_init(loop, (uv_tcp_t*) &pc->peer_handle); + if (sc->tcp_nodelay) { + rc = uv_tcp_nodelay((uv_tcp_t*) &pc->peer_handle, 1); + } + } + else if (ipc_pipe->type == UV_NAMED_PIPE) + rc = uv_pipe_init(loop, (uv_pipe_t*) &pc->peer_handle, 1); + + rc = uv_accept(ipc_pipe, (uv_stream_t*) &pc->peer_handle); + rc = uv_write2(&pc->write_req, + (uv_stream_t*) &pc->peer_handle, + &buf, + 1, + (uv_stream_t*) &sc->server_handle, + ipc_write_cb); + + if (--sc->num_connects == 0) + uv_close((uv_handle_t*) ipc_pipe, NULL); +} + +extern void print_configuration(); +/* Set up an IPC pipe server that hands out listen sockets to the worker + * threads. It's kind of cumbersome for such a simple operation, maybe we + * should revive uv_import() and uv_export(). + */ +void start_connection_dispatching(uv_handle_type type, unsigned int num_servers, struct server_ctx* servers, char* listen_address, int listen_port, bool tcp_nodelay, int listen_backlog) +{ + int rc; + struct ipc_server_ctx ctx; + uv_loop_t* loop; + unsigned int i; + + loop = uv_default_loop(); + ctx.num_connects = num_servers; + ctx.tcp_nodelay = tcp_nodelay; + + if (type == UV_TCP) + { + uv_ip4_addr(listen_address, listen_port, &listen_addr); + + rc = uv_tcp_init(loop, (uv_tcp_t*) &ctx.server_handle); + + if (ctx.tcp_nodelay) { + rc = uv_tcp_nodelay((uv_tcp_t*) &ctx.server_handle, 1); + } + + rc = uv_tcp_bind((uv_tcp_t*) &ctx.server_handle, (const struct sockaddr*)&listen_addr, 0); + print_configuration(); + printf("Listening...\n"); + } + + rc = uv_pipe_init(loop, &ctx.ipc_pipe, 1); + rc = uv_pipe_bind(&ctx.ipc_pipe, "HAYWIRE_CONNECTION_DISPATCH_PIPE_NAME"); + rc = uv_listen((uv_stream_t*) &ctx.ipc_pipe, listen_backlog, ipc_connection_cb); + + for (i = 0; i < num_servers; i++) + uv_sem_post(&servers[i].semaphore); + + rc = uv_run(loop, UV_RUN_DEFAULT); + uv_close((uv_handle_t*) &ctx.server_handle, NULL); + rc = uv_run(loop, UV_RUN_DEFAULT); + + for (i = 0; i < num_servers; i++) + uv_sem_wait(&servers[i].semaphore); +} diff --git a/srcs/httpserver/src/haywire/connection_dispatcher.h b/srcs/httpserver/src/haywire/connection_dispatcher.h new file mode 100644 index 0000000..a2b7f74 --- /dev/null +++ b/srcs/httpserver/src/haywire/connection_dispatcher.h @@ -0,0 +1,12 @@ +#pragma once +#include +#include "uv.h" +#include "connection_consumer.h" + +struct ipc_peer_ctx +{ + handle_storage_t peer_handle; + uv_write_t write_req; +}; + +void start_connection_dispatching(uv_handle_type type, unsigned int num_servers, struct server_ctx* servers, char* listen_address, int listen_port, bool tcp_nodelay, int listen_backlog); diff --git a/srcs/httpserver/src/haywire/http_connection.h b/srcs/httpserver/src/haywire/http_connection.h new file mode 100644 index 0000000..c8e2cb5 --- /dev/null +++ b/srcs/httpserver/src/haywire/http_connection.h @@ -0,0 +1,21 @@ +#include + +#pragma once +#include "uv.h" +#include "http_parser.h" +#include "http_request.h" +#include "http_request_buffers.h" + +typedef struct +{ + uv_tcp_t stream; + http_parser parser; + uv_write_t write_req; + http_request* request; + hw_string current_header_key; + hw_string current_header_value; + int keep_alive; + int last_was_value; + enum {OPEN, CLOSING, CLOSED} state; + hw_request_buffer* buffer; +} http_connection; diff --git a/srcs/httpserver/src/haywire/http_parser.c b/srcs/httpserver/src/haywire/http_parser.c new file mode 100644 index 0000000..c24a901 --- /dev/null +++ b/srcs/httpserver/src/haywire/http_parser.c @@ -0,0 +1,2410 @@ +/* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev + * + * Additional changes are licensed under the same terms as NGINX and + * 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 +#include +#include +#include +#include +#include + +#ifndef ULLONG_MAX +# define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ +#endif + +#ifndef MIN +# define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef ARRAY_SIZE +# define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) +#endif + +#ifndef BIT_AT +# define BIT_AT(a, i) \ +(!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ +(1 << ((unsigned int) (i) & 7)))) +#endif + +#ifndef ELEM_AT +# define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v)) +#endif + +#define SET_ERRNO(e) \ +do { \ +parser->http_errno = (e); \ +} while(0) + +#define CURRENT_STATE() p_state +#define UPDATE_STATE(V) p_state = (V); +#define RETURN(V) \ +do { \ +parser->state = CURRENT_STATE(); \ +return (V); \ +} while (0); +#define REEXECUTE() \ +--p; \ +break; + + +#ifdef __GNUC__ +# define LIKELY(X) __builtin_expect(!!(X), 1) +# define UNLIKELY(X) __builtin_expect(!!(X), 0) +#else +# define LIKELY(X) (X) +# define UNLIKELY(X) (X) +#endif + + +/* Run the notify callback FOR, returning ER if it fails */ +#define CALLBACK_NOTIFY_(FOR, ER) \ +do { \ +assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ +\ +if (LIKELY(settings->on_##FOR)) { \ +parser->state = CURRENT_STATE(); \ +if (UNLIKELY(0 != settings->on_##FOR(parser))) { \ +SET_ERRNO(HPE_CB_##FOR); \ +} \ +UPDATE_STATE(parser->state); \ +\ +/* We either errored above or got paused; get out */ \ +if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ +return (ER); \ +} \ +} \ +} while (0) + +/* Run the notify callback FOR and consume the current byte */ +#define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) + +/* Run the notify callback FOR and don't consume the current byte */ +#define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) + +/* Run data callback FOR with LEN bytes, returning ER if it fails */ +#define CALLBACK_DATA_(FOR, LEN, ER) \ +do { \ +assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ +\ +if (FOR##_mark) { \ +if (LIKELY(settings->on_##FOR)) { \ +parser->state = CURRENT_STATE(); \ +if (UNLIKELY(0 != \ +settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \ +SET_ERRNO(HPE_CB_##FOR); \ +} \ +UPDATE_STATE(parser->state); \ +\ +/* We either errored above or got paused; get out */ \ +if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ +return (ER); \ +} \ +} \ +FOR##_mark = NULL; \ +} \ +} while (0) + +/* Run the data callback FOR and consume the current byte */ +#define CALLBACK_DATA(FOR) \ +CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) + +/* Run the data callback FOR and don't consume the current byte */ +#define CALLBACK_DATA_NOADVANCE(FOR) \ +CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) + +/* Set the mark FOR; non-destructive if mark is already set */ +#define MARK(FOR) \ +do { \ +if (!FOR##_mark) { \ +FOR##_mark = p; \ +} \ +} while (0) + +/* Don't allow the total size of the HTTP headers (including the status + * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect + * embedders against denial-of-service attacks where the attacker feeds + * us a never-ending header that the embedder keeps buffering. + * + * This check is arguably the responsibility of embedders but we're doing + * it on the embedder's behalf because most won't bother and this way we + * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger + * than any reasonable request or response so this should never affect + * day-to-day operation. + */ +#define COUNT_HEADER_SIZE(V) \ +do { \ +parser->nread += (V); \ +if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) { \ +SET_ERRNO(HPE_HEADER_OVERFLOW); \ +goto error; \ +} \ +} 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[] = +{ +#define XX(num, name, string) #string, + HTTP_METHOD_MAP(XX) +#undef XX +}; + + +/* Tokens as defined by rfc 2616. Also lowercases them. + * token = 1* + * 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 ' */ + 0, '!', 0, '#', '$', '%', '&', '\'', + /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 0, 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, '~', 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 +}; + + +#if HTTP_PARSER_STRICT +# define T(v) 0 +#else +# define T(v) v +#endif + + +static const uint8_t normal_url_char[32] = { + /* 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 | T(2) | 0 | 0 | T(16) | 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 | 2 | 4 | 0 | 16 | 32 | 64 | 128, + /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, + /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, + /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ + 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; + +#undef T + +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_start + , 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_server_start + , s_req_server + , s_req_server_with_at + , 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_discard_ws + , s_header_value_discard_ws_almost_done + , s_header_value_discard_lws + , s_header_value_start + , s_header_value + , s_header_value_lws + + , s_header_almost_done + + , s_chunk_size_start + , s_chunk_size + , s_chunk_parameters + , s_chunk_size_almost_done + + , s_headers_almost_done + , s_headers_done + + /* Important: 's_headers_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 + + , s_message_done +}; + + +#define PARSING_HEADER(state) (state <= s_headers_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_token_start + , h_matching_connection_keep_alive + , h_matching_connection_close + , h_matching_connection_upgrade + , h_matching_connection_token + + , h_transfer_encoding_chunked + , h_connection_keep_alive + , h_connection_close + , h_connection_upgrade +}; + +enum http_host_state +{ + s_http_host_dead = 1 + , s_http_userinfo_start + , s_http_userinfo + , s_http_host_start + , s_http_host_v6_start + , s_http_host + , s_http_host_v6 + , s_http_host_v6_end + , s_http_host_port_start + , s_http_host_port +}; + +/* Macros for character classes; depends on strict-mode */ +#define CR '\r' +#define LF '\n' +#define LOWER(c) (unsigned char)(c | 0x20) +#define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') +#define IS_NUM(c) ((c) >= '0' && (c) <= '9') +#define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) +#define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) +#define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ +(c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ +(c) == ')') +#define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ +(c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ +(c) == '$' || (c) == ',') + +#define STRICT_TOKEN(c) (tokens[(unsigned char)c]) + +#if HTTP_PARSER_STRICT +#define TOKEN(c) (tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) +#define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') +#else +#define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) +#define IS_URL_CHAR(c) \ +(BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) +#define IS_HOST_CHAR(c) \ +(IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') +#endif + + +#define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) + + +#if HTTP_PARSER_STRICT +# define STRICT_CHECK(cond) \ +do { \ +if (cond) { \ +SET_ERRNO(HPE_STRICT); \ +goto error; \ +} \ +} while (0) +# define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) +#else +# define STRICT_CHECK(cond) +# define NEW_MESSAGE() start_state +#endif + + +/* Map errno values to strings for human-readable output */ +#define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, +static struct { + const char *name; + const char *description; +} http_strerror_tab[] = { + HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) +}; +#undef HTTP_STRERROR_GEN + +int http_message_needs_eof(const http_parser *parser); + +/* Our URL parser. + * + * This is designed to be shared by http_parser_execute() for URL validation, + * hence it has a state transition + byte-for-byte interface. In addition, it + * is meant to be embedded in http_parser_parse_url(), which does the dirty + * work of turning state transitions URL components for its API. + * + * This function should only be invoked with non-space characters. It is + * assumed that the caller cares about (and can detect) the transition between + * URL and non-URL states by looking for these. + */ +static enum state +parse_url_char(enum state s, const char ch) +{ + if (ch == ' ' || ch == '\r' || ch == '\n') { + return s_dead; + } + +#if HTTP_PARSER_STRICT + if (ch == '\t' || ch == '\f') { + return s_dead; + } +#endif + + switch (s) { + case s_req_spaces_before_url: + /* Proxied requests are followed by scheme of an absolute URI (alpha). + * All methods except CONNECT are followed by '/' or '*'. + */ + + if (ch == '/' || ch == '*') { + return s_req_path; + } + + if (IS_ALPHA(ch)) { + return s_req_schema; + } + + break; + + case s_req_schema: + if (IS_ALPHA(ch)) { + return s; + } + + if (ch == ':') { + return s_req_schema_slash; + } + + break; + + case s_req_schema_slash: + if (ch == '/') { + return s_req_schema_slash_slash; + } + + break; + + case s_req_schema_slash_slash: + if (ch == '/') { + return s_req_server_start; + } + + break; + + case s_req_server_with_at: + if (ch == '@') { + return s_dead; + } + + /* FALLTHROUGH */ + case s_req_server_start: + case s_req_server: + if (ch == '/') { + return s_req_path; + } + + if (ch == '?') { + return s_req_query_string_start; + } + + if (ch == '@') { + return s_req_server_with_at; + } + + if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { + return s_req_server; + } + + break; + + case s_req_path: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + return s_req_query_string_start; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_query_string_start: + case s_req_query_string: + if (IS_URL_CHAR(ch)) { + return s_req_query_string; + } + + switch (ch) { + case '?': + /* allow extra '?' in query string */ + return s_req_query_string; + + case '#': + return s_req_fragment_start; + } + + break; + + case s_req_fragment_start: + if (IS_URL_CHAR(ch)) { + return s_req_fragment; + } + + switch (ch) { + case '?': + return s_req_fragment; + + case '#': + return s; + } + + break; + + case s_req_fragment: + if (IS_URL_CHAR(ch)) { + return s; + } + + switch (ch) { + case '?': + case '#': + return s; + } + + break; + + default: + break; + } + + /* We should never fall out of the switch above unless there's an error */ + return s_dead; +} + +size_t http_parser_execute (http_parser *parser, + const http_parser_settings *settings, + const char *data, + size_t len) +{ + char c, ch; + int8_t unhex_val; + const char *p = data; + const char *header_field_mark = 0; + const char *header_value_mark = 0; + const char *url_mark = 0; + const char *body_mark = 0; + const char *status_mark = 0; + enum state p_state = parser->state; + + /* We're in an error state. Don't bother doing anything. */ + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + return 0; + } + + if (len == 0) { + switch (CURRENT_STATE()) { + case s_body_identity_eof: + /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if + * we got paused. + */ + CALLBACK_NOTIFY_NOADVANCE(message_complete); + return 0; + + case s_dead: + case s_start_req_or_res: + case s_start_res: + case s_start_req: + return 0; + + default: + SET_ERRNO(HPE_INVALID_EOF_STATE); + return 1; + } + } + + + if (CURRENT_STATE() == s_header_field) + header_field_mark = data; + if (CURRENT_STATE() == s_header_value) + header_value_mark = data; + switch (CURRENT_STATE()) { + case s_req_path: + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_server: + case s_req_server_with_at: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + url_mark = data; + break; + case s_res_status: + status_mark = data; + break; + default: + break; + } + + for (p=data; p != data + len; p++) { + ch = *p; + + if (PARSING_HEADER(CURRENT_STATE())) + COUNT_HEADER_SIZE(1); + + switch (CURRENT_STATE()) { + + case s_dead: + /* this state is used after a 'Connection: close' message + * the parser will error out if it reads another message + */ + if (LIKELY(ch == CR || ch == LF)) + break; + + SET_ERRNO(HPE_CLOSED_CONNECTION); + goto error; + + case s_start_req_or_res: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + if (ch == 'H') { + UPDATE_STATE(s_res_or_resp_H); + + CALLBACK_NOTIFY(message_begin); + } else { + parser->type = HTTP_REQUEST; + UPDATE_STATE(s_start_req); + REEXECUTE(); + } + + break; + } + + case s_res_or_resp_H: + if (ch == 'T') { + parser->type = HTTP_RESPONSE; + UPDATE_STATE(s_res_HT); + } else { + if (UNLIKELY(ch != 'E')) { + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + parser->type = HTTP_REQUEST; + parser->method = HTTP_HEAD; + parser->index = 2; + UPDATE_STATE(s_req_method); + } + break; + + case s_start_res: + { + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + switch (ch) { + case 'H': + UPDATE_STATE(s_res_H); + break; + + case CR: + case LF: + break; + + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + + CALLBACK_NOTIFY(message_begin); + break; + } + + case s_res_H: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_res_HT); + break; + + case s_res_HT: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_res_HTT); + break; + + case s_res_HTT: + STRICT_CHECK(ch != 'P'); + UPDATE_STATE(s_res_HTTP); + break; + + case s_res_HTTP: + STRICT_CHECK(ch != '/'); + UPDATE_STATE(s_res_first_http_major); + break; + + case s_res_first_http_major: + if (UNLIKELY(ch < '0' || ch > '9')) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + UPDATE_STATE(s_res_http_major); + break; + + /* major HTTP version or dot */ + case s_res_http_major: + { + if (ch == '.') { + UPDATE_STATE(s_res_first_http_minor); + break; + } + + if (!IS_NUM(ch)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (UNLIKELY(parser->http_major > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_res_first_http_minor: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + UPDATE_STATE(s_res_http_minor); + break; + + /* minor HTTP version or end of request line */ + case s_res_http_minor: + { + if (ch == ' ') { + UPDATE_STATE(s_res_first_status_code); + break; + } + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (UNLIKELY(parser->http_minor > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + case s_res_first_status_code: + { + if (!IS_NUM(ch)) { + if (ch == ' ') { + break; + } + + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + parser->status_code = ch - '0'; + UPDATE_STATE(s_res_status_code); + break; + } + + case s_res_status_code: + { + if (!IS_NUM(ch)) { + switch (ch) { + case ' ': + UPDATE_STATE(s_res_status_start); + break; + case CR: + UPDATE_STATE(s_res_line_almost_done); + break; + case LF: + UPDATE_STATE(s_header_field_start); + break; + default: + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + break; + } + + parser->status_code *= 10; + parser->status_code += ch - '0'; + + if (UNLIKELY(parser->status_code > 999)) { + SET_ERRNO(HPE_INVALID_STATUS); + goto error; + } + + break; + } + + case s_res_status_start: + { + if (ch == CR) { + UPDATE_STATE(s_res_line_almost_done); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_field_start); + break; + } + + MARK(status); + UPDATE_STATE(s_res_status); + parser->index = 0; + break; + } + + case s_res_status: + if (ch == CR) { + UPDATE_STATE(s_res_line_almost_done); + CALLBACK_DATA(status); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_field_start); + CALLBACK_DATA(status); + break; + } + + break; + + case s_res_line_almost_done: + STRICT_CHECK(ch != LF); + UPDATE_STATE(s_header_field_start); + break; + + case s_start_req: + { + if (ch == CR || ch == LF) + break; + parser->flags = 0; + parser->content_length = ULLONG_MAX; + + if (UNLIKELY(!IS_ALPHA(ch))) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + parser->method = (enum http_method) 0; + parser->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, MKCALENDAR */ break; + case 'N': parser->method = HTTP_NOTIFY; break; + case 'O': parser->method = HTTP_OPTIONS; break; + case 'P': parser->method = HTTP_POST; + /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ + break; + case 'R': parser->method = HTTP_REPORT; break; + case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break; + case 'T': parser->method = HTTP_TRACE; break; + case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break; + default: + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + UPDATE_STATE(s_req_method); + + CALLBACK_NOTIFY(message_begin); + + break; + } + + case s_req_method: + { + const char *matcher; + if (UNLIKELY(ch == '\0')) { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + matcher = method_strings[parser->method]; + if (ch == ' ' && matcher[parser->index] == '\0') { + UPDATE_STATE(s_req_spaces_before_url); + } else if (ch == matcher[parser->index]) { + ; /* nada */ + } else if (parser->method == HTTP_CONNECT) { + if (parser->index == 1 && ch == 'H') { + parser->method = HTTP_CHECKOUT; + } else if (parser->index == 2 && ch == 'P') { + parser->method = HTTP_COPY; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else if (parser->method == HTTP_MKCOL) { + if (parser->index == 1 && ch == 'O') { + parser->method = HTTP_MOVE; + } else if (parser->index == 1 && ch == 'E') { + parser->method = HTTP_MERGE; + } else if (parser->index == 1 && ch == '-') { + parser->method = HTTP_MSEARCH; + } else if (parser->index == 2 && ch == 'A') { + parser->method = HTTP_MKACTIVITY; + } else if (parser->index == 3 && ch == 'A') { + parser->method = HTTP_MKCALENDAR; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else if (parser->method == HTTP_SUBSCRIBE) { + if (parser->index == 1 && ch == 'E') { + parser->method = HTTP_SEARCH; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else if (parser->index == 1 && parser->method == HTTP_POST) { + if (ch == 'R') { + parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */ + } else if (ch == 'U') { + parser->method = HTTP_PUT; /* or HTTP_PURGE */ + } else if (ch == 'A') { + parser->method = HTTP_PATCH; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else if (parser->index == 2) { + if (parser->method == HTTP_PUT) { + if (ch == 'R') { + parser->method = HTTP_PURGE; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else if (parser->method == HTTP_UNLOCK) { + if (ch == 'S') { + parser->method = HTTP_UNSUBSCRIBE; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') { + parser->method = HTTP_PROPPATCH; + } else { + SET_ERRNO(HPE_INVALID_METHOD); + goto error; + } + + ++parser->index; + break; + } + + case s_req_spaces_before_url: + { + if (ch == ' ') break; + + MARK(url); + if (parser->method == HTTP_CONNECT) { + UPDATE_STATE(s_req_server_start); + } + + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + + break; + } + + case s_req_schema: + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + { + switch (ch) { + /* No whitespace allowed here */ + case ' ': + case CR: + case LF: + SET_ERRNO(HPE_INVALID_URL); + goto error; + default: + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + } + + break; + } + + case s_req_server: + case s_req_server_with_at: + case s_req_path: + case s_req_query_string_start: + case s_req_query_string: + case s_req_fragment_start: + case s_req_fragment: + { + switch (ch) { + case ' ': + UPDATE_STATE(s_req_http_start); + CALLBACK_DATA(url); + break; + case CR: + case LF: + parser->http_major = 0; + parser->http_minor = 9; + UPDATE_STATE((ch == CR) ? + s_req_line_almost_done : + s_header_field_start); + CALLBACK_DATA(url); + break; + default: + UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); + if (UNLIKELY(CURRENT_STATE() == s_dead)) { + SET_ERRNO(HPE_INVALID_URL); + goto error; + } + } + break; + } + + case s_req_http_start: + switch (ch) { + case 'H': + UPDATE_STATE(s_req_http_H); + break; + case ' ': + break; + default: + SET_ERRNO(HPE_INVALID_CONSTANT); + goto error; + } + break; + + case s_req_http_H: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_req_http_HT); + break; + + case s_req_http_HT: + STRICT_CHECK(ch != 'T'); + UPDATE_STATE(s_req_http_HTT); + break; + + case s_req_http_HTT: + STRICT_CHECK(ch != 'P'); + UPDATE_STATE(s_req_http_HTTP); + break; + + case s_req_http_HTTP: + STRICT_CHECK(ch != '/'); + UPDATE_STATE(s_req_first_http_major); + break; + + /* first digit of major HTTP version */ + case s_req_first_http_major: + if (UNLIKELY(ch < '1' || ch > '9')) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major = ch - '0'; + UPDATE_STATE(s_req_http_major); + break; + + /* major HTTP version or dot */ + case s_req_http_major: + { + if (ch == '.') { + UPDATE_STATE(s_req_first_http_minor); + break; + } + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_major *= 10; + parser->http_major += ch - '0'; + + if (UNLIKELY(parser->http_major > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* first digit of minor HTTP version */ + case s_req_first_http_minor: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor = ch - '0'; + UPDATE_STATE(s_req_http_minor); + break; + + /* minor HTTP version or end of request line */ + case s_req_http_minor: + { + if (ch == CR) { + UPDATE_STATE(s_req_line_almost_done); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_field_start); + break; + } + + /* XXX allow spaces after digit? */ + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + parser->http_minor *= 10; + parser->http_minor += ch - '0'; + + if (UNLIKELY(parser->http_minor > 999)) { + SET_ERRNO(HPE_INVALID_VERSION); + goto error; + } + + break; + } + + /* end of request line */ + case s_req_line_almost_done: + { + if (UNLIKELY(ch != LF)) { + SET_ERRNO(HPE_LF_EXPECTED); + goto error; + } + + UPDATE_STATE(s_header_field_start); + break; + } + + case s_header_field_start: + { + if (ch == CR) { + UPDATE_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*/ + UPDATE_STATE(s_headers_almost_done); + REEXECUTE(); + } + + c = TOKEN(ch); + + if (UNLIKELY(!c)) { + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + MARK(header_field); + + parser->index = 0; + UPDATE_STATE(s_header_field); + + switch (c) { + case 'c': + parser->header_state = h_C; + break; + + case 'p': + parser->header_state = h_matching_proxy_connection; + break; + + case 't': + parser->header_state = h_matching_transfer_encoding; + break; + + case 'u': + parser->header_state = h_matching_upgrade; + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_field: + { + const char* start = p; + for (; p != data + len; p++) { + ch = *p; + c = TOKEN(ch); + + if (!c) + break; + + switch (parser->header_state) { + case h_general: + break; + + case h_C: + parser->index++; + parser->header_state = (c == 'o' ? h_CO : h_general); + break; + + case h_CO: + parser->index++; + parser->header_state = (c == 'n' ? h_CON : h_general); + break; + + case h_CON: + parser->index++; + switch (c) { + case 'n': + parser->header_state = h_matching_connection; + break; + case 't': + parser->header_state = h_matching_content_length; + break; + default: + parser->header_state = h_general; + break; + } + break; + + /* connection */ + + case h_matching_connection: + parser->index++; + if (parser->index > sizeof(CONNECTION)-1 + || c != CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* proxy-connection */ + + case h_matching_proxy_connection: + parser->index++; + if (parser->index > sizeof(PROXY_CONNECTION)-1 + || c != PROXY_CONNECTION[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(PROXY_CONNECTION)-2) { + parser->header_state = h_connection; + } + break; + + /* content-length */ + + case h_matching_content_length: + parser->index++; + if (parser->index > sizeof(CONTENT_LENGTH)-1 + || c != CONTENT_LENGTH[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { + parser->header_state = h_content_length; + } + break; + + /* transfer-encoding */ + + case h_matching_transfer_encoding: + parser->index++; + if (parser->index > sizeof(TRANSFER_ENCODING)-1 + || c != TRANSFER_ENCODING[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { + parser->header_state = h_transfer_encoding; + } + break; + + /* upgrade */ + + case h_matching_upgrade: + parser->index++; + if (parser->index > sizeof(UPGRADE)-1 + || c != UPGRADE[parser->index]) { + parser->header_state = h_general; + } else if (parser->index == sizeof(UPGRADE)-2) { + parser->header_state = h_upgrade; + } + break; + + case h_connection: + case h_content_length: + case h_transfer_encoding: + case h_upgrade: + if (ch != ' ') parser->header_state = h_general; + break; + + default: + assert(0 && "Unknown header_state"); + break; + } + } + + COUNT_HEADER_SIZE(p - start); + + if (p == data + len) { + --p; + break; + } + + if (ch == ':') { + UPDATE_STATE(s_header_value_discard_ws); + CALLBACK_DATA(header_field); + break; + } + + SET_ERRNO(HPE_INVALID_HEADER_TOKEN); + goto error; + } + + case s_header_value_discard_ws: + if (ch == ' ' || ch == '\t') break; + + if (ch == CR) { + UPDATE_STATE(s_header_value_discard_ws_almost_done); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_value_discard_lws); + break; + } + + /* FALLTHROUGH */ + + case s_header_value_start: + { + MARK(header_value); + + UPDATE_STATE(s_header_value); + parser->index = 0; + + c = LOWER(ch); + + switch (parser->header_state) { + case h_upgrade: + parser->flags |= F_UPGRADE; + parser->header_state = h_general; + break; + + case h_transfer_encoding: + /* looking for 'Transfer-Encoding: chunked' */ + if ('c' == c) { + parser->header_state = h_matching_transfer_encoding_chunked; + } else { + parser->header_state = h_general; + } + break; + + case h_content_length: + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = ch - '0'; + break; + + case h_connection: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + parser->header_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + parser->header_state = h_matching_connection_close; + } else if (c == 'u') { + parser->header_state = h_matching_connection_upgrade; + } else { + parser->header_state = h_matching_connection_token; + } + break; + + /* Multi-value `Connection` header */ + case h_matching_connection_token_start: + break; + + default: + parser->header_state = h_general; + break; + } + break; + } + + case s_header_value: + { + const char* start = p; + enum header_states h_state = parser->header_state; + for (; p != data + len; p++) { + ch = *p; + if (ch == CR) { + UPDATE_STATE(s_header_almost_done); + parser->header_state = h_state; + CALLBACK_DATA(header_value); + break; + } + + if (ch == LF) { + UPDATE_STATE(s_header_almost_done); + COUNT_HEADER_SIZE(p - start); + parser->header_state = h_state; + CALLBACK_DATA_NOADVANCE(header_value); + REEXECUTE(); + } + + c = LOWER(ch); + + switch (h_state) { + case h_general: + { + const char* p_cr; + const char* p_lf; + size_t limit = data + len - p; + + limit = MIN(limit, HTTP_MAX_HEADER_SIZE); + + p_cr = memchr(p, CR, limit); + p_lf = memchr(p, LF, limit); + if (p_cr != NULL) { + if (p_lf != NULL && p_cr >= p_lf) + p = p_lf; + else + p = p_cr; + } else if (UNLIKELY(p_lf != NULL)) { + p = p_lf; + } else { + p = data + len; + } + --p; + + break; + } + + case h_connection: + case h_transfer_encoding: + assert(0 && "Shouldn't get here."); + break; + + case h_content_length: + { + uint64_t t; + + if (ch == ' ') break; + + if (UNLIKELY(!IS_NUM(ch))) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + parser->header_state = h_state; + goto error; + } + + t = parser->content_length; + t *= 10; + t += ch - '0'; + + /* Overflow? Test against a conservative limit for simplicity. */ + if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + parser->header_state = h_state; + goto error; + } + + parser->content_length = t; + break; + } + + /* Transfer-Encoding: chunked */ + case h_matching_transfer_encoding_chunked: + parser->index++; + if (parser->index > sizeof(CHUNKED)-1 + || c != CHUNKED[parser->index]) { + h_state = h_general; + } else if (parser->index == sizeof(CHUNKED)-2) { + h_state = h_transfer_encoding_chunked; + } + break; + + case h_matching_connection_token_start: + /* looking for 'Connection: keep-alive' */ + if (c == 'k') { + h_state = h_matching_connection_keep_alive; + /* looking for 'Connection: close' */ + } else if (c == 'c') { + h_state = h_matching_connection_close; + } else if (c == 'u') { + h_state = h_matching_connection_upgrade; + } else if (STRICT_TOKEN(c)) { + h_state = h_matching_connection_token; + } else { + h_state = h_general; + } + break; + + /* looking for 'Connection: keep-alive' */ + case h_matching_connection_keep_alive: + parser->index++; + if (parser->index > sizeof(KEEP_ALIVE)-1 + || c != KEEP_ALIVE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(KEEP_ALIVE)-2) { + h_state = h_connection_keep_alive; + } + break; + + /* looking for 'Connection: close' */ + case h_matching_connection_close: + parser->index++; + if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(CLOSE)-2) { + h_state = h_connection_close; + } + break; + + /* looking for 'Connection: upgrade' */ + case h_matching_connection_upgrade: + parser->index++; + if (parser->index > sizeof(UPGRADE) - 1 || + c != UPGRADE[parser->index]) { + h_state = h_matching_connection_token; + } else if (parser->index == sizeof(UPGRADE)-2) { + h_state = h_connection_upgrade; + } + break; + + case h_matching_connection_token: + if (ch == ',') { + h_state = h_matching_connection_token_start; + parser->index = 0; + } + break; + + case h_transfer_encoding_chunked: + if (ch != ' ') h_state = h_general; + break; + + case h_connection_keep_alive: + case h_connection_close: + case h_connection_upgrade: + if (ch == ',') { + if (h_state == h_connection_keep_alive) { + parser->flags |= F_CONNECTION_KEEP_ALIVE; + } else if (h_state == h_connection_close) { + parser->flags |= F_CONNECTION_CLOSE; + } else if (h_state == h_connection_upgrade) { + parser->flags |= F_CONNECTION_UPGRADE; + } + h_state = h_matching_connection_token_start; + parser->index = 0; + } else if (ch != ' ') { + h_state = h_matching_connection_token; + } + break; + + default: + UPDATE_STATE(s_header_value); + h_state = h_general; + break; + } + } + parser->header_state = h_state; + + COUNT_HEADER_SIZE(p - start); + + if (p == data + len) + --p; + break; + } + + case s_header_almost_done: + { + STRICT_CHECK(ch != LF); + + UPDATE_STATE(s_header_value_lws); + break; + } + + case s_header_value_lws: + { + if (ch == ' ' || ch == '\t') { + UPDATE_STATE(s_header_value_start); + REEXECUTE(); + } + + /* finished the header */ + switch (parser->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; + case h_connection_upgrade: + parser->flags |= F_CONNECTION_UPGRADE; + break; + default: + break; + } + + UPDATE_STATE(s_header_field_start); + REEXECUTE(); + } + + case s_header_value_discard_ws_almost_done: + { + STRICT_CHECK(ch != LF); + UPDATE_STATE(s_header_value_discard_lws); + break; + } + + case s_header_value_discard_lws: + { + if (ch == ' ' || ch == '\t') { + UPDATE_STATE(s_header_value_discard_ws); + break; + } else { + switch (parser->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_connection_upgrade: + parser->flags |= F_CONNECTION_UPGRADE; + break; + case h_transfer_encoding_chunked: + parser->flags |= F_CHUNKED; + break; + default: + break; + } + + /* header value was empty */ + MARK(header_value); + UPDATE_STATE(s_header_field_start); + CALLBACK_DATA_NOADVANCE(header_value); + REEXECUTE(); + } + } + + case s_headers_almost_done: + { + STRICT_CHECK(ch != LF); + + if (parser->flags & F_TRAILING) { + /* End of a chunked request */ + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + break; + } + + UPDATE_STATE(s_headers_done); + + /* Set this here so that on_headers_complete() callbacks can see it */ + parser->upgrade = + ((parser->flags & (F_UPGRADE | F_CONNECTION_UPGRADE)) == + (F_UPGRADE | F_CONNECTION_UPGRADE) || + parser->method == HTTP_CONNECT); + + /* 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. + * + * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so + * we have to simulate it by handling a change in errno below. + */ + if (settings->on_headers_complete) { + switch (settings->on_headers_complete(parser)) { + case 0: + break; + + case 1: + parser->flags |= F_SKIPBODY; + break; + + default: + SET_ERRNO(HPE_CB_headers_complete); + RETURN(p - data); /* Error */ + } + } + + if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { + RETURN(p - data); + } + + REEXECUTE(); + } + + case s_headers_done: + { + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + /* Exit, the rest of the connect is in a different protocol. */ + if (parser->upgrade) { + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + RETURN((p - data) + 1); + } + + if (parser->flags & F_SKIPBODY) { + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + } else if (parser->flags & F_CHUNKED) { + /* chunked encoding - ignore Content-Length header */ + UPDATE_STATE(s_chunk_size_start); + } else { + if (parser->content_length == 0) { + /* Content-Length header given but zero: Content-Length: 0\r\n */ + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + } else if (parser->content_length != ULLONG_MAX) { + /* Content-Length header given and non-zero */ + UPDATE_STATE(s_body_identity); + } else { + if (parser->type == HTTP_REQUEST || + !http_message_needs_eof(parser)) { + /* Assume content-length 0 - read the next */ + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + } else { + /* Read body until EOF */ + UPDATE_STATE(s_body_identity_eof); + } + } + } + + break; + } + + case s_body_identity: + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* The difference between advancing content_length and p is because + * the latter will automaticaly advance on the next loop iteration. + * Further, if content_length ends up at 0, we want to see the last + * byte again for our message complete callback. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + UPDATE_STATE(s_message_done); + + /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. + * + * The alternative to doing this is to wait for the next byte to + * trigger the data callback, just as in every other case. The + * problem with this is that this makes it difficult for the test + * harness to distinguish between complete-on-EOF and + * complete-on-length. It's not clear that this distinction is + * important for applications, but let's keep it for now. + */ + CALLBACK_DATA_(body, p - body_mark + 1, p - data); + REEXECUTE(); + } + + break; + } + + /* read until EOF */ + case s_body_identity_eof: + MARK(body); + p = data + len - 1; + + break; + + case s_message_done: + UPDATE_STATE(NEW_MESSAGE()); + CALLBACK_NOTIFY(message_complete); + break; + + case s_chunk_size_start: + { + assert(parser->nread == 1); + assert(parser->flags & F_CHUNKED); + + unhex_val = unhex[(unsigned char)ch]; + if (UNLIKELY(unhex_val == -1)) { + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + parser->content_length = unhex_val; + UPDATE_STATE(s_chunk_size); + break; + } + + case s_chunk_size: + { + uint64_t t; + + assert(parser->flags & F_CHUNKED); + + if (ch == CR) { + UPDATE_STATE(s_chunk_size_almost_done); + break; + } + + unhex_val = unhex[(unsigned char)ch]; + + if (unhex_val == -1) { + if (ch == ';' || ch == ' ') { + UPDATE_STATE(s_chunk_parameters); + break; + } + + SET_ERRNO(HPE_INVALID_CHUNK_SIZE); + goto error; + } + + t = parser->content_length; + t *= 16; + t += unhex_val; + + /* Overflow? Test against a conservative limit for simplicity. */ + if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) { + SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); + goto error; + } + + parser->content_length = t; + break; + } + + case s_chunk_parameters: + { + assert(parser->flags & F_CHUNKED); + /* just ignore this shit. TODO check for overflow */ + if (ch == CR) { + UPDATE_STATE(s_chunk_size_almost_done); + break; + } + break; + } + + case s_chunk_size_almost_done: + { + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + + parser->nread = 0; + + if (parser->content_length == 0) { + parser->flags |= F_TRAILING; + UPDATE_STATE(s_header_field_start); + } else { + UPDATE_STATE(s_chunk_data); + } + break; + } + + case s_chunk_data: + { + uint64_t to_read = MIN(parser->content_length, + (uint64_t) ((data + len) - p)); + + assert(parser->flags & F_CHUNKED); + assert(parser->content_length != 0 + && parser->content_length != ULLONG_MAX); + + /* See the explanation in s_body_identity for why the content + * length and data pointers are managed this way. + */ + MARK(body); + parser->content_length -= to_read; + p += to_read - 1; + + if (parser->content_length == 0) { + UPDATE_STATE(s_chunk_data_almost_done); + } + + break; + } + + case s_chunk_data_almost_done: + assert(parser->flags & F_CHUNKED); + assert(parser->content_length == 0); + STRICT_CHECK(ch != CR); + UPDATE_STATE(s_chunk_data_done); + CALLBACK_DATA(body); + break; + + case s_chunk_data_done: + assert(parser->flags & F_CHUNKED); + STRICT_CHECK(ch != LF); + parser->nread = 0; + UPDATE_STATE(s_chunk_size_start); + break; + + default: + assert(0 && "unhandled state"); + SET_ERRNO(HPE_INVALID_INTERNAL_STATE); + goto error; + } + } + + /* Run callbacks for any marks that we have leftover after we ran our of + * bytes. There should be at most one of these set, so it's OK to invoke + * them in series (unset marks will not result in callbacks). + * + * We use the NOADVANCE() variety of callbacks here because 'p' has already + * overflowed 'data' and this allows us to correct for the off-by-one that + * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' + * value that's in-bounds). + */ + + assert(((header_field_mark ? 1 : 0) + + (header_value_mark ? 1 : 0) + + (url_mark ? 1 : 0) + + (body_mark ? 1 : 0) + + (status_mark ? 1 : 0)) <= 1); + + CALLBACK_DATA_NOADVANCE(header_field); + CALLBACK_DATA_NOADVANCE(header_value); + CALLBACK_DATA_NOADVANCE(url); + CALLBACK_DATA_NOADVANCE(body); + CALLBACK_DATA_NOADVANCE(status); + + RETURN(len); + +error: + if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { + SET_ERRNO(HPE_UNKNOWN); + } + + RETURN(p - data); +} + + +/* Does the parser need to see an EOF to find the end of the message? */ +int +http_message_needs_eof (const http_parser *parser) +{ + if (parser->type == HTTP_REQUEST) { + return 0; + } + + /* See RFC 2616 section 4.4 */ + if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ + parser->status_code == 204 || /* No Content */ + parser->status_code == 304 || /* Not Modified */ + parser->flags & F_SKIPBODY) { /* response to a HEAD request */ + return 0; + } + + if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { + return 0; + } + + return 1; +} + + +int +http_should_keep_alive (const http_parser *parser) +{ + if (parser->http_major > 0 && parser->http_minor > 0) { + /* HTTP/1.1 */ + if (parser->flags & F_CONNECTION_CLOSE) { + return 0; + } + } else { + /* HTTP/1.0 or earlier */ + if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { + return 0; + } + } + + return !http_message_needs_eof(parser); +} + + +const char * +http_method_str (enum http_method m) +{ + return ELEM_AT(method_strings, m, ""); +} + + +void +http_parser_init (http_parser *parser, enum http_parser_type t) +{ + void *data = parser->data; /* preserve application data */ + memset(parser, 0, sizeof(*parser)); + parser->data = data; + parser->type = t; + parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); + parser->http_errno = HPE_OK; +} + +const char * +http_errno_name(enum http_errno err) { + assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); + return http_strerror_tab[err].name; +} + +const char * +http_errno_description(enum http_errno err) { + assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); + return http_strerror_tab[err].description; +} + +static enum http_host_state +http_parse_host_char(enum http_host_state s, const char ch) { + switch(s) { + case s_http_userinfo: + case s_http_userinfo_start: + if (ch == '@') { + return s_http_host_start; + } + + if (IS_USERINFO_CHAR(ch)) { + return s_http_userinfo; + } + break; + + case s_http_host_start: + if (ch == '[') { + return s_http_host_v6_start; + } + + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + break; + + case s_http_host: + if (IS_HOST_CHAR(ch)) { + return s_http_host; + } + + /* FALLTHROUGH */ + case s_http_host_v6_end: + if (ch == ':') { + return s_http_host_port_start; + } + + break; + + case s_http_host_v6: + if (ch == ']') { + return s_http_host_v6_end; + } + + /* FALLTHROUGH */ + case s_http_host_v6_start: + if (IS_HEX(ch) || ch == ':' || ch == '.') { + return s_http_host_v6; + } + + break; + + case s_http_host_port: + case s_http_host_port_start: + if (IS_NUM(ch)) { + return s_http_host_port; + } + + break; + + default: + break; + } + return s_http_host_dead; +} + +static int +http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { + enum http_host_state s; + + const char *p; + size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; + + u->field_data[UF_HOST].len = 0; + + s = found_at ? s_http_userinfo_start : s_http_host_start; + + for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { + enum http_host_state new_s = http_parse_host_char(s, *p); + + if (new_s == s_http_host_dead) { + return 1; + } + + switch(new_s) { + case s_http_host: + if (s != s_http_host) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_v6: + if (s != s_http_host_v6) { + u->field_data[UF_HOST].off = p - buf; + } + u->field_data[UF_HOST].len++; + break; + + case s_http_host_port: + if (s != s_http_host_port) { + u->field_data[UF_PORT].off = p - buf; + u->field_data[UF_PORT].len = 0; + u->field_set |= (1 << UF_PORT); + } + u->field_data[UF_PORT].len++; + break; + + case s_http_userinfo: + if (s != s_http_userinfo) { + u->field_data[UF_USERINFO].off = p - buf ; + u->field_data[UF_USERINFO].len = 0; + u->field_set |= (1 << UF_USERINFO); + } + u->field_data[UF_USERINFO].len++; + break; + + default: + break; + } + s = new_s; + } + + /* Make sure we don't end somewhere unexpected */ + switch (s) { + case s_http_host_start: + case s_http_host_v6_start: + case s_http_host_v6: + case s_http_host_port_start: + case s_http_userinfo: + case s_http_userinfo_start: + return 1; + default: + break; + } + + return 0; +} + +int +http_parser_parse_url(const char *buf, size_t buflen, int is_connect, + struct http_parser_url *u) +{ + enum state s; + const char *p; + enum http_parser_url_fields uf, old_uf; + int found_at = 0; + + u->port = u->field_set = 0; + s = is_connect ? s_req_server_start : s_req_spaces_before_url; + old_uf = UF_MAX; + + for (p = buf; p < buf + buflen; p++) { + s = parse_url_char(s, *p); + + /* Figure out the next field that we're operating on */ + switch (s) { + case s_dead: + return 1; + + /* Skip delimeters */ + case s_req_schema_slash: + case s_req_schema_slash_slash: + case s_req_server_start: + case s_req_query_string_start: + case s_req_fragment_start: + continue; + + case s_req_schema: + uf = UF_SCHEMA; + break; + + case s_req_server_with_at: + found_at = 1; + + /* FALLTROUGH */ + case s_req_server: + uf = UF_HOST; + break; + + case s_req_path: + uf = UF_PATH; + break; + + case s_req_query_string: + uf = UF_QUERY; + break; + + case s_req_fragment: + uf = UF_FRAGMENT; + break; + + default: + assert(!"Unexpected state"); + return 1; + } + + /* Nothing's changed; soldier on */ + if (uf == old_uf) { + u->field_data[uf].len++; + continue; + } + + u->field_data[uf].off = p - buf; + u->field_data[uf].len = 1; + + u->field_set |= (1 << uf); + old_uf = uf; + } + + /* host must be present if there is a schema */ + /* parsing http:///toto will fail */ + if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) { + if (http_parse_host(buf, u, found_at) != 0) { + return 1; + } + } + + /* CONNECT requests can only contain "hostname:port" */ + if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { + return 1; + } + + if (u->field_set & (1 << UF_PORT)) { + /* Don't bother with endp; we've already validated the string */ + unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); + + /* Ports have a max value of 2^16 */ + if (v > 0xffff) { + return 1; + } + + u->port = (uint16_t) v; + } + + return 0; +} + +void +http_parser_pause(http_parser *parser, int paused) { + /* Users should only be pausing/unpausing a parser that is not in an error + * state. In non-debug builds, there's not much that we can do about this + * other than ignore it. + */ + if (HTTP_PARSER_ERRNO(parser) == HPE_OK || + HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { + SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); + } else { + assert(0 && "Attempting to pause parser in error state"); + } +} + +int +http_body_is_final(const struct http_parser *parser) { + return parser->state == s_message_done; +} + +unsigned long +http_parser_version(void) { + return HTTP_PARSER_VERSION_MAJOR * 0x10000 | + HTTP_PARSER_VERSION_MINOR * 0x00100 | + HTTP_PARSER_VERSION_PATCH * 0x00001; +} \ No newline at end of file diff --git a/srcs/httpserver/src/haywire/http_parser.h b/srcs/httpserver/src/haywire/http_parser.h new file mode 100644 index 0000000..d8289ff --- /dev/null +++ b/srcs/httpserver/src/haywire/http_parser.h @@ -0,0 +1,329 @@ +/* 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 + + /* Also update SONAME in the Makefile whenever you change these. */ +#define HTTP_PARSER_VERSION_MAJOR 2 +#define HTTP_PARSER_VERSION_MINOR 3 +#define HTTP_PARSER_VERSION_PATCH 0 + +#include +#if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600) +#include +#include + 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; +#else +#include +#endif + + /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run + * faster + */ +#ifndef HTTP_PARSER_STRICT +# define HTTP_PARSER_STRICT 1 +#endif + + /* Maximium header size allowed. If the macro is not defined + * before including this header then the default is used. To + * change the maximum header size, define the macro in the build + * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove + * the effective limit on the size of the header, define the macro + * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) + */ +#ifndef HTTP_MAX_HEADER_SIZE +# define HTTP_MAX_HEADER_SIZE (80*1024) +#endif + + 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 called arbitrarily + * many times for each string. E.G. you might get 10 callbacks for "on_url" + * each providing just a few characters more data. + */ + typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); + typedef int (*http_cb) (http_parser*); + + /* Request Methods */ +#define HTTP_METHOD_MAP(XX) \ +XX(0, DELETE, DELETE) \ +XX(1, GET, GET) \ +XX(2, HEAD, HEAD) \ +XX(3, POST, POST) \ +XX(4, PUT, PUT) \ +/* pathological */ \ +XX(5, CONNECT, CONNECT) \ +XX(6, OPTIONS, OPTIONS) \ +XX(7, TRACE, TRACE) \ +/* webdav */ \ +XX(8, COPY, COPY) \ +XX(9, LOCK, LOCK) \ +XX(10, MKCOL, MKCOL) \ +XX(11, MOVE, MOVE) \ +XX(12, PROPFIND, PROPFIND) \ +XX(13, PROPPATCH, PROPPATCH) \ +XX(14, SEARCH, SEARCH) \ +XX(15, UNLOCK, UNLOCK) \ +/* subversion */ \ +XX(16, REPORT, REPORT) \ +XX(17, MKACTIVITY, MKACTIVITY) \ +XX(18, CHECKOUT, CHECKOUT) \ +XX(19, MERGE, MERGE) \ +/* upnp */ \ +XX(20, MSEARCH, M-SEARCH) \ +XX(21, NOTIFY, NOTIFY) \ +XX(22, SUBSCRIBE, SUBSCRIBE) \ +XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \ +/* RFC-5789 */ \ +XX(24, PATCH, PATCH) \ +XX(25, PURGE, PURGE) \ +/* CalDAV */ \ +XX(26, MKCALENDAR, MKCALENDAR) \ + + enum http_method + { +#define XX(num, name, string) HTTP_##name = num, + HTTP_METHOD_MAP(XX) +#undef XX + }; + + + enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; + + + /* Flag values for http_parser.flags field */ + enum flags + { F_CHUNKED = 1 << 0 + , F_CONNECTION_KEEP_ALIVE = 1 << 1 + , F_CONNECTION_CLOSE = 1 << 2 + , F_CONNECTION_UPGRADE = 1 << 3 + , F_TRAILING = 1 << 4 + , F_UPGRADE = 1 << 5 + , F_SKIPBODY = 1 << 6 + }; + + + /* Map for errno-related constants + * + * The provided argument should be a macro that takes 2 arguments. + */ +#define HTTP_ERRNO_MAP(XX) \ +/* No error */ \ +XX(OK, "success") \ +\ +/* Callback-related errors */ \ +XX(CB_message_begin, "the on_message_begin callback failed") \ +XX(CB_url, "the on_url callback failed") \ +XX(CB_header_field, "the on_header_field callback failed") \ +XX(CB_header_value, "the on_header_value callback failed") \ +XX(CB_headers_complete, "the on_headers_complete callback failed") \ +XX(CB_body, "the on_body callback failed") \ +XX(CB_message_complete, "the on_message_complete callback failed") \ +XX(CB_status, "the on_status callback failed") \ +\ +/* Parsing-related errors */ \ +XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ +XX(HEADER_OVERFLOW, \ +"too many header bytes seen; overflow detected") \ +XX(CLOSED_CONNECTION, \ +"data received after completed connection: close message") \ +XX(INVALID_VERSION, "invalid HTTP version") \ +XX(INVALID_STATUS, "invalid HTTP status code") \ +XX(INVALID_METHOD, "invalid HTTP method") \ +XX(INVALID_URL, "invalid URL") \ +XX(INVALID_HOST, "invalid host") \ +XX(INVALID_PORT, "invalid port") \ +XX(INVALID_PATH, "invalid path") \ +XX(INVALID_QUERY_STRING, "invalid query string") \ +XX(INVALID_FRAGMENT, "invalid fragment") \ +XX(LF_EXPECTED, "LF character expected") \ +XX(INVALID_HEADER_TOKEN, "invalid character in header") \ +XX(INVALID_CONTENT_LENGTH, \ +"invalid character in content-length header") \ +XX(INVALID_CHUNK_SIZE, \ +"invalid character in chunk size header") \ +XX(INVALID_CONSTANT, "invalid constant string") \ +XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ +XX(STRICT, "strict mode assertion failed") \ +XX(PAUSED, "parser is paused") \ +XX(UNKNOWN, "an unknown error occurred") + + + /* Define HPE_* values for each errno value above */ +#define HTTP_ERRNO_GEN(n, s) HPE_##n, + enum http_errno { + HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) + }; +#undef HTTP_ERRNO_GEN + + + /* Get an http_errno value from an http_parser */ +#define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) + + + struct http_parser { + /** PRIVATE **/ + unsigned int type : 2; /* enum http_parser_type */ + unsigned int flags : 6; /* F_* values from 'flags' enum; semi-public */ + unsigned int state : 8; /* enum state from http_parser.c */ + unsigned int header_state : 8; /* enum header_state from http_parser.c */ + unsigned int index : 8; /* index into current matcher */ + + uint32_t nread; /* # bytes read in various scenarios */ + uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ + + /** READ-ONLY **/ + unsigned short http_major; + unsigned short http_minor; + unsigned int status_code : 16; /* responses only */ + unsigned int method : 8; /* requests only */ + unsigned int http_errno : 7; + + /* 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. + */ + unsigned int upgrade : 1; + + /** 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_url; + http_data_cb on_status; + 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; + }; + + + enum http_parser_url_fields + { UF_SCHEMA = 0 + , UF_HOST = 1 + , UF_PORT = 2 + , UF_PATH = 3 + , UF_QUERY = 4 + , UF_FRAGMENT = 5 + , UF_USERINFO = 6 + , UF_MAX = 7 + }; + + + /* Result structure for http_parser_parse_url(). + * + * Callers should index into field_data[] with UF_* values iff field_set + * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and + * because we probably have padding left over), we convert any port to + * a uint16_t. + */ + struct http_parser_url { + uint16_t field_set; /* Bitmask of (1 << UF_*) values */ + uint16_t port; /* Converted UF_PORT string */ + + struct { + uint16_t off; /* Offset into buffer in which field starts */ + uint16_t len; /* Length of run in buffer */ + } field_data[UF_MAX]; + }; + + + /* Returns the library version. Bits 16-23 contain the major version number, + * bits 8-15 the minor version number and bits 0-7 the patch level. + * Usage example: + * + * unsigned long version = http_parser_version(); + * unsigned major = (version >> 16) & 255; + * unsigned minor = (version >> 8) & 255; + * unsigned patch = version & 255; + * printf("http_parser v%u.%u.%u\n", major, minor, patch); + */ + unsigned long http_parser_version(void); + + void http_parser_init(http_parser *parser, enum http_parser_type type); + + + /* Executes the parser. Returns number of parsed bytes. Sets + * `parser->http_errno` on error. */ + 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 0, then this 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(const http_parser *parser); + + /* Returns a string version of the HTTP method. */ + const char *http_method_str(enum http_method m); + + /* Return a string name of the given error */ + const char *http_errno_name(enum http_errno err); + + /* Return a string description of the given error */ + const char *http_errno_description(enum http_errno err); + + /* Parse a URL; return nonzero on failure */ + int http_parser_parse_url(const char *buf, size_t buflen, + int is_connect, + struct http_parser_url *u); + + /* Pause or un-pause the parser; a nonzero value pauses */ + void http_parser_pause(http_parser *parser, int paused); + + /* Checks if this is the final chunk of the body. */ + int http_body_is_final(const http_parser *parser); + +#ifdef __cplusplus +} +#endif +#endif \ No newline at end of file diff --git a/srcs/httpserver/src/haywire/http_request.c b/srcs/httpserver/src/haywire/http_request.c new file mode 100644 index 0000000..11986f6 --- /dev/null +++ b/srcs/httpserver/src/haywire/http_request.c @@ -0,0 +1,415 @@ +#include +#include +#include +#include +#include +#include "hw_string.h" +#include "khash.h" +#include "http_request.h" +#include "http_response.h" +#include "http_server.h" +#include "server_stats.h" +#include "route_compare_method.h" + +extern char* uv__strndup(const char* s, size_t n); + +#define CRLF "\r\n" +static const char response_404[] = + "HTTP/1.1 404 Not Found" CRLF + "Server: Haywire/master" CRLF + "Date: Fri, 26 Aug 2011 00:31:53 GMT" CRLF + "Connection: Keep-Alive" CRLF + "Content-Type: text/html" CRLF + "Content-Length: 16" CRLF + CRLF + "404 Not Found" CRLF + ; + +static kh_inline khint_t hw_string_hash_func(hw_string* s) +{ + khint_t h = s->length > 0 ? (khint_t)*s->value : 0; + if (h) for (int i = 0; i < s->length; i++) h = (h << 5) - h + (khint_t)*(s->value + i); + return h; +} + +#define hw_string_hash_equal(a, b) (hw_strcmp(a, b) == 0) + +KHASH_INIT(hw_string_hashmap, hw_string*, hw_string*, 1, hw_string_hash_func, hw_string_hash_equal) +KHASH_MAP_INIT_STR(string_hashmap, char*) +KHASH_MAP_INIT_INT64(offset_hashmap, int) + +void hw_print_request_headers(http_request* request) +{ + hw_string* k; + hw_string* v; + + khash_t(hw_string_hashmap) *h = request->headers; + kh_foreach(h, k, v, { + char* key = uv__strndup(k->value, k->length + 1); + char* value = uv__strndup(v->value, v->length + 1); + printf("KEY: %s VALUE: %s\n", key, value); + free(key); + free(value); + }); +} + +void hw_print_body(http_request* request) +{ + if (request->body->length > 0) { + char* body = uv__strndup(request->body->value, request->body->length); + printf("BODY: %s\n", body); + free(body); + } + else { + printf("BODY is empty!\n"); + } +} + +void* get_header(http_request* request, hw_string* name) +{ + khash_t(hw_string_hashmap) *h = request->headers; + khiter_t k = kh_get(hw_string_hashmap, h, name); + + void* val; + + int is_missing = (k == kh_end(h)); + if (is_missing) { + val = NULL; + } else { + val = kh_value(h, k); + } + return val; +} + +void set_header(http_request* request, hw_string* name, hw_string* value) +{ + int ret, i; + khiter_t k; + khash_t(hw_string_hashmap) *h = request->headers; + + for (i = 0; i < name->length; i++) + { + name->value[i] = tolower(name->value[i]); + } + + void* prev = get_header(request, name); + if (prev) { + free(prev); + } + + k = kh_put(hw_string_hashmap, h, name, &ret); + kh_value(h, k) = value; +} + +http_request* create_http_request(http_connection* connection) +{ + http_request* request = calloc(1, sizeof(http_request)); + request->headers = kh_init(hw_string_hashmap); + request->url = calloc(1, sizeof(hw_string)); + request->body = calloc(1, sizeof(hw_string)); + request->state = OK; + INCREMENT_STAT(stat_requests_created_total); + return request; +} + +void free_http_request(http_request* request) +{ + khash_t(hw_string_hashmap) *h = request->headers; + + hw_string* k; + hw_string* v; + + kh_foreach(h, k, v, + { + free((hw_string*)k); + free((hw_string*)v); + }); + + kh_destroy(hw_string_hashmap, h); + + free(request->url); + free(request->body); + + free(request); + + INCREMENT_STAT(stat_requests_destroyed_total); +} + +hw_string* hw_get_header(http_request* request, hw_string* key) +{ + void* value = get_header(request, key); + return value; +} + +int http_request_on_message_begin(http_parser* parser) +{ + http_connection* connection = (http_connection*)parser->data; + if (connection->request) { + /* We're seeing a new request on the same connection, so it's time to free up the old one + * and create a new one. + * + * Note: This assumes that the request callback is synchronous. If we're ever to support async callbacks, + * we either need to copy all required data and pass it into the async callback or free on write instead. */ + free_http_request(connection->request); + } + connection->request = create_http_request(connection); + return 0; +} + +int http_request_on_url(http_parser *parser, const char *at, size_t length) +{ + http_connection* connection = (http_connection*)parser->data; + http_request* request = connection->request; + hw_string* url = request->url; + + if (url->length) { + /* This is not the first URL chunk that has been seen in the buffer. Since we've already captured the initial pointer in the branch below, + so we can recover the URL later on, we only increment the URL length and ignore the pointer passed in as a parameter. */ + url->length += length; + } else { + url->value = at; + url->length = length; + /* We've seen the URL start, so we need to pin it to recover it later */ + http_request_buffer_pin(connection->buffer, url, url->value); + } + + return 0; +} + +void http_request_save_current_header(http_connection* connection) { + hw_string* header_key_copy = hw_strdup(&connection->current_header_key); + hw_string* header_value_copy = hw_strdup(&connection->current_header_value); + + /* We duplicate the current header key/value, so we actually need to use the new pointers as pin ids, as + * those will be the IDs we'll use later on to locate the headers */ + http_request_buffer_reassign_pin(connection->buffer, &connection->current_header_key, header_key_copy); + http_request_buffer_reassign_pin(connection->buffer, &connection->current_header_value, header_value_copy); + + /* Set headers is going to need to have the values of header key/value, so we need to make sure we get + * the pointers pointing at the right place in the buffer */ + header_key_copy->value = http_request_buffer_locate(connection->buffer, header_key_copy, + connection->current_header_key.value); + + header_value_copy->value = http_request_buffer_locate(connection->buffer, header_value_copy, + connection->current_header_value.value); + + /* Save last header key/value pair that was read */ + set_header(connection->request, header_key_copy, header_value_copy); +} + + +int http_request_on_header_field(http_parser *parser, const char *at, size_t length) +{ + http_connection* connection = (http_connection*)parser->data; + + if (connection->last_was_value && connection->current_header_key.length > 0) { + http_request_save_current_header(connection); + + connection->current_header_key.length = 0; + connection->current_header_value.length = 0; + } + + if (connection->current_header_key.length > 0) { + /* This is not the first header chunk that has been seen in the buffer. Since we've already captured the initial pointer in the branch below, + so we can recover the header later on, we only increment the header length and ignore the pointer passed in as a parameter. */ + connection->current_header_key.length += length; + } else { + /* Start of a new header key */ + connection->current_header_key.value = at; + connection->current_header_key.length = length; + /* Pin the header key */ + http_request_buffer_pin(connection->buffer, &connection->current_header_key, at); + } + + connection->last_was_value = 0; + return 0; +} + +int http_request_on_header_value(http_parser *parser, const char *at, size_t length) +{ + http_connection* connection = (http_connection*)parser->data; + + if (!connection->last_was_value) { + connection->current_header_value.value = at; + connection->current_header_value.length = length; + connection->last_was_value = 1; + /* Pin the header value */ + http_request_buffer_pin(connection->buffer, &connection->current_header_value, at); + } else { + /* Another header value chunk, not necessarily contiguous to the other chunks seen before, so we only increment + * the length. When the header value is located later on, it will be guaranteedly contiguous. */ + connection->current_header_value.length += length; + } + + return 0; +} + +int http_request_on_headers_complete(http_parser* parser) +{ + http_connection* connection = (http_connection*)parser->data; + + if (connection->current_header_key.length > 0 && connection->current_header_value.length > 0) { + http_request_save_current_header(connection); + } + connection->current_header_key.length = 0; + connection->current_header_value.length = 0; + + connection->request->http_major = parser->http_major; + connection->request->http_minor = parser->http_minor; + connection->request->method = parser->method; + connection->keep_alive = http_should_keep_alive(parser); + connection->request->keep_alive = connection->keep_alive; + return 0; +} + +int http_request_on_body(http_parser *parser, const char *at, size_t length) +{ + http_connection* connection = (http_connection*)parser->data; + http_request* request = connection->request; + hw_string* body = request->body; + if (length != 0) + { + if (body->length == 0) { + body->value = at; + body->length = length; + /* Let's pin the body so we can recover it later, even if the underlying buffers change */ + http_request_buffer_pin(connection->buffer, body, body->value); + } else { + body->length += length; + } + + request->body_length = body->length; + } + + return 0; +} + +hw_route_entry* get_route_callback(hw_string* url) +{ + hw_route_entry* route_entry = NULL; + + const char* k; + const char* v; + + khash_t(string_hashmap) *h = routes; + + kh_foreach(h, k, v, + { + int found = hw_route_compare_method(url, k); + if (found) + { + route_entry = (hw_route_entry*)v; + } + }); + + return route_entry; +} + +void send_error_response(http_request* request, http_response* response, const char* error_code, + const char* error_message) +{ + hw_string status_code; + hw_string content_type_name; + hw_string content_type_value; + hw_string body; + hw_string keep_alive_name; + hw_string keep_alive_value; + + status_code.value = error_code; + status_code.length = strlen(error_code); + hw_set_response_status_code(response, &status_code); + + SETSTRING(content_type_name, "Content-Type"); + + SETSTRING(content_type_value, "text/html"); + hw_set_response_header(response, &content_type_name, &content_type_value); + + body.value = error_message; + body.length = strlen(error_message); + hw_set_body(response, &body); + + if (request->keep_alive) + { + SETSTRING(keep_alive_name, "Connection"); + + SETSTRING(keep_alive_value, "Keep-Alive"); + hw_set_response_header(response, &keep_alive_name, &keep_alive_value); + } + else + { + hw_set_http_version(response, 1, 0); + } + + hw_http_response_send(response, NULL, 0); +} + +/** + * Ensures that the URL, body and header pointers are correct. + * This is because the underlying buffers might have been reallocated/resized, + * so we can't use the pointers of where data chunks (e.g. URL start) were originally seen. + */ +void http_request_locate_members(http_connection* connection) +{ + hw_request_buffer* buffer = connection->buffer; + http_request* request = connection->request; + request->url->value = http_request_buffer_locate(buffer, request->url, request->url->value); + request->body->value = http_request_buffer_locate(buffer, request->body, request->body->value); + + hw_string* header_name; + hw_string* header_value; + + khash_t(hw_string_hashmap) *h = request->headers; + kh_foreach(h, header_name, header_value, { + header_name->value = http_request_buffer_locate(buffer, header_name, header_name->value); + header_value->value = http_request_buffer_locate(buffer, header_value, header_value->value); + }); +} + +int http_request_on_message_complete(http_parser* parser) +{ + http_connection* connection = (http_connection*)parser->data; + http_request* request = connection->request; + hw_http_response* response = hw_create_http_response(connection); + + char* error = NULL; + + if (connection->request->state == SIZE_EXCEEDED) { + // 413 Request entity too large + error = HTTP_STATUS_413; + } else if (connection->request->state == BAD_REQUEST) { + // 400 Bad Request + error = HTTP_STATUS_400; + } else if (connection->request->state == INTERNAL_ERROR) { + // 500 Internal Server Error + } else { + http_request_locate_members(connection); + + hw_route_entry* route_entry = request != NULL ? get_route_callback(request->url) : NULL; + + if (route_entry != NULL) + { + route_entry->callback(request, response, route_entry->user_data); + } + else + { + // 404 Not Found. + error = HTTP_STATUS_404; + } + + /* Let's tell the buffer that we don't care about the data that was read before that is not currently + * being processed, so it can be swept later on. + * + * Since the HTTP parser doesn't tell us exactly where the request ends, we can be exact when creating a mark, + * otherwise we could discard everything up to, and including, the end of the current request. + * + * Instead, we create a mark that will be set at the end of the last chunk that was read before the one + * currently being processed and on which callback fired. */ + http_request_buffer_mark(connection->buffer); + } + + if (error) { + send_error_response(request, (http_response*)response, error, error); + } + + return 0; +} diff --git a/srcs/httpserver/src/haywire/http_request.h b/srcs/httpserver/src/haywire/http_request.h new file mode 100644 index 0000000..97acd6a --- /dev/null +++ b/srcs/httpserver/src/haywire/http_request.h @@ -0,0 +1,16 @@ +#include + +#pragma once +#include "uv.h" +#include "http_parser.h" + +extern int last_was_value; + +void free_http_request(http_request* request); +int http_request_on_message_begin(http_parser *parser); +int http_request_on_url(http_parser *parser, const char *at, size_t length); +int http_request_on_header_field(http_parser *parser, const char *at, size_t length); +int http_request_on_header_value(http_parser *parser, const char *at, size_t length); +int http_request_on_body(http_parser *parser, const char *at, size_t length); +int http_request_on_headers_complete(http_parser *parser); +int http_request_on_message_complete(http_parser *parser); \ No newline at end of file diff --git a/srcs/httpserver/src/haywire/http_request_buffers.c b/srcs/httpserver/src/haywire/http_request_buffers.c new file mode 100644 index 0000000..e89c846 --- /dev/null +++ b/srcs/httpserver/src/haywire/http_request_buffers.c @@ -0,0 +1,239 @@ +#include +#include +#include "haywire.h" +#include "http_request_buffers.h" + +#define DEFAULT_BUFFER_SHRINKSIZE 65536 + +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) + +KHASH_MAP_INIT_INT64(pointer_hashmap, void*) + +typedef struct { + size_t max_size; + size_t size; + size_t mark; + size_t used; + size_t used_before; + void* current; + khash_t(pointer_hashmap)* offsets; + bool offsets_active; +} http_request_buffer; + +void http_request_buffer_consume(hw_request_buffer* buf, size_t consumed) { + http_request_buffer* buffer = (http_request_buffer*) buf; + buffer->used_before = buffer->used; + buffer->used += consumed; +} + +void http_request_buffer_mark(hw_request_buffer* buf) { + http_request_buffer* buffer = (http_request_buffer*) buf; + /* unfortunately, the parser doesn't tell us where the request ends exactly, + * so the only thing we can be sure is that it ends in the current buffer chunk, so anything before it can + * effectively be swept, so we're placing the mark at that point now. */ + buffer->mark = buffer->used_before; +} + +void http_request_buffer_sweep(hw_request_buffer* buf) { + http_request_buffer* buffer = (http_request_buffer*) buf; + void* pointer; + int offset; + int used = buffer->used - buffer->mark; + + if (buffer->mark > 0) { + bool offsets_active = false; + + if (buffer->used > 0) { + /* Move data beyond the mark to the beginning of the buffer. + * While we should avoid memory copies, this is relatively infrequent and will only really copy a + * significant amount of data if requests are pipelined. Otherwise, we'll just be copying the last chunk + * that was read back to the beginning of the buffer. */ + memcpy(buffer->current, buffer->current + buffer->mark, used); + } + + if (buffer->size > DEFAULT_BUFFER_SHRINKSIZE && buffer->used < DEFAULT_BUFFER_SHRINKSIZE) { + /* Shrink buffer */ + buffer->size = DEFAULT_BUFFER_SHRINKSIZE; + buffer->current = realloc(buffer->current, buffer->size); + if (!buffer->current) { + errno = ENOMEM; + buffer->size = 0; + } + } + + /* Update offsets */ + if (buffer->used) { + kh_foreach(buffer->offsets, pointer, offset, { + khiter_t offset_key = kh_get(pointer_hashmap, buffer->offsets, pointer); + + if (offset <= buffer->mark) { + /* Delete offsets that pointed to bytes before the mark */ + kh_del(pointer_hashmap, buffer->offsets, offset_key); + } else { + /* There's at least one offset beyond the mark, so the offsets are active and should be considered + * when locating pointers. We need to shift the offset back by the width of the mark */ + offsets_active = true; + kh_value(buffer->offsets, offset_key) = offset - buffer->mark; + } + }); + } else { + /* the buffer is now empty, so we don't need to keep offsets */ + kh_clear(pointer_hashmap, buffer->offsets); + } + + buffer->mark = 0; + buffer->used = used; + buffer->offsets_active = offsets_active; + } +} + +hw_request_buffer* http_request_buffer_init(size_t max_size) { + http_request_buffer* buffer = malloc(sizeof(http_request_buffer)); + buffer->max_size = max_size; + buffer->size = 0; + buffer->used = 0; + buffer->mark = 0; + buffer->used_before = 0; + buffer->current = NULL; + buffer->offsets = kh_init(pointer_hashmap); + buffer->offsets_active = false; + return buffer; +} + +void http_request_buffer_chunk(hw_request_buffer* buf, hw_request_buffer_chunk* chunk) { + http_request_buffer *buffer = (http_request_buffer *) buf; + chunk->size = buffer->size ? buffer->size - buffer->used : 0; + chunk->buffer = buffer->current + buffer->used; +} + +bool http_request_buffer_alloc(hw_request_buffer* buf, size_t requested_size) { + http_request_buffer* buffer = (http_request_buffer*) buf; + bool ret = true; + void* previous = NULL; + + size_t requested_size_capped = MIN(buffer->max_size, requested_size); + + if (!buffer->current) { + buffer->current = malloc(requested_size_capped); + if (!buffer->current) { + buffer->size = 0; + errno = ENOMEM; + ret = false; + } else { + buffer->size = requested_size_capped; + } + } else if (buffer->used * 2 < buffer->size) { + /* ignoring allocation size unless we're above 50% usage */ + } else if (buffer->size + requested_size_capped <= buffer->max_size) { + /* time to reallocate memory and re-point anything using the buffer */ + previous = buffer->current; + + buffer->current = realloc(buffer->current, buffer->size + requested_size_capped); + buffer->size += requested_size_capped; + + if (!buffer->current) { + buffer->size = 0; + errno = ENOMEM; + ret = false; + } else if (buffer->current != previous) { + buffer->offsets_active = true; + } + } else { + /* maximum request size exceeded */ + errno = ERANGE; + buffer->size = 0; + ret = false; + } + + return ret; +} + +void http_request_buffer_print(hw_request_buffer* buf) { + http_request_buffer* buffer = (http_request_buffer*) buf; + + printf("Buffer: current=%u; size=%u; used=%u\n", buffer->current, buffer->size, buffer->used); + printf(" 0\t"); + for (int i = 0; i < buffer->used; i++) { + if (((char*) buffer->current)[i] == '\n') { + printf("\\n"); + } else if (((char*) buffer->current)[i] == '\r') { + printf("\\r"); + } else { + printf("%c", ((char*) buffer->current)[i]); + } + + if ((i + 1) % 10 == 0) { + printf("\n%5d\t", (i + 1) / 10); + } else { + printf("\t"); + } + } + printf("\n"); + + void* pointer; + int offset; + kh_foreach(buffer->offsets, pointer, offset, { + printf("\tPointer %u -> offset=%u\n", pointer, offset); + }); + printf("----\n"); +} + +void http_request_buffer_pin(hw_request_buffer* buf, void* key, void* pointer) { + http_request_buffer* buffer = (http_request_buffer*) buf; + + khiter_t offset_key = kh_get(pointer_hashmap, buffer->offsets, key); + + int offset = pointer - buffer->current; + int ret; + + int is_missing = (offset_key == kh_end(buffer->offsets)); + if (is_missing) { + offset_key = kh_put(pointer_hashmap, buffer->offsets, key, &ret); + } + + kh_value(buffer->offsets, offset_key) = offset; +} + +void http_request_buffer_reassign_pin(hw_request_buffer* buf, void* old_key, void* new_key) { + http_request_buffer* buffer = (http_request_buffer*) buf; + + khiter_t old_offset_key = kh_get(pointer_hashmap, buffer->offsets, old_key); + + int offset; + int ret; + + int is_missing = (old_offset_key == kh_end(buffer->offsets)); + if (!is_missing) { + offset = kh_val(buffer->offsets, old_offset_key); + + khiter_t new_offset_key = kh_put(pointer_hashmap, buffer->offsets, new_key, &ret); + kh_value(buffer->offsets, new_offset_key) = offset; + old_offset_key = kh_get(pointer_hashmap, buffer->offsets, old_key); + kh_del(pointer_hashmap, buffer->offsets, old_offset_key); + } +} + +void* http_request_buffer_locate(hw_request_buffer* buf, void* key, void* default_pointer) { + http_request_buffer* buffer = (http_request_buffer*) buf; + void* location = default_pointer; + khiter_t offset_key = kh_get(pointer_hashmap, buffer->offsets, key); + + int offset, is_missing; + + if (buffer->offsets_active) { + is_missing = (offset_key == kh_end(buffer->offsets)); + if (!is_missing) { + offset = kh_value(buffer->offsets, offset_key); + location = buffer->current + offset; + } + } + + return location; +} + +void http_request_buffer_destroy(hw_request_buffer* buf) { + http_request_buffer* buffer = (http_request_buffer*) buf; + kh_destroy(pointer_hashmap, buffer->offsets); + free(buffer->current); + free(buffer); +} diff --git a/srcs/httpserver/src/haywire/http_request_buffers.h b/srcs/httpserver/src/haywire/http_request_buffers.h new file mode 100644 index 0000000..c906c43 --- /dev/null +++ b/srcs/httpserver/src/haywire/http_request_buffers.h @@ -0,0 +1,81 @@ +#pragma once +#include +#include "khash.h" +#include "haywire.h" + +typedef struct { + void* buffer; + size_t size; +} hw_request_buffer_chunk; + +typedef void* hw_request_buffer; + +/** + * Initializes a new buffer. The caller is responsible for calling http_request_buffer_destroy to free up memory. + */ +hw_request_buffer* http_request_buffer_init(size_t max_size); + +/** + * Signals that "size" bytes from the buffer are now in use. + */ +void http_request_buffer_consume(hw_request_buffer* buffer, size_t size); + +/** + * Marks all buffer chunks up to the last one allocated (exclusive) for removal when + * http_request_buffer_sweep gets called. + */ +void http_request_buffer_mark(hw_request_buffer* buffer); + +/** + * Sweeps all buffer chunks up to the mark. Data for the last chunk is copied to the beginning of the buffer + * and the offsets are updates accordingly. + */ +void http_request_buffer_sweep(hw_request_buffer* buffer); + +/** + * Prints the buffer. + */ +void http_request_buffer_print(hw_request_buffer* buffer); + +/** + * Ensures that there's will be a sizable chunk available when the it's requested via http_request_buffer_chunk. + * This will ensure that the requested size doesn't exceed the maximum buffer size and will try to + * re-use the existing underlying buffers if there's sufficient space still to be used. + * Therefore, "requested_size" is just a hint, and not necessarily the size that will be allocated. + * + * Returns true if the allocation succeeds, false otherwise (errno is set accordingly). + */ +bool http_request_buffer_alloc(hw_request_buffer* buf, size_t requested_size); + +/** + * Returns a new buffer chunk to be used by request reader. + */ +void http_request_buffer_chunk(hw_request_buffer* buffer, hw_request_buffer_chunk* chunk); + +/** + * Informs the buffer manager that *pointer is a memory region of interest and that will have to be made available to + * the caller eventually, when http_request_buffer_locate is called. The pin is given a key by the caller so it can be + * retrieved later. Pins will be overwritten if the same key is used multiple times. + */ +void http_request_buffer_pin(hw_request_buffer* buffer, void* key, void* pointer); + +/** + * Allows the caller to assign a new key to a pin. + */ +void http_request_buffer_reassign_pin(hw_request_buffer* buffer, void* old_key, void* new_key); + +/** + * Returns the pointer to the memory region associated with a pin. If there isn't a pin with that key, then + * the value of "default_pointer" is returned. + * + * This call guarantees that the memory region returned is contiguous, i.e. even if multiple chunks non contiguous + * chunks were used before, the pointer returned by this function points to a memory region that can be read + * sequentially. It's the responsibility of the caller to know how long that region is, by keeping track of its + * length as it's being read in. + */ +void* http_request_buffer_locate(hw_request_buffer* buffer, void* key, void* default_pointer); + +/** + * Destroys the buffer and any underlying buffer chunks. + */ +void http_request_buffer_destroy(hw_request_buffer* buffer); diff --git a/srcs/httpserver/src/haywire/http_response.c b/srcs/httpserver/src/haywire/http_response.c new file mode 100644 index 0000000..58b4987 --- /dev/null +++ b/srcs/httpserver/src/haywire/http_response.c @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include "http_response.h" +#include "http_server.h" +#include "http_response_cache.h" +#include "hw_string.h" + +#define CRLF "\r\n" +KHASH_MAP_INIT_STR(string_hashmap, char*) + +hw_http_response hw_create_http_response(http_connection* connection) +{ + http_response* response = malloc(sizeof(http_response)); + response->connection = connection; + response->request = connection->request; + response->http_major = 1; + response->http_minor = 1; + response->body.value = NULL; + response->body.length = 0; + response->number_of_headers = 0; + return response; +} + +void hw_free_http_response(hw_http_response* response) +{ + http_response* resp = (http_response*)response; + free(resp); +} + +void hw_set_http_version(hw_http_response* response, unsigned short major, unsigned short minor) +{ + http_response* resp = (http_response*)response; + resp->http_major = major; + resp->http_minor = minor; +} + +void hw_set_response_status_code(hw_http_response* response, hw_string* status_code) +{ + http_response* resp = (http_response*)response; + resp->status_code = *status_code; +} + +void hw_set_response_header(hw_http_response* response, hw_string* name, hw_string* value) +{ + http_response* resp = (http_response*)response; + http_header* header = &resp->headers[resp->number_of_headers]; + header->name = *name; + header->value = *value; + resp->headers[resp->number_of_headers] = *header; + resp->number_of_headers++; +} + +void hw_set_body(hw_http_response* response, hw_string* body) +{ + http_response* resp = (http_response*)response; + resp->body = *body; +} + +int num_chars(int n) { + int r = 1; + if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n; + while (n > 9) { + n /= 10; + r++; + } + return r; +} + +hw_string* create_response_buffer(hw_http_response* response) +{ + http_response* resp = (http_response*)response; + hw_string* response_string = malloc(sizeof(hw_string)); + hw_string* cached_entry = get_cached_request(resp->status_code.value); + hw_string content_length; + + int i = 0; + + char length_header[] = "Content-Length: "; + int line_sep_size = sizeof(CRLF); + + int header_buffer_incr = 512; + int body_size = resp->body.length; + int header_size_remaining = header_buffer_incr; + int response_size = header_size_remaining + sizeof(length_header) + num_chars(resp->body.length) + 2 * line_sep_size + body_size + line_sep_size; + + response_string->value = malloc(response_size); + + response_string->length = 0; + append_string(response_string, cached_entry); + + for (i=0; i< resp->number_of_headers; i++) + { + http_header header = resp->headers[i]; + + header_size_remaining -= header.name.length + 2 + header.value.length + line_sep_size; + if (header_size_remaining < 0) { + header_size_remaining += header_buffer_incr * ((-header_size_remaining/header_buffer_incr) + 1); + response_size += header_size_remaining; + response_string->value = realloc(response_string->value, response_size); + } + + append_string(response_string, &header.name); + APPENDSTRING(response_string, ": "); + append_string(response_string, &header.value); + APPENDSTRING(response_string, CRLF); + } + + /* Add the body */ + APPENDSTRING(response_string, length_header); + + string_from_int(&content_length, body_size, 10); + + if (body_size > 0) { + append_string(response_string, &content_length); + } + else { + hw_string zero_content; + zero_content.value = "0"; + zero_content.length = 1; + append_string(response_string, &zero_content); + } + + APPENDSTRING(response_string, CRLF CRLF); + + if (body_size > 0) + { + append_string(response_string, &resp->body); + } + + return response_string; +} + + +void hw_http_response_send(hw_http_response* response, void* user_data, http_response_complete_callback callback) +{ + hw_write_context* write_context = malloc(sizeof(hw_write_context)); + http_response* resp = (http_response*)response; + hw_string* response_buffer = create_response_buffer(response); + + write_context->connection = resp->connection; + write_context->user_data = user_data; + write_context->callback = callback; + write_context->request = resp->request; + + http_server_write_response(write_context, response_buffer); + resp->sent = 1; + + free(response_buffer); + hw_free_http_response(response); +} diff --git a/srcs/httpserver/src/haywire/http_response.h b/srcs/httpserver/src/haywire/http_response.h new file mode 100644 index 0000000..0d8725e --- /dev/null +++ b/srcs/httpserver/src/haywire/http_response.h @@ -0,0 +1,34 @@ +#pragma once +#include "haywire.h" +#include "http_connection.h" + +typedef struct +{ + hw_string name; + hw_string value; +} http_header; + +#define MAX_HEADERS 64 +typedef struct +{ + http_connection* connection; + http_request* request; + unsigned short http_major; + unsigned short http_minor; + hw_string status_code; + http_header headers[MAX_HEADERS]; + int number_of_headers; + hw_string body; + int sent; +} http_response; + +typedef struct +{ + http_connection* connection; + http_request* request; + void* user_data; + http_response_complete_callback callback; +} hw_write_context; + +hw_http_response hw_create_http_response(http_connection* connection); +hw_string* create_response_buffer(hw_http_response* response); diff --git a/srcs/httpserver/src/haywire/http_response_cache.c b/srcs/httpserver/src/haywire/http_response_cache.c new file mode 100644 index 0000000..80ba854 --- /dev/null +++ b/srcs/httpserver/src/haywire/http_response_cache.c @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include +#include "uv.h" +#include "haywire.h" +#include "hw_string.h" +#include "khash.h" +#include "http_response_cache.h" +#include "http_server.h" + +#define CRLF "\r\n" +KHASH_MAP_INIT_STR(string_hashmap, hw_string*) + +static uv_timer_t cache_invalidation_timer; +static uv_key_t thread_cache_key; + +void initialize_http_request_cache(); +void free_http_request_cache(); +void http_request_cache_timer(uv_timer_t* handle); +void create_cached_http_request(khash_t(string_hashmap)* http_request_cache, const char* http_status); +void set_cached_request(khash_t(string_hashmap)* http_request_cache, char* http_status, hw_string* cache_entry); +hw_string* get_cached_request(const char* http_status); + +void initialize_http_request_cache() +{ + uv_key_create(&thread_cache_key); +} + +void http_request_cache_configure_listener(uv_loop_t* loop, uv_async_t* handle) +{ + uv_timer_t* cache_invalidation_timer = malloc(sizeof(uv_timer_t)); + uv_timer_init(loop, cache_invalidation_timer); + uv_timer_start(cache_invalidation_timer, http_request_cache_timer, 500, 500); + + if (handle != NULL) + { + uv_unref((uv_handle_t*) cache_invalidation_timer); + } +} + +void http_request_cache_timer(uv_timer_t* timer) +{ + khash_t(string_hashmap)* http_request_cache = uv_key_get(&thread_cache_key); + if (http_request_cache != NULL) + { + free_http_request_cache(http_request_cache); + } +} + +void free_http_request_cache(khash_t(string_hashmap)* http_request_cache) +{ + const char* k; + hw_string* v; + + kh_foreach(http_request_cache, k, v, + { + free((char*)k); + free(v->value); + free(v); + }); + + kh_destroy_string_hashmap(http_request_cache); + uv_key_set(&thread_cache_key, NULL); +} + +void create_cached_http_request(khash_t(string_hashmap)* http_request_cache, const char* http_status) +{ + hw_string* cache_entry = malloc(sizeof(hw_string)); + cache_entry->value = calloc(1024, 1); + cache_entry->length = 0; + hw_string status; + status.length = strlen(http_status); + status.value = http_status; + + append_string(cache_entry, http_v1_1); + append_string(cache_entry, &status); + APPENDSTRING(cache_entry, CRLF); + append_string(cache_entry, server_name); + APPENDSTRING(cache_entry, CRLF); + + // Add the current time. + time_t curtime; + time(&curtime); + char* current_time = ctime(&curtime); + hw_string current_datetime; + current_datetime.value = current_time; + current_datetime.length = strlen(current_time); + APPENDSTRING(cache_entry, "Date: "); + append_string(cache_entry, ¤t_datetime); + + set_cached_request(http_request_cache, http_status, cache_entry); +} + +void set_cached_request(khash_t(string_hashmap)* http_request_cache, char* http_status, hw_string* cache_entry) +{ + int ret; + khiter_t key; + + key = kh_get(string_hashmap, http_request_cache, http_status); + if (key == kh_end(http_request_cache)) + { + key = kh_put(string_hashmap, http_request_cache, dupstr(http_status), &ret); + kh_value(http_request_cache, key) = cache_entry; + } +} + +hw_string* get_cached_request(const char* http_status) +{ + int is_missing = 0; + void* val; + khash_t(string_hashmap)* http_request_cache; + khiter_t key = 0; + + /* This thread hasn't created a response cache so create one */ + http_request_cache = uv_key_get(&thread_cache_key); + if (http_request_cache == NULL) + { + http_request_cache = kh_init(string_hashmap); + uv_key_set(&thread_cache_key, http_request_cache); + } + + key = kh_get(string_hashmap, http_request_cache, http_status); + if (key == kh_end(http_request_cache)) + { + create_cached_http_request(http_request_cache, http_status); + } + else + { + val = kh_value(http_request_cache, key); + } + + key = kh_get(string_hashmap, http_request_cache, http_status); + if (key == kh_end(http_request_cache)) + { + is_missing = 1; + } + else + { + val = kh_value(http_request_cache, key); + is_missing = 0; + } + + if (is_missing) + { + return NULL; + } + + return val; +} + diff --git a/srcs/httpserver/src/haywire/http_response_cache.h b/srcs/httpserver/src/haywire/http_response_cache.h new file mode 100644 index 0000000..fb04b4e --- /dev/null +++ b/srcs/httpserver/src/haywire/http_response_cache.h @@ -0,0 +1,7 @@ +#pragma once +#include "uv.h" +#include "haywire.h" + +void initialize_http_request_cache(); +hw_string* get_cached_request(const char* http_status); +void http_request_cache_configure_listener(uv_loop_t* loop, uv_async_t* handle); diff --git a/srcs/httpserver/src/haywire/http_server.c b/srcs/httpserver/src/haywire/http_server.c new file mode 100644 index 0000000..a9fa504 --- /dev/null +++ b/srcs/httpserver/src/haywire/http_server.c @@ -0,0 +1,524 @@ +#pragma comment (lib, "libuv.lib") +#pragma comment (lib, "ws2_32.lib") +#pragma comment(lib, "psapi.lib") +#pragma comment(lib, "Iphlpapi.lib") + +#ifdef PLATFORM_POSIX +#include +#endif // PLATFORM_POSIX + +#include +#include +#include +#include +#include +#include +#include "uv.h" +#include "hw_string.h" +#include "khash.h" +#include "http_server.h" +#include "connection_consumer.h" +#include "connection_dispatcher.h" +#include "http_response_cache.h" +#include "server_stats.h" +#include "configuration/configuration.h" +#include "http_connection.h" +#include "http_request.h" + +#define UVERR(err, msg) dzlog_error("%s: %s\n", msg, uv_strerror(err)) + //fprintf(stderr, "%s: %s\n", msg, uv_strerror(err)) +#define CHECK(r, msg) \ +if (r) { \ +uv_err_t err = uv_last_error(uv_loop); \ +UVERR(err, msg); \ +exit(1); \ +} + +KHASH_MAP_INIT_STR(string_hashmap, hw_route_entry*) + +configuration* config; +static uv_tcp_t server; +static http_parser_settings parser_settings; +static struct sockaddr_in listen_address; + +uv_loop_t* uv_loop; +void* routes; +hw_string* http_v1_0; +hw_string* http_v1_1; +hw_string* server_name; +int listener_count; +uv_async_t* listener_async_handles; +uv_loop_t* listener_event_loops; +uv_barrier_t* listeners_created_barrier; + +int hw_init_with_config(configuration* c) +{ +#ifdef DEBUG + char route[] = "/stats"; + hw_http_add_route(route, get_server_stats, NULL); +#endif /* DEBUG */ + /* Copy the configuration */ + config = malloc(sizeof(configuration)); + config->http_listen_address = dupstr(c->http_listen_address); + config->http_listen_port = c->http_listen_port; + config->thread_count = c->thread_count; + config->tcp_nodelay = c->tcp_nodelay; + config->listen_backlog = c->listen_backlog? c->listen_backlog : SOMAXCONN; + config->parser = dupstr(c->parser); + config->balancer = dupstr(c->balancer); + config->max_request_size = c->max_request_size; + + http_v1_0 = create_string("HTTP/1.0 "); + http_v1_1 = create_string("HTTP/1.1 "); + server_name = create_string("Server: Haywire/master"); + + if (strcmp(config->parser, "http_parser") == 0) + { + http_stream_on_read = &http_stream_on_read_http_parser; + } + http_server_write_response = &http_server_write_response_single; + return 0; +} + +int hw_init_from_config(char* configuration_filename) +{ + configuration* config = load_configuration(configuration_filename); + if (config == NULL) + { + return 1; + } + return hw_init_with_config(config); +} + +void print_configuration() +{ +#if 0 + dzlog_debug("Address: %s\n\tPort: %d\n\tThreads: %d\n\tBalancer: %s\n\t" + "Parser: %s\n\tTCP No Delay: %s\n\tListen backlog: %d\n\tMaximum request size: %d\n", + config->http_listen_address, + config->http_listen_port, + config->thread_count, + config->balancer, + config->parser, + config->tcp_nodelay? "on": "off", + config->listen_backlog, + config->max_request_size); +#endif +} + +http_connection* create_http_connection() +{ + http_connection* connection = calloc(1, sizeof(http_connection)); + connection->buffer = http_request_buffer_init(config->max_request_size); + INCREMENT_STAT(stat_connections_created_total); + return connection; +} + +void free_http_connection(http_connection* connection) +{ + if (connection->request) + { + free_http_request(connection->request); + } + http_request_buffer_destroy(connection->buffer); + free(connection); + INCREMENT_STAT(stat_connections_destroyed_total); +} + +void set_route(void* hashmap, char* name, hw_route_entry* route_entry) +{ + int ret; + khiter_t k; + khash_t(string_hashmap) *h = hashmap; + k = kh_put(string_hashmap, h, dupstr(name), &ret); + kh_value(h, k) = route_entry; +} + +void hw_http_add_route(char *route, http_request_callback callback, void* user_data) +{ + hw_route_entry* route_entry = malloc(sizeof(hw_route_entry)); + route_entry->callback = callback; + route_entry->user_data = user_data; + + if (routes == NULL) + { + routes = kh_init(string_hashmap); + } + set_route(routes, route, route_entry); + dzlog_debug("Added route %s\n", route); // TODO: Replace with logging instead. +} + +void free_http_server() +{ + /* TODO: Shut down accepting incoming requests */ + khash_t(string_hashmap) *h = routes; + const char* k; + const char* v; + kh_foreach(h, k, v, { free((char*)k); free((char*)v); }); + kh_destroy(string_hashmap, routes); +} + +int hw_http_open() +{ + int threads = config->thread_count; + uv_async_t* service_handle = 0; + + if (routes == NULL) + { + routes = kh_init(string_hashmap); + } + + parser_settings.on_header_field = http_request_on_header_field; + parser_settings.on_header_value = http_request_on_header_value; + parser_settings.on_headers_complete = http_request_on_headers_complete; + parser_settings.on_body = http_request_on_body; + parser_settings.on_message_begin = http_request_on_message_begin; + parser_settings.on_message_complete = http_request_on_message_complete; + parser_settings.on_url = http_request_on_url; + +#ifdef UNIX + signal(SIGPIPE, SIG_IGN); +#endif // UNIX + + listener_count = threads; + + /* TODO: Use the return values from uv_tcp_init() and uv_tcp_bind() */ + uv_loop = uv_default_loop(); + + listener_async_handles = calloc(listener_count, sizeof(uv_async_t)); + listener_event_loops = calloc(listener_count, sizeof(uv_loop_t)); + + listeners_created_barrier = malloc(sizeof(uv_barrier_t)); + uv_barrier_init(listeners_created_barrier, listener_count + 1); + + service_handle = malloc(sizeof(uv_async_t)); + uv_async_init(uv_loop, service_handle, NULL); + + if (listener_count == 0) + { + /* If running single threaded there is no need to use the IPC pipe + to distribute requests between threads so lets avoid the IPC overhead */ + + int rc; + rc = uv_tcp_init_ex(uv_loop, &server, AF_INET); + if (rc != 0) + { + dzlog_warn("TWO %d\n", rc); + } + + if (strcmp(config->balancer, "reuseport") == 0) + { + uv_os_fd_t fd; + int on = 1; + rc = uv_fileno(&server, &fd); + if (rc != 0) + { + dzlog_warn("ONE %d\n", rc); + } + rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char*)&on, sizeof(on)); + if (rc != 0) + { + dzlog_warn("THREE %d\n", errno); + } + } + + initialize_http_request_cache(); + http_request_cache_configure_listener(uv_loop, NULL); + + uv_ip4_addr(config->http_listen_address, config->http_listen_port, &listen_address); + uv_tcp_bind(&server, (const struct sockaddr*)&listen_address, 0); + + if (config->tcp_nodelay) { + uv_tcp_nodelay(&server, 1); + } + + uv_listen((uv_stream_t*)&server, config->listen_backlog, http_stream_on_connect); + print_configuration(); + dzlog_debug("Listening...\n"); + //uv_run(uv_loop, UV_RUN_DEFAULT); + } + else if (listener_count > 0 && strcmp(config->balancer, "ipc") == 0) + { + int i = 0; + + /* If we are running multi-threaded spin up the dispatcher that uses + an IPC pipe to send socket connection requests to listening threads */ + struct server_ctx* servers; + servers = calloc(threads, sizeof(servers[0])); + for (i = 0; i < threads; i++) + { + int rc = 0; + struct server_ctx* ctx = servers + i; + ctx->index = i; + ctx->listen_backlog = config->listen_backlog; + + rc = uv_sem_init(&ctx->semaphore, 0); + rc = uv_thread_create(&ctx->thread_id, connection_consumer_start, ctx); + } + + uv_barrier_wait(listeners_created_barrier); + initialize_http_request_cache(); + + start_connection_dispatching(UV_TCP, threads, servers, config->http_listen_address, config->http_listen_port, config->tcp_nodelay, config->listen_backlog); + } + else if (listener_count > 0 && strcmp(config->balancer, "reuseport") == 0) + { + struct server_ctx* servers; + servers = calloc(threads, sizeof(servers[0])); + + for (int i = 0; i < threads; i++) + { + struct server_ctx* ctx = servers + i; + ctx->index = i; + int rc = uv_thread_create(&ctx->thread_id, reuseport_thread_start, ctx); + } + + print_configuration(); + dzlog_debug("Listening...\n"); + //uv_run(uv_loop, UV_RUN_DEFAULT); + } + + return 0; +} + +void reuseport_thread_start(void *arg) +{ + int rc; + struct server_ctx* ctx; + uv_loop_t* loop; + uv_tcp_t svr; + + ctx = arg; + loop = uv_loop_new(); + listener_event_loops[ctx->index] = *loop; + + initialize_http_request_cache(); + http_request_cache_configure_listener(loop, &listener_async_handles[ctx->index]); + + struct sockaddr_in addr; + uv_tcp_t server; + + rc = uv_tcp_init_ex(loop, &server, AF_INET); + uv_ip4_addr(config->http_listen_address, config->http_listen_port, &addr); + + uv_os_fd_t fd; + int on = 1; + uv_fileno(&server, &fd); + rc = setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, (char*)&on, sizeof(on)); + if (rc != 0) + { + dzlog_warn("%d\n", errno); + } + + uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0); + int r = uv_listen((uv_stream_t*) &server, 128, http_stream_on_connect); + + rc = uv_run(loop, UV_RUN_DEFAULT); + uv_loop_delete(loop); +} + +void http_stream_on_connect(uv_stream_t* stream, int status) +{ + http_connection* connection = create_http_connection(); + uv_tcp_init(stream->loop, &connection->stream); + http_parser_init(&connection->parser, HTTP_REQUEST); + + connection->parser.data = connection; + connection->stream.data = connection; + + /* TODO: Use the return values from uv_accept() and uv_read_start() */ + uv_accept(stream, (uv_stream_t*)&connection->stream); + connection->state = OPEN; + uv_read_start((uv_stream_t*)&connection->stream, http_stream_on_alloc, http_stream_on_read); +} + +void http_stream_on_alloc(uv_handle_t* client, size_t suggested_size, uv_buf_t* buf) +{ + http_connection* connection = (http_connection*)client->data; + + bool success = http_request_buffer_alloc(connection->buffer, suggested_size); + hw_request_buffer_chunk chunk; + chunk.size = 0; + chunk.buffer = NULL; + + if (success) { + http_request_buffer_chunk(connection->buffer, &chunk); + } else { + /* TODO out of memory event - we should hook up an application callback to this */ + } + + *buf = uv_buf_init(chunk.buffer, chunk.size); +} + +void http_stream_on_close(uv_handle_t* handle) +{ + uv_handle_t* stream = handle; + http_connection* connection = stream->data; + + if (connection->state != CLOSED) { + connection->state = CLOSED; + http_connection* connection = (http_connection*)handle->data; + free_http_connection(connection); + } +} + +void http_stream_close_connection(http_connection* connection) { + if (connection->state == OPEN) { + connection->state = CLOSING; + uv_close(&connection->stream, http_stream_on_close); + } +} + +void handle_request_error(http_connection* connection) +{ + uv_handle_t* stream = &connection->stream; + + if (connection->state == OPEN) { + uv_read_stop(stream); + } + + connection->keep_alive = false; + + if (connection->request) { + if (connection->state == OPEN) { + /* Send the error message back. */ + http_request_on_message_complete(&connection->parser); + } + } else { + http_stream_close_connection(connection); + } +} + +void handle_bad_request(http_connection* connection) +{ + if (connection->request) { + connection->request->state = BAD_REQUEST; + } + + handle_request_error(connection); +} + +void handle_buffer_exceeded_error(http_connection* connection) +{ + if (connection->request) { + connection->request->state = SIZE_EXCEEDED; + } + + handle_request_error(connection); +} + +void handle_internal_error(http_connection* connection) +{ + if (connection->request) { + connection->request->state = INTERNAL_ERROR; + } + + handle_request_error(connection); +} + +void http_stream_on_shutdown(uv_shutdown_t* req, int status) +{ + http_connection* connection = req->data; + uv_handle_t* stream = &connection->stream; + if (connection->state == OPEN) { + http_stream_close_connection(connection); + } + free(req); +} + +void http_stream_on_read_http_parser(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf) +{ + http_connection* connection = (http_connection*)tcp->data; + + if (nread > 0) { + /* Need to tell the buffer that we care about the next nread bytes */ + http_request_buffer_consume(connection->buffer, nread); + + http_parser_execute(&connection->parser, &parser_settings, (const char*) buf->base, nread); + + if (connection->parser.http_errno) { + handle_bad_request(connection); + } else { + /* We finished processing this chunk of data, therefore we can't get rid of any chunks that were read before + * the current one we're reading. + * + * We can't get rid of the one we're currently processing as it may contain a partial request that will + * only be complete with the next chunk coming into a subsequent call of this function. */ + http_request_buffer_sweep(connection->buffer); + } + } else if (nread == 0) { + /* no-op - there's no data to be read, but there might be later */ + } + else if (nread == UV_ENOBUFS) { + handle_buffer_exceeded_error(connection); + } + else if (nread == UV_EOF){ + uv_shutdown_t* req = malloc(sizeof(uv_shutdown_t)); + req->data = connection; + uv_shutdown(req, &connection->stream, http_stream_on_shutdown); + } + else if (nread == UV_ECONNRESET || nread == UV_ECONNABORTED) { + /* Let's close the connection as the other peer just disappeared */ + http_stream_close_connection(connection); + } else { + /* We didn't see this coming, but an unexpected UV error code was passed in, so we'll + * respond with a blanket 500 error if we can */ + handle_internal_error(connection); + } +} + +void http_server_cleanup_write(char* response_string, hw_write_context* write_context, uv_write_t* write_req) +{ + free(response_string); + free(write_context); + free(write_req); +} + +int http_server_write_response_single(hw_write_context* write_context, hw_string* response) +{ + http_connection* connection = write_context->connection; + + if (connection->state == OPEN) { + uv_write_t *write_req = (uv_write_t *) malloc(sizeof(*write_req) + sizeof(uv_buf_t)); + uv_buf_t *resbuf = (uv_buf_t *) (write_req + 1); + + resbuf->base = response->value; + resbuf->len = response->length; + + write_req->data = write_context; + + uv_stream_t *stream = (uv_stream_t *) &write_context->connection->stream; + + if (uv_is_writable(stream)) { + /* Ensuring that the the response can still be written. */ + uv_write(write_req, stream, resbuf, 1, http_server_after_write); + /* TODO: Use the return values from uv_write() */ + } else { + /* The connection was closed, so we can write the response back, but we still need to free up things */ + http_server_cleanup_write(resbuf->base, write_context, write_req); + } + } + + return 0; +} + +void http_server_after_write(uv_write_t* req, int status) +{ + hw_write_context* write_context = (hw_write_context*)req->data; + uv_buf_t *resbuf = (uv_buf_t *)(req+1); + uv_handle_t* stream = (uv_handle_t*) req->handle; + + http_connection* connection = write_context->connection; + + if (!connection->keep_alive && connection->state == OPEN) { + http_stream_close_connection(connection); + } + + if (write_context->callback) + { + write_context->callback(write_context->user_data); + } + + http_server_cleanup_write(resbuf->base, write_context, req); +} + diff --git a/srcs/httpserver/src/haywire/http_server.h b/srcs/httpserver/src/haywire/http_server.h new file mode 100644 index 0000000..7861cf4 --- /dev/null +++ b/srcs/httpserver/src/haywire/http_server.h @@ -0,0 +1,40 @@ +#pragma once +#include "uv.h" +#include "haywire.h" +#include "http_connection.h" +#include "http_parser.h" +#include "http_response.h" + +typedef struct +{ + http_request_callback callback; + void* user_data; +} hw_route_entry; + +union stream_handle +{ + uv_pipe_t pipe; + uv_tcp_t tcp; +}; + +extern void* routes; +extern uv_loop_t* uv_loop; +extern hw_string* http_v1_0; +extern hw_string* http_v1_1; +extern hw_string* server_name; +extern int listener_count; +extern uv_async_t* listener_async_handles; +extern uv_loop_t* listener_event_loops; +extern uv_barrier_t* listeners_created_barrier; + +void (*http_stream_on_read)(uv_stream_t*, ssize_t, const uv_buf_t*); +int (*http_server_write_response)(hw_write_context*, hw_string*); + +http_connection* create_http_connection(); +void http_stream_on_connect(uv_stream_t* stream, int status); +void http_stream_on_alloc(uv_handle_t* client, size_t suggested_size, uv_buf_t* buf); +void http_stream_on_close(uv_handle_t* handle); +int http_server_write_response_single(hw_write_context* write_context, hw_string* response); +void http_server_after_write(uv_write_t* req, int status); +void http_stream_on_read_http_parser(uv_stream_t* tcp, ssize_t nread, const uv_buf_t* buf); +void reuseport_thread_start(void *arg); diff --git a/srcs/httpserver/src/haywire/hw_string.c b/srcs/httpserver/src/haywire/hw_string.c new file mode 100644 index 0000000..d28e8b5 --- /dev/null +++ b/srcs/httpserver/src/haywire/hw_string.c @@ -0,0 +1,76 @@ +#include +#include +#include +#include "haywire.h" + +hw_string* create_string(const char* value) +{ + int length = (int)strlen(value); + hw_string* str = malloc(sizeof(hw_string)); + str->value = malloc(length); + memcpy(str->value, value, length); + str->length = length; + return str; +} + +hw_string* hw_strdup(hw_string* tocopy) +{ + hw_string* str = malloc(sizeof(hw_string)); + str->value = tocopy->value; + str->length = tocopy->length; + return str; +} + +int hw_strcmp(hw_string* a, hw_string* b) { + int ret; + + if (a->length > b->length) { + ret = strncmp(a->value, b->value, b->length); + if (!ret) { + ret = 1; + } + } else if (a->length == b->length) { + ret = strncmp(a->value, b->value, a->length); + } else { + ret = strncmp(a->value, b->value, a->length); + if (!ret) { + ret = -1; + } + } + + return ret; +} + +void append_string(hw_string* destination, hw_string* source) +{ + void* location = (void*) (destination->value + destination->length); + memcpy(location, source->value, source->length); + destination->length += source->length; +} + +/* Added because of http://stackoverflow.com/questions/8359966/strdup-returning-address-out-of-bounds */ +char* dupstr(const char *s) +{ + char* result = malloc(strlen(s) + 1); + if (result != NULL) + { + strcpy(result, s); + } + return result; +} + +void string_from_int(hw_string* str, int val, int base) +{ + static char buf[32] = {0}; + int i = 30; + int length = 0; + + for(; val && i ; --i, val /= base) + { + buf[i] = "0123456789abcdef"[val % base]; + length++; + } + + str->value = &buf[i+1]; + str->length = length; +} \ No newline at end of file diff --git a/srcs/httpserver/src/haywire/hw_string.h b/srcs/httpserver/src/haywire/hw_string.h new file mode 100644 index 0000000..0af8d4f --- /dev/null +++ b/srcs/httpserver/src/haywire/hw_string.h @@ -0,0 +1,9 @@ +#pragma once + +hw_string* create_string(const char* value); +int hw_strcmp(hw_string* a, hw_string* b); +hw_string* hw_strdup(hw_string* tocopy); +void append_string(hw_string* destination, hw_string* source); +char* dupstr(const char *s); +void string_from_int(hw_string* str, int val, int base); +int hw_strcmp(hw_string* a, hw_string* b); diff --git a/srcs/httpserver/src/haywire/khash.h b/srcs/httpserver/src/haywire/khash.h new file mode 100644 index 0000000..3aede7e --- /dev/null +++ b/srcs/httpserver/src/haywire/khash.h @@ -0,0 +1,617 @@ +/* The MIT License + + Copyright (c) 2008, 2009, 2011 by Attractive Chaos + + 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. + */ + +/* + An example: + + #include "khash.h" + KHASH_MAP_INIT_INT(32, char) + int main() { + int ret, is_missing; + khiter_t k; + khash_t(32) *h = kh_init(32); + k = kh_put(32, h, 5, &ret); + kh_value(h, k) = 10; + k = kh_get(32, h, 10); + is_missing = (k == kh_end(h)); + k = kh_get(32, h, 5); + kh_del(32, h, k); + for (k = kh_begin(h); k != kh_end(h); ++k) + if (kh_exist(h, k)) kh_value(h, k) = 1; + kh_destroy(32, h); + return 0; + } + */ + +/* + 2013-05-02 (0.2.8): + + * Use quadratic probing. When the capacity is power of 2, stepping function + i*(i+1)/2 guarantees to traverse each bucket. It is better than double + hashing on cache performance and is more robust than linear probing. + + In theory, double hashing should be more robust than quadratic probing. + However, my implementation is probably not for large hash tables, because + the second hash function is closely tied to the first hash function, + which reduce the effectiveness of double hashing. + + Reference: http://research.cs.vt.edu/AVresearch/hashing/quadratic.php + + 2011-12-29 (0.2.7): + + * Minor code clean up; no actual effect. + + 2011-09-16 (0.2.6): + + * The capacity is a power of 2. This seems to dramatically improve the + speed for simple keys. Thank Zilong Tan for the suggestion. Reference: + + - http://code.google.com/p/ulib/ + - http://nothings.org/computer/judy/ + + * Allow to optionally use linear probing which usually has better + performance for random input. Double hashing is still the default as it + is more robust to certain non-random input. + + * Added Wang's integer hash function (not used by default). This hash + function is more robust to certain non-random input. + + 2011-02-14 (0.2.5): + + * Allow to declare global functions. + + 2009-09-26 (0.2.4): + + * Improve portability + + 2008-09-19 (0.2.3): + + * Corrected the example + * Improved interfaces + + 2008-09-11 (0.2.2): + + * Improved speed a little in kh_put() + + 2008-09-10 (0.2.1): + + * Added kh_clear() + * Fixed a compiling error + + 2008-09-02 (0.2.0): + + * Changed to token concatenation which increases flexibility. + + 2008-08-31 (0.1.2): + + * Fixed a bug in kh_get(), which has not been tested previously. + + 2008-08-31 (0.1.1): + + * Added destructor + */ + + +#ifndef __AC_KHASH_H +#define __AC_KHASH_H + +/*! + @header + + Generic hash table library. + */ + +#define AC_VERSION_KHASH_H "0.2.8" + +#include +#include +#include + +/* compiler specific configuration */ + +#if UINT_MAX == 0xffffffffu +typedef unsigned int khint32_t; +#elif ULONG_MAX == 0xffffffffu +typedef unsigned long khint32_t; +#endif + +#if ULONG_MAX == ULLONG_MAX +typedef unsigned long khint64_t; +#else +typedef unsigned long long khint64_t; +#endif + +#ifdef _MSC_VER +#define kh_inline __inline +#else +#define kh_inline inline +#endif + +typedef khint32_t khint_t; +typedef khint_t khiter_t; + +#define __ac_isempty(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&2) +#define __ac_isdel(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&1) +#define __ac_iseither(flag, i) ((flag[i>>4]>>((i&0xfU)<<1))&3) +#define __ac_set_isdel_false(flag, i) (flag[i>>4]&=~(1ul<<((i&0xfU)<<1))) +#define __ac_set_isempty_false(flag, i) (flag[i>>4]&=~(2ul<<((i&0xfU)<<1))) +#define __ac_set_isboth_false(flag, i) (flag[i>>4]&=~(3ul<<((i&0xfU)<<1))) +#define __ac_set_isdel_true(flag, i) (flag[i>>4]|=1ul<<((i&0xfU)<<1)) + +#define __ac_fsize(m) ((m) < 16? 1 : (m)>>4) + +#ifndef kroundup32 +#define kroundup32(x) (--(x), (x)|=(x)>>1, (x)|=(x)>>2, (x)|=(x)>>4, (x)|=(x)>>8, (x)|=(x)>>16, ++(x)) +#endif + +#ifndef kcalloc +#define kcalloc(N,Z) calloc(N,Z) +#endif +#ifndef kmalloc +#define kmalloc(Z) malloc(Z) +#endif +#ifndef krealloc +#define krealloc(P,Z) realloc(P,Z) +#endif +#ifndef kfree +#define kfree(P) free(P) +#endif + +static const double __ac_HASH_UPPER = 0.77; + +#define __KHASH_TYPE(name, khkey_t, khval_t) \ +typedef struct { \ +khint_t n_buckets, size, n_occupied, upper_bound; \ +khint32_t *flags; \ +khkey_t *keys; \ +khval_t *vals; \ +} kh_##name##_t; + +#define __KHASH_PROTOTYPES(name, khkey_t, khval_t) \ +extern kh_##name##_t *kh_init_##name(void); \ +extern void kh_destroy_##name(kh_##name##_t *h); \ +extern void kh_clear_##name(kh_##name##_t *h); \ +extern khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key); \ +extern int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets); \ +extern khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret); \ +extern void kh_del_##name(kh_##name##_t *h, khint_t x); + +#define __KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ +SCOPE kh_##name##_t *kh_init_##name(void) { \ +return (kh_##name##_t*)kcalloc(1, sizeof(kh_##name##_t)); \ +} \ +SCOPE void kh_destroy_##name(kh_##name##_t *h) \ +{ \ +if (h) { \ +kfree((void *)h->keys); kfree(h->flags); \ +kfree((void *)h->vals); \ +kfree(h); \ +} \ +} \ +SCOPE void kh_clear_##name(kh_##name##_t *h) \ +{ \ +if (h && h->flags) { \ +memset(h->flags, 0xaa, __ac_fsize(h->n_buckets) * sizeof(khint32_t)); \ +h->size = h->n_occupied = 0; \ +} \ +} \ +SCOPE khint_t kh_get_##name(const kh_##name##_t *h, khkey_t key) \ +{ \ +if (h->n_buckets) { \ +khint_t k, i, last, mask, step = 0; \ +mask = h->n_buckets - 1; \ +k = __hash_func(key); i = k & mask; \ +last = i; \ +while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ +i = (i + (++step)) & mask; \ +if (i == last) return h->n_buckets; \ +} \ +return __ac_iseither(h->flags, i)? h->n_buckets : i; \ +} else return 0; \ +} \ +SCOPE int kh_resize_##name(kh_##name##_t *h, khint_t new_n_buckets) \ +{ /* This function uses 0.25*n_buckets bytes of working space instead of [sizeof(key_t+val_t)+.25]*n_buckets. */ \ +khint32_t *new_flags = 0; \ +khint_t j = 1; \ +{ \ +kroundup32(new_n_buckets); \ +if (new_n_buckets < 4) new_n_buckets = 4; \ +if (h->size >= (khint_t)(new_n_buckets * __ac_HASH_UPPER + 0.5)) j = 0; /* requested size is too small */ \ +else { /* hash table size to be changed (shrink or expand); rehash */ \ +new_flags = (khint32_t*)kmalloc(__ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ +if (!new_flags) return -1; \ +memset(new_flags, 0xaa, __ac_fsize(new_n_buckets) * sizeof(khint32_t)); \ +if (h->n_buckets < new_n_buckets) { /* expand */ \ +khkey_t *new_keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ +if (!new_keys) return -1; \ +h->keys = new_keys; \ +if (kh_is_map) { \ +khval_t *new_vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ +if (!new_vals) return -1; \ +h->vals = new_vals; \ +} \ +} /* otherwise shrink */ \ +} \ +} \ +if (j) { /* rehashing is needed */ \ +for (j = 0; j != h->n_buckets; ++j) { \ +if (__ac_iseither(h->flags, j) == 0) { \ +khkey_t key = h->keys[j]; \ +khval_t val; \ +khint_t new_mask; \ +new_mask = new_n_buckets - 1; \ +if (kh_is_map) val = h->vals[j]; \ +__ac_set_isdel_true(h->flags, j); \ +while (1) { /* kick-out process; sort of like in Cuckoo hashing */ \ +khint_t k, i, step = 0; \ +k = __hash_func(key); \ +i = k & new_mask; \ +while (!__ac_isempty(new_flags, i)) i = (i + (++step)) & new_mask; \ +__ac_set_isempty_false(new_flags, i); \ +if (i < h->n_buckets && __ac_iseither(h->flags, i) == 0) { /* kick out the existing element */ \ +{ khkey_t tmp = h->keys[i]; h->keys[i] = key; key = tmp; } \ +if (kh_is_map) { khval_t tmp = h->vals[i]; h->vals[i] = val; val = tmp; } \ +__ac_set_isdel_true(h->flags, i); /* mark it as deleted in the old hash table */ \ +} else { /* write the element and jump out of the loop */ \ +h->keys[i] = key; \ +if (kh_is_map) h->vals[i] = val; \ +break; \ +} \ +} \ +} \ +} \ +if (h->n_buckets > new_n_buckets) { /* shrink the hash table */ \ +h->keys = (khkey_t*)krealloc((void *)h->keys, new_n_buckets * sizeof(khkey_t)); \ +if (kh_is_map) h->vals = (khval_t*)krealloc((void *)h->vals, new_n_buckets * sizeof(khval_t)); \ +} \ +kfree(h->flags); /* free the working space */ \ +h->flags = new_flags; \ +h->n_buckets = new_n_buckets; \ +h->n_occupied = h->size; \ +h->upper_bound = (khint_t)(h->n_buckets * __ac_HASH_UPPER + 0.5); \ +} \ +return 0; \ +} \ +SCOPE khint_t kh_put_##name(kh_##name##_t *h, khkey_t key, int *ret) \ +{ \ +khint_t x; \ +if (h->n_occupied >= h->upper_bound) { /* update the hash table */ \ +if (h->n_buckets > (h->size<<1)) { \ +if (kh_resize_##name(h, h->n_buckets - 1) < 0) { /* clear "deleted" elements */ \ +*ret = -1; return h->n_buckets; \ +} \ +} else if (kh_resize_##name(h, h->n_buckets + 1) < 0) { /* expand the hash table */ \ +*ret = -1; return h->n_buckets; \ +} \ +} /* TODO: to implement automatically shrinking; resize() already support shrinking */ \ +{ \ +khint_t k, i, site, last, mask = h->n_buckets - 1, step = 0; \ +x = site = h->n_buckets; k = __hash_func(key); i = k & mask; \ +if (__ac_isempty(h->flags, i)) x = i; /* for speed up */ \ +else { \ +last = i; \ +while (!__ac_isempty(h->flags, i) && (__ac_isdel(h->flags, i) || !__hash_equal(h->keys[i], key))) { \ +if (__ac_isdel(h->flags, i)) site = i; \ +i = (i + (++step)) & mask; \ +if (i == last) { x = site; break; } \ +} \ +if (x == h->n_buckets) { \ +if (__ac_isempty(h->flags, i) && site != h->n_buckets) x = site; \ +else x = i; \ +} \ +} \ +} \ +if (__ac_isempty(h->flags, x)) { /* not present at all */ \ +h->keys[x] = key; \ +__ac_set_isboth_false(h->flags, x); \ +++h->size; ++h->n_occupied; \ +*ret = 1; \ +} else if (__ac_isdel(h->flags, x)) { /* deleted */ \ +h->keys[x] = key; \ +__ac_set_isboth_false(h->flags, x); \ +++h->size; \ +*ret = 2; \ +} else *ret = 0; /* Don't touch h->keys[x] if present and not deleted */ \ +return x; \ +} \ +SCOPE void kh_del_##name(kh_##name##_t *h, khint_t x) \ +{ \ +if (x != h->n_buckets && !__ac_iseither(h->flags, x)) { \ +__ac_set_isdel_true(h->flags, x); \ +--h->size; \ +} \ +} + +#define KHASH_DECLARE(name, khkey_t, khval_t) \ +__KHASH_TYPE(name, khkey_t, khval_t) \ +__KHASH_PROTOTYPES(name, khkey_t, khval_t) + +#define KHASH_INIT2(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ +__KHASH_TYPE(name, khkey_t, khval_t) \ +__KHASH_IMPL(name, SCOPE, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +#define KHASH_INIT(name, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) \ +KHASH_INIT2(name, static kh_inline, khkey_t, khval_t, kh_is_map, __hash_func, __hash_equal) + +/* --- BEGIN OF HASH FUNCTIONS --- */ + +/*! @function + @abstract Integer hash function + @param key The integer [khint32_t] + @return The hash value [khint_t] + */ +#define kh_int_hash_func(key) (khint32_t)(key) +/*! @function + @abstract Integer comparison function + */ +#define kh_int_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract 64-bit integer hash function + @param key The integer [khint64_t] + @return The hash value [khint_t] + */ +#define kh_int64_hash_func(key) (khint32_t)((key)>>33^(key)^(key)<<11) +/*! @function + @abstract 64-bit integer comparison function + */ +#define kh_int64_hash_equal(a, b) ((a) == (b)) +/*! @function + @abstract const char* hash function + @param s Pointer to a null terminated string + @return The hash value + */ +static kh_inline khint_t __ac_X31_hash_string(const char *s) +{ + khint_t h = (khint_t)*s; + if (h) for (++s ; *s; ++s) h = (h << 5) - h + (khint_t)*s; + return h; +} +/*! @function + @abstract Another interface to const char* hash function + @param key Pointer to a null terminated string [const char*] + @return The hash value [khint_t] + */ +#define kh_str_hash_func(key) __ac_X31_hash_string(key) +/*! @function + @abstract Const char* comparison function + */ +#define kh_str_hash_equal(a, b) (strcmp(a, b) == 0) + +static kh_inline khint_t __ac_Wang_hash(khint_t key) +{ + key += ~(key << 15); + key ^= (key >> 10); + key += (key << 3); + key ^= (key >> 6); + key += ~(key << 11); + key ^= (key >> 16); + return key; +} +#define kh_int_hash_func2(k) __ac_Wang_hash((khint_t)key) + +/* --- END OF HASH FUNCTIONS --- */ + +/* Other convenient macros... */ + +/*! + @abstract Type of the hash table. + @param name Name of the hash table [symbol] + */ +#define khash_t(name) kh_##name##_t + +/*! @function + @abstract Initiate a hash table. + @param name Name of the hash table [symbol] + @return Pointer to the hash table [khash_t(name)*] + */ +#define kh_init(name) kh_init_##name() + +/*! @function + @abstract Destroy a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_destroy(name, h) kh_destroy_##name(h) + +/*! @function + @abstract Reset a hash table without deallocating memory. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + */ +#define kh_clear(name, h) kh_clear_##name(h) + +/*! @function + @abstract Resize a hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param s New size [khint_t] + */ +#define kh_resize(name, h, s) kh_resize_##name(h, s) + +/*! @function + @abstract Insert a key to the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @param r Extra return code: 0 if the key is present in the hash table; + 1 if the bucket is empty (never used); 2 if the element in + the bucket has been deleted [int*] + @return Iterator to the inserted element [khint_t] + */ +#define kh_put(name, h, k, r) kh_put_##name(h, k, r) + +/*! @function + @abstract Retrieve a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Key [type of keys] + @return Iterator to the found element, or kh_end(h) if the element is absent [khint_t] + */ +#define kh_get(name, h, k) kh_get_##name(h, k) + +/*! @function + @abstract Remove a key from the hash table. + @param name Name of the hash table [symbol] + @param h Pointer to the hash table [khash_t(name)*] + @param k Iterator to the element to be deleted [khint_t] + */ +#define kh_del(name, h, k) kh_del_##name(h, k) + +/*! @function + @abstract Test whether a bucket contains data. + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return 1 if containing data; 0 otherwise [int] + */ +#define kh_exist(h, x) (!__ac_iseither((h)->flags, (x))) + +/*! @function + @abstract Get key given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Key [type of keys] + */ +#define kh_key(h, x) ((h)->keys[x]) + +/*! @function + @abstract Get value given an iterator + @param h Pointer to the hash table [khash_t(name)*] + @param x Iterator to the bucket [khint_t] + @return Value [type of values] + @discussion For hash sets, calling this results in segfault. + */ +#define kh_val(h, x) ((h)->vals[x]) + +/*! @function + @abstract Alias of kh_val() + */ +#define kh_value(h, x) ((h)->vals[x]) + +/*! @function + @abstract Get the start iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The start iterator [khint_t] + */ +#define kh_begin(h) (khint_t)(0) + +/*! @function + @abstract Get the end iterator + @param h Pointer to the hash table [khash_t(name)*] + @return The end iterator [khint_t] + */ +#define kh_end(h) ((h)->n_buckets) + +/*! @function + @abstract Get the number of elements in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of elements in the hash table [khint_t] + */ +#define kh_size(h) ((h)->size) + +/*! @function + @abstract Get the number of buckets in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @return Number of buckets in the hash table [khint_t] + */ +#define kh_n_buckets(h) ((h)->n_buckets) + +/*! @function + @abstract Iterate over the entries in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param kvar Variable to which key will be assigned + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach(h, kvar, vvar, code) { khint_t __i; \ +for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ +if (!kh_exist(h,__i)) continue; \ +(kvar) = kh_key(h,__i); \ +(vvar) = kh_val(h,__i); \ +code; \ +} } + +/*! @function + @abstract Iterate over the values in the hash table + @param h Pointer to the hash table [khash_t(name)*] + @param vvar Variable to which value will be assigned + @param code Block of code to execute + */ +#define kh_foreach_value(h, vvar, code) { khint_t __i; \ +for (__i = kh_begin(h); __i != kh_end(h); ++__i) { \ +if (!kh_exist(h,__i)) continue; \ +(vvar) = kh_val(h,__i); \ +code; \ +} } + +/* More conenient interfaces */ + +/*! @function + @abstract Instantiate a hash set containing integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT(name) \ +KHASH_INIT(name, khint32_t, char, 0, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT(name, khval_t) \ +KHASH_INIT(name, khint32_t, khval_t, 1, kh_int_hash_func, kh_int_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_INT64(name) \ +KHASH_INIT(name, khint64_t, char, 0, kh_int64_hash_func, kh_int64_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing 64-bit integer keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_INT64(name, khval_t) \ +KHASH_INIT(name, khint64_t, khval_t, 1, kh_int64_hash_func, kh_int64_hash_equal) + +typedef const char *kh_cstr_t; +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + */ +#define KHASH_SET_INIT_STR(name) \ +KHASH_INIT(name, kh_cstr_t, char, 0, kh_str_hash_func, kh_str_hash_equal) + +/*! @function + @abstract Instantiate a hash map containing const char* keys + @param name Name of the hash table [symbol] + @param khval_t Type of values [type] + */ +#define KHASH_MAP_INIT_STR(name, khval_t) \ +KHASH_INIT(name, kh_cstr_t, khval_t, 1, kh_str_hash_func, kh_str_hash_equal) + +#endif /* __AC_KHASH_H */ + diff --git a/srcs/httpserver/src/haywire/picohttpparser.c b/srcs/httpserver/src/haywire/picohttpparser.c new file mode 100644 index 0000000..5d47311 --- /dev/null +++ b/srcs/httpserver/src/haywire/picohttpparser.c @@ -0,0 +1,595 @@ +/* + * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, + * Shigeo Mitsunari + * + * The software is licensed under either the MIT License (below) or the Perl + * license. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ +#include +#include +#include +#ifdef __SSE4_2__ +#ifdef _MSC_VER +#include +#else +#include +#endif +#endif +#include "picohttpparser.h" + +/* $Id$ */ + +#if __GNUC__ >= 3 +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#else +#define likely(x) (x) +#define unlikely(x) (x) +#endif + +#ifdef _MSC_VER +#define ALIGNED(n) _declspec(align(n)) +#else +#define ALIGNED(n) __attribute__((aligned(n))) +#endif + +#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u) + +#define CHECK_EOF() \ + if (buf == buf_end) { \ + *ret = -2; \ + return NULL; \ + } + +#define EXPECT_CHAR(ch) \ + CHECK_EOF(); \ + if (*buf++ != ch) { \ + *ret = -1; \ + return NULL; \ + } + +#define ADVANCE_TOKEN(tok, toklen) \ + do { \ + const char *tok_start = buf; \ + static const char ALIGNED(16) ranges2[] = "\000\040\177\177"; \ + int found2; \ + buf = findchar_fast(buf, buf_end, ranges2, sizeof(ranges2) - 1, &found2); \ + if (!found2) { \ + CHECK_EOF(); \ + } \ + while (1) { \ + if (*buf == ' ') { \ + break; \ + } else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \ + if ((unsigned char)*buf < '\040' || *buf == '\177') { \ + *ret = -1; \ + return NULL; \ + } \ + } \ + ++buf; \ + CHECK_EOF(); \ + } \ + tok = tok_start; \ + toklen = buf - tok_start; \ + } while (0) + +static const char *token_char_map = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\1\1\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0" + "\0\1\1\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\0\0\1\1" + "\1\1\1\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\0\1\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + +static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, size_t ranges_size, int *found) +{ + *found = 0; +#if __SSE4_2__ + if (likely(buf_end - buf >= 16)) { + __m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges); + + size_t left = (buf_end - buf) & ~15; + do { + __m128i b16 = _mm_loadu_si128((void *)buf); + int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS); + if (unlikely(r != 16)) { + buf += r; + *found = 1; + break; + } + buf += 16; + left -= 16; + } while (likely(left != 0)); + } +#endif + return buf; +} + +static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret) +{ + const char *token_start = buf; + +#ifdef __SSE4_2__ + static const char ranges1[] = "\0\010" + /* allow HT */ + "\012\037" + /* allow SP and up to but not including DEL */ + "\177\177" + /* allow chars w. MSB set */ + ; + int found; + buf = findchar_fast(buf, buf_end, ranges1, sizeof(ranges1) - 1, &found); + if (found) + goto FOUND_CTL; +#else + /* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */ + while (likely(buf_end - buf >= 8)) { +#define DOIT() \ + do { \ + if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \ + goto NonPrintable; \ + ++buf; \ + } while (0) + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); +#undef DOIT + continue; + NonPrintable: + if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { + goto FOUND_CTL; + } + ++buf; + } +#endif + for (;; ++buf) { + CHECK_EOF(); + if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { + if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { + goto FOUND_CTL; + } + } + } +FOUND_CTL: + if (likely(*buf == '\015')) { + ++buf; + EXPECT_CHAR('\012'); + *token_len = buf - 2 - token_start; + } else if (*buf == '\012') { + *token_len = buf - token_start; + ++buf; + } else { + *ret = -1; + return NULL; + } + *token = token_start; + + return buf; +} + +static const char *is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret) +{ + int ret_cnt = 0; + buf = last_len < 3 ? buf : buf + last_len - 3; + + while (1) { + CHECK_EOF(); + if (*buf == '\015') { + ++buf; + CHECK_EOF(); + EXPECT_CHAR('\012'); + ++ret_cnt; + } else if (*buf == '\012') { + ++buf; + ++ret_cnt; + } else { + ++buf; + ret_cnt = 0; + } + if (ret_cnt == 2) { + return buf; + } + } + + *ret = -2; + return NULL; +} + +/* *_buf is always within [buf, buf_end) upon success */ +static const char *parse_int(const char *buf, const char *buf_end, int *value, int *ret) +{ + int v; + CHECK_EOF(); + if (!('0' <= *buf && *buf <= '9')) { + *ret = -1; + return NULL; + } + v = 0; + for (;; ++buf) { + CHECK_EOF(); + if ('0' <= *buf && *buf <= '9') { + v = v * 10 + *buf - '0'; + } else { + break; + } + } + + *value = v; + return buf; +} + +/* returned pointer is always within [buf, buf_end), or null */ +static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret) +{ + EXPECT_CHAR('H'); + EXPECT_CHAR('T'); + EXPECT_CHAR('T'); + EXPECT_CHAR('P'); + EXPECT_CHAR('/'); + EXPECT_CHAR('1'); + EXPECT_CHAR('.'); + return parse_int(buf, buf_end, minor_version, ret); +} + +static const char *parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, size_t *num_headers, + size_t max_headers, int *ret) +{ + for (;; ++*num_headers) { + CHECK_EOF(); + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + break; + } else if (*buf == '\012') { + ++buf; + break; + } + if (*num_headers == max_headers) { + *ret = -1; + return NULL; + } + if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) { + if (!token_char_map[(unsigned char)*buf]) { + *ret = -1; + return NULL; + } + /* parsing name, but do not discard SP before colon, see + * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */ + headers[*num_headers].name = buf; + static const char ALIGNED(16) ranges1[] = "::\x00\037"; + int found; + buf = findchar_fast(buf, buf_end, ranges1, sizeof(ranges1) - 1, &found); + if (!found) { + CHECK_EOF(); + } + while (1) { + if (*buf == ':') { + break; + } else if (*buf < ' ') { + *ret = -1; + return NULL; + } + ++buf; + CHECK_EOF(); + } + headers[*num_headers].name_len = buf - headers[*num_headers].name; + ++buf; + for (;; ++buf) { + CHECK_EOF(); + if (!(*buf == ' ' || *buf == '\t')) { + break; + } + } + } else { + headers[*num_headers].name = NULL; + headers[*num_headers].name_len = 0; + } + if ((buf = get_token_to_eol(buf, buf_end, &headers[*num_headers].value, &headers[*num_headers].value_len, ret)) == NULL) { + return NULL; + } + } + return buf; +} + +static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path, + size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, + size_t max_headers, int *ret) +{ + /* skip first empty line (some clients add CRLF after POST content) */ + CHECK_EOF(); + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + } else if (*buf == '\012') { + ++buf; + } + + /* parse request line */ + ADVANCE_TOKEN(*method, *method_len); + ++buf; + ADVANCE_TOKEN(*path, *path_len); + ++buf; + if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { + return NULL; + } + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + } else if (*buf == '\012') { + ++buf; + } else { + *ret = -1; + return NULL; + } + + return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); +} + +int phr_parse_request(const char *buf_start, size_t len, const char **method, size_t *method_len, const char **path, + size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len) +{ + const char *buf = buf_start, *buf_end = buf_start + len; + size_t max_headers = *num_headers; + int r; + + *method = NULL; + *method_len = 0; + *path = NULL; + *path_len = 0; + *minor_version = -1; + *num_headers = 0; + + /* if last_len != 0, check if the request is complete (a fast countermeasure + againt slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers, + &r)) == NULL) { + return r; + } + + return (int)(buf - buf_start); +} + +static const char *parse_response(const char *buf, const char *buf_end, int *minor_version, int *status, const char **msg, + size_t *msg_len, struct phr_header *headers, size_t *num_headers, size_t max_headers, int *ret) +{ + /* parse "HTTP/1.x" */ + if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { + return NULL; + } + /* skip space */ + if (*buf++ != ' ') { + *ret = -1; + return NULL; + } + /* parse status code */ + if ((buf = parse_int(buf, buf_end, status, ret)) == NULL) { + return NULL; + } + /* skip space */ + if (*buf++ != ' ') { + *ret = -1; + return NULL; + } + /* get message */ + if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) { + return NULL; + } + + return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); +} + +int phr_parse_response(const char *buf_start, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len, + struct phr_header *headers, size_t *num_headers, size_t last_len) +{ + const char *buf = buf_start, *buf_end = buf + len; + size_t max_headers = *num_headers; + int r; + + *minor_version = -1; + *status = 0; + *msg = NULL; + *msg_len = 0; + *num_headers = 0; + + /* if last_len != 0, check if the response is complete (a fast countermeasure + against slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) == NULL) { + return r; + } + + return (int)(buf - buf_start); +} + +int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len) +{ + const char *buf = buf_start, *buf_end = buf + len; + size_t max_headers = *num_headers; + int r; + + *num_headers = 0; + + /* if last_len != 0, check if the response is complete (a fast countermeasure + against slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) { + return r; + } + + return (int)(buf - buf_start); +} + +enum { + CHUNKED_IN_CHUNK_SIZE, + CHUNKED_IN_CHUNK_EXT, + CHUNKED_IN_CHUNK_DATA, + CHUNKED_IN_CHUNK_CRLF, + CHUNKED_IN_TRAILERS_LINE_HEAD, + CHUNKED_IN_TRAILERS_LINE_MIDDLE +}; + +static int decode_hex(int ch) +{ + if ('0' <= ch && ch <= '9') { + return ch - '0'; + } else if ('A' <= ch && ch <= 'F') { + return ch - 'A' + 0xa; + } else if ('a' <= ch && ch <= 'f') { + return ch - 'a' + 0xa; + } else { + return -1; + } +} + +ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *_bufsz) +{ + size_t dst = 0, src = 0, bufsz = *_bufsz; + ssize_t ret = -2; /* incomplete */ + + while (1) { + switch (decoder->_state) { + case CHUNKED_IN_CHUNK_SIZE: + for (;; ++src) { + int v; + if (src == bufsz) + goto Exit; + if ((v = decode_hex(buf[src])) == -1) { + if (decoder->_hex_count == 0) { + ret = -1; + goto Exit; + } + break; + } + if (decoder->_hex_count == sizeof(size_t) * 2) { + ret = -1; + goto Exit; + } + decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v; + ++decoder->_hex_count; + } + decoder->_hex_count = 0; + decoder->_state = CHUNKED_IN_CHUNK_EXT; + /* fallthru */ + case CHUNKED_IN_CHUNK_EXT: + /* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */ + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] == '\012') + break; + } + ++src; + if (decoder->bytes_left_in_chunk == 0) { + if (decoder->consume_trailer) { + decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; + break; + } else { + goto Complete; + } + } + decoder->_state = CHUNKED_IN_CHUNK_DATA; + /* fallthru */ + case CHUNKED_IN_CHUNK_DATA: { + size_t avail = bufsz - src; + if (avail < decoder->bytes_left_in_chunk) { + if (dst != src) + memmove(buf + dst, buf + src, avail); + src += avail; + dst += avail; + decoder->bytes_left_in_chunk -= avail; + goto Exit; + } + if (dst != src) + memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk); + src += decoder->bytes_left_in_chunk; + dst += decoder->bytes_left_in_chunk; + decoder->bytes_left_in_chunk = 0; + decoder->_state = CHUNKED_IN_CHUNK_CRLF; + } + /* fallthru */ + case CHUNKED_IN_CHUNK_CRLF: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] != '\015') + break; + } + if (buf[src] != '\012') { + ret = -1; + goto Exit; + } + ++src; + decoder->_state = CHUNKED_IN_CHUNK_SIZE; + break; + case CHUNKED_IN_TRAILERS_LINE_HEAD: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] != '\015') + break; + } + if (buf[src++] == '\012') + goto Complete; + decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE; + /* fallthru */ + case CHUNKED_IN_TRAILERS_LINE_MIDDLE: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] == '\012') + break; + } + ++src; + decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; + break; + default: + assert(!"decoder is corrupt"); + } + } + +Complete: + ret = bufsz - src; +Exit: + if (dst != src) + memmove(buf + dst, buf + src, bufsz - src); + *_bufsz = dst; + return ret; +} + +#undef CHECK_EOF +#undef EXPECT_CHAR +#undef ADVANCE_TOKEN \ No newline at end of file diff --git a/srcs/httpserver/src/haywire/picohttpparser.h b/srcs/httpserver/src/haywire/picohttpparser.h new file mode 100644 index 0000000..ded2259 --- /dev/null +++ b/srcs/httpserver/src/haywire/picohttpparser.h @@ -0,0 +1,86 @@ +/* + * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, + * Shigeo Mitsunari + * + * The software is licensed under either the MIT License (below) or the Perl + * license. + * + * 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 picohttpparser_h +#define picohttpparser_h + +#include + +#ifdef _MSC_VER +#define ssize_t intptr_t +#endif + +/* $Id$ */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* contains name and value of a header (name == NULL if is a continuing line + * of a multiline header */ +struct phr_header { + const char *name; + size_t name_len; + const char *value; + size_t value_len; +}; + +/* returns number of bytes consumed if successful, -2 if request is partial, + * -1 if failed */ +int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len, + int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len); + +/* ditto */ +int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len, + struct phr_header *headers, size_t *num_headers, size_t last_len); + +/* ditto */ +int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len); + +/* should be zero-filled before start */ +struct phr_chunked_decoder { + size_t bytes_left_in_chunk; /* number of bytes left in current chunk */ + char consume_trailer; /* if trailing headers should be consumed */ + char _hex_count; + char _state; +}; + +/* the function rewrites the buffer given as (buf, bufsz) removing the chunked- + * encoding headers. When the function returns without an error, bufsz is + * updated to the length of the decoded data available. Applications should + * repeatedly call the function while it returns -2 (incomplete) every time + * supplying newly arrived data. If the end of the chunked-encoded data is + * found, the function returns a non-negative number indicating the number of + * octets left undecoded at the tail of the supplied buffer. Returns -1 on + * error. + */ +ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/srcs/httpserver/src/haywire/route_compare_method.c b/srcs/httpserver/src/haywire/route_compare_method.c new file mode 100644 index 0000000..3690d05 --- /dev/null +++ b/srcs/httpserver/src/haywire/route_compare_method.c @@ -0,0 +1,91 @@ +#include +#include +#include +#include +#include "hw_string.h" +#include "route_compare_method.h" + +typedef struct hw_route_token_st { + hw_string string; + int start; +} hw_route_token; + +void hw_route_next_token(hw_string* url, int start, hw_route_token* result) { + while (start < url->length && (url->value[start] == '/')) { + start++; + } + + int end = start; + + while (end < url->length && url->value[end] != '/') { + end++; + } + + if (end != start) { + result->string.value = url->value + start; + result->string.length = end - start; + result->start = start; + } + else { + result->string.value = NULL; + result->string.length = 0; + result->start = -1; + } +} + +int hw_route_compare_method(hw_string* url, char* route) +{ + int equal = 0; + int match = 0; + + // TODO route should probably be a hw_string* too + hw_string hw_route; + hw_route.value = route; + hw_route.length = strlen(route); + + hw_route_token route_token; + hw_route_token request_token; + + hw_route_next_token(url, 0, &request_token); + hw_route_next_token(&hw_route, 0, &route_token); + + while (route_token.string.value != NULL && request_token.string.value != NULL) + { + if (route_token.string.value[0] == '*') { + // wildcard support: any route fragment marked with '*' matches the corresponding url fragment + equal = 1; + } + else + { + match = hw_strcmp(&route_token.string, &request_token.string); + if (!match) + { + equal = 1; + } + else + { + equal = 0; + break; + } + } + + hw_route_next_token(url, request_token.start + request_token.string.length + 1, &request_token); + hw_route_next_token(&hw_route, route_token.start + route_token.string.length + 1, &route_token); + } + + if (!equal) + { + match = hw_strcmp(url, &hw_route); + if (!match) + { + equal = 1; + } + } + + if ((route_token.string.value == NULL && request_token.string.value != NULL) || (route_token.string.value != NULL && request_token.string.value == NULL)) + { + equal = 0; + } + + return equal; +} \ No newline at end of file diff --git a/srcs/httpserver/src/haywire/route_compare_method.h b/srcs/httpserver/src/haywire/route_compare_method.h new file mode 100644 index 0000000..cc35aa4 --- /dev/null +++ b/srcs/httpserver/src/haywire/route_compare_method.h @@ -0,0 +1,3 @@ +#include "haywire.h" + +int hw_route_compare_method(hw_string* url, char* route); diff --git a/srcs/httpserver/src/haywire/server_stats.c b/srcs/httpserver/src/haywire/server_stats.c new file mode 100644 index 0000000..3b00746 --- /dev/null +++ b/srcs/httpserver/src/haywire/server_stats.c @@ -0,0 +1,61 @@ +#include +#include "server_stats.h" +#include "haywire.h" + +int stat_connections_created_total; +int stat_connections_destroyed_total; +int stat_requests_created_total; +int stat_requests_destroyed_total; + +#define CRLF "\r\n" + +static const char stats_response[] = + "HTTP/1.1 200 OK" CRLF + "Server: Haywire/master" CRLF + "Date: Fri, 26 Aug 2011 00:31:53 GMT" CRLF + "Connection: Keep-Alive" CRLF + "Content-Type: text/html" CRLF + "Content-Length: 16" CRLF + CRLF + "stats printed" CRLF; + +void get_server_stats(http_request* request, hw_http_response* response, void* user_data) +{ + hw_string status_code; + hw_string content_type_name; + hw_string content_type_value; + hw_string body; + hw_string keep_alive_name; + hw_string keep_alive_value; + + SETSTRING(status_code, HTTP_STATUS_200); + hw_set_response_status_code(response, &status_code); + + SETSTRING(content_type_name, "Content-Type"); + + SETSTRING(content_type_value, "text/html"); + hw_set_response_header(response, &content_type_name, &content_type_value); + + SETSTRING(body, "stats printed"); + hw_set_body(response, &body); + + if (request->keep_alive) + { + SETSTRING(keep_alive_name, "Connection"); + + SETSTRING(keep_alive_value, "Keep-Alive"); + hw_set_response_header(response, &keep_alive_name, &keep_alive_value); + } + else + { + hw_set_http_version(response, 1, 0); + } + + hw_http_response_send(response, NULL, NULL); + + printf("connections_created_total: %d\nconnections_destroyed_total: %d\nrequests_created_total: %d\nrequests_destroyed_total: %d\n\n", + stat_connections_created_total, + stat_connections_destroyed_total, + stat_requests_created_total, + stat_requests_destroyed_total); +} diff --git a/srcs/httpserver/src/haywire/server_stats.h b/srcs/httpserver/src/haywire/server_stats.h new file mode 100644 index 0000000..1743fd8 --- /dev/null +++ b/srcs/httpserver/src/haywire/server_stats.h @@ -0,0 +1,16 @@ +#pragma once +#include "haywire.h" + +#ifdef DEBUG +#define INCREMENT_STAT(stat) stat++ +#else +#define INCREMENT_STAT(stat) +#endif /* DEBUG */ + + +extern int stat_connections_created_total; +extern int stat_connections_destroyed_total; +extern int stat_requests_created_total; +extern int stat_requests_destroyed_total; + +void get_server_stats(http_request* request, hw_http_response* response, void* user_data); diff --git a/srcs/libs/CMakeLists.txt b/srcs/libs/CMakeLists.txt index c7f21ac..48ffae8 100644 --- a/srcs/libs/CMakeLists.txt +++ b/srcs/libs/CMakeLists.txt @@ -4,7 +4,10 @@ PROJECT(${LIB_PROJECT_TARGET}) include(ExternalProject) -INCLUDE_DIRECTORIES(include ./ ./include ../lwip/src/include ../lwip/src/arch_linux/include ../include ${COMMON_INCLUDE}) +INCLUDE_DIRECTORIES(include + ./ ./include ../lwip/src/include ../lwip/src/arch_linux/include ../include + ../httpserver/include ../httpserver/src/haywire ../httpserver/src/haywire/configuration + ${COMMON_INCLUDE}) FILE(GLOB C_HEADS include/*.h include/uthash/*.h include/s2j/*.h) AUX_SOURCE_DIRECTORY(json C_SRC) @@ -27,4 +30,7 @@ SET(CMAKE_C_STANDARD 99) ADD_DEFINITIONS(-DBUILD_VERSION="${GIT_VERSION}") LINK_LIBRARIES(${COMMON_LIBS}) -ADD_LIBRARY(${LIB_PROJECT_TARGET} ${C_SRC} ${C_HEADS}) \ No newline at end of file +ADD_LIBRARY(${LIB_PROJECT_TARGET} ${C_SRC} ${C_HEADS}) + +TARGET_LINK_LIBRARIES(${LIB_PROJECT_TARGET} haywire) +TARGET_LINK_LIBRARIES(${LIB_PROJECT_TARGET} opendhcpd) \ No newline at end of file diff --git a/srcs/libs/include/http_svr.h b/srcs/libs/include/http_svr.h new file mode 100644 index 0000000..34825e9 --- /dev/null +++ b/srcs/libs/include/http_svr.h @@ -0,0 +1,15 @@ +// +// Created by xajhuang on 2022/11/4. +// + +#ifndef VCPE_PROJECT_HTTP_SVR_H +#define VCPE_PROJECT_HTTP_SVR_H +#ifdef __cplusplus +extern "C" { +#endif +int http_svr_init(); + +#ifdef __cplusplus +} +#endif +#endif//VCPE_PROJECT_HTTP_SVR_H diff --git a/srcs/libs/network/http_svr.c b/srcs/libs/network/http_svr.c new file mode 100644 index 0000000..d2458b3 --- /dev/null +++ b/srcs/libs/network/http_svr.c @@ -0,0 +1,23 @@ +// +// Created by xajhuang on 2022/11/4. +// +#include "http_svr.h" +#include "user_errno.h" +#include "haywire.h" +#include "misc.h" + +int http_svr_init() { + configuration config; + config.http_listen_address = "0.0.0.0"; + config.http_listen_port = 8000; + config.thread_count = 0; + config.parser = "http_parser"; + config.balancer = "ipc"; + config.tcp_nodelay = TRUE; + config.max_request_size = 1048576; + + hw_init_with_config(&config); + + hw_http_open(); + return ERR_SUCCESS; +} \ No newline at end of file