diff --git a/.gitignore b/.gitignore deleted file mode 100644 index bf093a9..0000000 --- a/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -coverage_report - -# Coverage stuff -*.info -*.gcda -*.gcno - -*.out -*.exe -*.o -*.a -*.so -*.pem \ No newline at end of file diff --git a/LICENSE b/LICENSE index 0e59be6..a646523 100644 --- a/LICENSE +++ b/LICENSE @@ -4,4 +4,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of 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. \ No newline at end of file +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile b/Makefile index 89daa0f..afabc27 100644 --- a/Makefile +++ b/Makefile @@ -1,76 +1,12 @@ +.PHONY: all clean example -CC = gcc -CFLAGS = -I. -Wall -Wextra -O0 -g3 -LFLAGS = -AR = ar +all: chttp.c chttp.h example -CFILES = $(shell find src -name "*.c") -HFILES = $(shell find src -name "*.h") -OFILES = $(patsubst %.c,%.o,$(CFILES)) - -# Library names -LIBNAME = chttp -STATIC_LIB = lib$(LIBNAME).a - -# Detect OS and set executable extension -ifeq ($(OS),Windows_NT) - EXT = .exe - LFLAGS = -lws2_32 -else - EXT = .out - LFLAGS += -lssl -lcrypto -endif - -HTTPS ?= 0 -ifneq ($(HTTPS),0) - CFLAGS += -DHTTPS_ENABLED - LFLAGS += -lssl -lcrypto -endif - -# Installation directories -PREFIX ?= /usr/local -LIBDIR = $(PREFIX)/lib -INCDIR = $(PREFIX)/include - -EXAMPLES_CLIENT_SRC := $(shell ls examples/client/*.c 2>/dev/null) -EXAMPLES_SERVER_SRC := $(shell ls examples/server/*.c 2>/dev/null) -EXAMPLES_ENGINE_SRC := $(shell ls examples/engine/*.c 2>/dev/null) - -EXAMPLES_CLIENT := $(patsubst %.c,%$(EXT),$(EXAMPLES_CLIENT_SRC)) -EXAMPLES_SERVER := $(patsubst %.c,%$(EXT),$(EXAMPLES_SERVER_SRC)) -EXAMPLES_ENGINE := $(patsubst %.c,%$(EXT),$(EXAMPLES_ENGINE_SRC)) - -all: chttp.c chttp.h examples lib - -lib: $(STATIC_LIB) - -chttp.c chttp.h: $(HFILES) $(CFILES) +chttp.c chttp.h: $(wildcard src/*.c src/*.h) misc/amalg.py Makefile python misc/amalg.py -# Object files from source files -%.o: %.c $(HFILES) - $(CC) $(CFLAGS) -c $< -o $@ - -# Static library -$(STATIC_LIB): $(OFILES) - $(AR) rcs $@ $^ - -examples: $(EXAMPLES_CLIENT) $(EXAMPLES_SERVER) $(EXAMPLES_ENGINE) - -examples/client/%$(EXT): examples/client/%.c chttp.c chttp.h - $(CC) $(CFLAGS) $< chttp.c -o $@ $(LFLAGS) - -examples/server/%$(EXT): examples/server/%.c chttp.c chttp.h - $(CC) $(CFLAGS) $< chttp.c -o $@ $(LFLAGS) - -examples/engine/%$(EXT): examples/engine/%.c chttp.c chttp.h - $(CC) $(CFLAGS) $< chttp.c -o $@ $(LFLAGS) +example: main.c chttp.c chttp.h + gcc main.c chttp.c -o example.exe clean: - rm -f client_example server_example - rm -f examples/client/*$(EXT) examples/server/*$(EXT) examples/engine/*$(EXT) - rm -f $(OFILES) - rm -f $(STATIC_LIB) - rm -f chttp.c chttp.h - -.PHONY: all lib examples install install-lib install-headers uninstall clean + rm chttp.c chttp.h diff --git a/README.md b/README.md index 1c99dd9..3fee8cd 100644 --- a/README.md +++ b/README.md @@ -1,114 +1,12 @@ # cHTTP -cHTTP is an HTTP **client and server** library for C with minimal dependencies and **distributed as a single chttp.c** file. +This is an HTTP library for C, featuring an HTTP(S) server, HTTP(S) client, and much more! -Here is a list of features: +## Contributing -* HTTP 1.1 client and server -* Non-blocking -* Cross-Platform -* Minimal dependencies -* HTTPS support -* Virtual Hosts -* Single-threaded -* Ergonomic API +Contributions are welcome! The following are some notes on how to work with the codebase. Don't worry if you get something wrong. I will remind you. -Note: This library is beta-quality software as some essential features are still being implemented. - -## Getting Started - -The library is distributed as a single amalgamated `chttp.c` file or as a static library, so you can: - -1) Download `chttp.c` and `chttp.h` from the repository (no need to clone the project) and include them in your soource tree - -2) or clone the project and build the static library by running: - -``` -make libchttp.a -``` - -If you used the amalgamated files, these are the flags required to build a project: - -```bash -# Linux -gcc your_app.c chttp.c - -# Windows -gcc your_app.c chttp.c -lws2_32 -``` - -If you are using the static library, instead of adding `chttp.c`, you will need to add the `-lchttp` flag. - -By default, the library is built without HTTPS. To enable it, add the flags `-DHTTPS_ENABLED -lssl -lcrypto`. - -```bash -# Linux + HTTPS -gcc your_app.c chttp.c -DHTTPS_ENABLED -lssl -lcrypto - -# Windows + HTTPS -gcc your_app.c chttp.c -lws2_32 -DHTTPS_ENABLED -lssl -lcrypto -``` - -## Example - -To give you a feel of the library, here are some examples of programs using cHTTP. To learn more, you can look at the files in `examples/` (they are intended to be skimmed in order). - -Here is a client performing a GET request: -```c -#include -#include - -int main(void) -{ - http_global_init(); - - HTTP_String url = HTTP_STR("http://example.com/index.html"); - - HTTP_String headers[] = { - HTTP_STR("User-Agent: cHTTP"), - }; - - HTTP_Response *res = http_get(url, headers, 1); - - fwrite(res->body.ptr, 1, res->body.len, stdout); - - http_response_free(res); - http_global_free(); - return 0; -} -``` - -And this is a server: -```c -#include - -int main(void) -{ - http_global_init(); - HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080); - - for (;;) { - - HTTP_Request *req; - HTTP_ResponseBuilder builder; - http_server_wait(server, &req, &builder); - - http_response_builder_status(builder, 200); - http_response_builder_header(builder, "Content-Type: text/plain"); - http_response_builder_body(builder, HTTP_STR("Hello, world!")); - http_response_builder_done(builder); - } - - http_server_free(server); - http_global_free(); - return 0; -} -``` - -## Platform Support -cHTTP officially supports Linux and Windows. - -## HTTPS support -Currently, HTTPS is implemented using OpenSSL. If you are on Windows, you'll need to install it manually or disabling it by not defining `HTTPS_ENABLED`. - -## Scalability -cHTTP is designed to reach moderate scale to allow a compact and easy to work with implementation. The non-blocking I/O is based on `poll()` which I would say works up to about 500 concurrent connections. If you have more than that, you should consider APIs like epoll, io_uring, and I/O completion ports. If you do go that route, you can still reuse the cHTTP I/O independent core (see HTTP_Engine) to handle the HTTP protocol for you, both for client and server. +The source code in the `src/` directory is intended to be be amalgamated into a single file before compilation. The amalgamation is not only intended as a distribution method, but also as easy-access documentation, and therefore need to be readable. For this reasons: +1. You never need need to include other cHTTP source files +2. All inclusions of third-party headers are to be placed inside `src/includes.h` +3. All files must start with a single empty line, unless they start with an overview comment of the file, in which case they must have no empty lines at the beginning of the file. +4. All files must end with a single empty line. diff --git a/chttp.c b/chttp.c index 13a0424..64c9fd2 100644 --- a/chttp.c +++ b/chttp.c @@ -1,268 +1,16 @@ -#ifndef HTTP_NOINCLUDE +// cHTTP, an HTTP client and server library! +// +// This file was generated automatically. Do not modify directly. +// +// Refer to the end of this file for the license +#ifndef HTTP_DONT_INCLUDE #include "chttp.h" #endif -//////////////////////////////////////////////////////////////////////////////////////// -// src/sec.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/sec.h" -#ifndef SEC_INCLUDED -#define SEC_INCLUDED - - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#endif - -#ifndef HTTPS_ENABLED - -typedef struct { -} SecureContext; - -#else - -#define MAX_CERTS 10 - -#include - -#include - -typedef struct { - char domain[128]; - SSL_CTX *ctx; -} CertData; - -typedef struct { - - bool is_server; - - SSL_CTX *ctx; - - // Only used when server - int num_certs; - CertData certs[MAX_CERTS]; - -} SecureContext; - -#endif - -void secure_context_global_init(void); -void secure_context_global_free(void); - -int secure_context_init_as_client(SecureContext *sec); - -int secure_context_init_as_server(SecureContext *sec, - HTTP_String cert_file, HTTP_String key_file); - -int secure_context_add_cert(SecureContext *sec, - HTTP_String domain, HTTP_String cert_file, - HTTP_String key_file); - -void secure_context_free(SecureContext *sec); - -#endif // SEC_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/socket_raw.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/socket_raw.h" -#ifndef SOCKET_RAW_INCLUDED -#define SOCKET_RAW_INCLUDED - -#include -#include - -#ifdef _WIN32 -#include -#define RAW_SOCKET SOCKET -#define BAD_SOCKET INVALID_SOCKET -#define POLL WSAPoll -#define CLOSE_SOCKET closesocket -#endif - -#ifdef __linux__ -#include -#include -#define RAW_SOCKET int -#define BAD_SOCKET -1 -#define POLL poll -#define CLOSE_SOCKET close -#endif - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#endif - -int socket_raw_global_init(void); -void socket_raw_global_free(void); - -int set_socket_blocking(RAW_SOCKET sock, bool value); - -RAW_SOCKET listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int backlog); - -#endif // SOCKET_RAW_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/socket.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/socket.h" -#ifndef SOCKET_INCLUDED -#define SOCKET_INCLUDED - -// This module implements the socket state machine to encapsulate -// the complexity of non-blocking TCP and TLS sockets. -// -// A socket is represented by the "Socket" structure, which may -// be in a number of states. As far as an user of the interface -// is concerned, the socket may be DIED, READY, or in an internal -// state that requires waiting for an event. Therefore, if the -// socket is not DIED or READY, the user needs to wait for the -// events specified in the [socket->events] field, then call the -// socket_update function. At some point the socket will become -// either READY or DIED. -// -// When the socket reaches the DIED state, the user must call -// socket_free. -// -// If the socket is ESTABLISHED_READY, the user may call socket_read, -// socket_write, or socket_close on it. - -#ifndef HTTP_AMALGAMATION -#include "sec.h" -#include "parse.h" -#include "socket_raw.h" -#endif - -typedef struct PendingConnect PendingConnect; - -// These should only be relevant to socket.c -typedef enum { - SOCKET_STATE_FREE, - SOCKET_STATE_DIED, - SOCKET_STATE_ESTABLISHED_WAIT, - SOCKET_STATE_ESTABLISHED_READY, - SOCKET_STATE_PENDING, - SOCKET_STATE_ACCEPTED, - SOCKET_STATE_CONNECTED, - SOCKET_STATE_CONNECTING, - SOCKET_STATE_SHUTDOWN, -} SocketState; - -typedef struct { - SocketState state; - - RAW_SOCKET raw; - int events; - - void *user_data; - PendingConnect *pending_connect; - -#ifdef HTTPS_ENABLED - SSL *ssl; -#endif - - SecureContext *sec; - -} Socket; - -void socket_connect(Socket *sock, SecureContext *sec, HTTP_String hostname, uint16_t port, void *user_data); -void socket_connect_ipv4(Socket *sock, SecureContext *sec, HTTP_IPv4 addr, uint16_t port, void *user_data); -void socket_connect_ipv6(Socket *sock, SecureContext *sec, HTTP_IPv6 addr, uint16_t port, void *user_data); -void socket_accept(Socket *sock, SecureContext *sec, RAW_SOCKET raw); -void socket_update(Socket *sock); -void socket_close(Socket *sock); -bool socket_ready(Socket *sock); -bool socket_died(Socket *sock); -int socket_read(Socket *sock, char *dst, int max); -int socket_write(Socket *sock, char *src, int len); -void socket_free(Socket *sock); -bool socket_secure(Socket *sock); -void socket_set_user_data(Socket *sock, void *user_data); -void* socket_get_user_data(Socket *sock); - -#endif // SOCKET_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/socket_pool.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/socket_pool.h" -#ifndef SOCKET_POOL_INCLUDED -#define SOCKET_POOL_INCLUDED - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#include "socket.h" -#include "socket_raw.h" -#endif - -typedef struct SocketPool SocketPool; - -typedef int SocketHandle; - -typedef enum { - SOCKET_EVENT_DIED, - SOCKET_EVENT_READY, - SOCKET_EVENT_ERROR, - SOCKET_EVENT_SIGNAL, -} SocketEventType; - -typedef struct { - SocketEventType type; - SocketHandle handle; - void *user_data; -} SocketEvent; - -int socket_pool_global_init(void); -void socket_pool_global_free(void); - -SocketPool *socket_pool_init(HTTP_String addr, - uint16_t port, uint16_t secure_port, int max_socks, - bool reuse_addr, int backlog, HTTP_String cert_file, - HTTP_String key_file); - -void socket_pool_free(SocketPool *pool); - -int socket_pool_add_cert(SocketPool *pool, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file); - -SocketEvent socket_pool_wait(SocketPool *pool); - -void socket_pool_set_user_data(SocketPool *pool, SocketHandle handle, void *user_data); - -void socket_pool_close(SocketPool *pool, SocketHandle handle); - -int socket_pool_connect(SocketPool *pool, bool secure, - HTTP_String addr, uint16_t port, void *user_data); - -int socket_pool_connect_ipv4(SocketPool *pool, bool secure, - HTTP_IPv4 addr, uint16_t port, void *user_data); - -int socket_pool_connect_ipv6(SocketPool *pool, bool secure, - HTTP_IPv6 addr, uint16_t port, void *user_data); - -int socket_pool_read(SocketPool *pool, SocketHandle handle, char *dst, int len); - -int socket_pool_write(SocketPool *pool, SocketHandle handle, char *src, int len); - -bool socket_pool_secure(SocketPool *pool, SocketHandle handle); - -#endif // SOCKET_POOL_INCLUDED - //////////////////////////////////////////////////////////////////////////////////////// // src/basic.c //////////////////////////////////////////////////////////////////////////////////////// -#line 1 "src/basic.c" -#include -#include - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#endif - bool http_streq(HTTP_String s1, HTTP_String s2) { if (s1.len != s2.len) @@ -361,23 +109,1562 @@ void print_bytes(HTTP_String prefix, HTTP_String src) } //////////////////////////////////////////////////////////////////////////////////////// -// src/parse.c +// src/secure_context.c //////////////////////////////////////////////////////////////////////////////////////// -#line 1 "src/parse.c" -#include // snprintf -#include -#include -#include -#include -#include -#include +int global_secure_context_init(void) +{ + // TODO +} -#ifndef HTTP_AMALGAMATION -#include "parse.h" -#include "basic.h" +int global_secure_context_free(void) +{ + // TODO +} + +int client_secure_context_init(ClientSecureContext *ctx) +{ + // TODO +} + +int client_secure_context_free(ClientSecureContext *ctx) +{ + // TODO +} + +int server_secure_context_init(ServerSecureContext *ctx) +{ + // TODO +} + +int server_secure_context_free(ServerSecureContext *ctx) +{ + // TODO +} + +int server_secure_context_add_certificate(ServerSecureContext *ctx, + String domain, String cert_file) +{ + // TODO +} + +//////////////////////////////////////////////////////////////////////////////////////// +// src/socket.c +//////////////////////////////////////////////////////////////////////////////////////// + +static int create_socket_pair(NATIVE_SOCKET *a, NATIVE_SOCKET *b) +{ +#ifdef _WIN32 + SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock == INVALID_SOCKET) + return -1; + + // Bind to loopback address with port 0 (dynamic port assignment) + struct sockaddr_in addr; + int addr_len = sizeof(addr); + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // 127.0.0.1 + addr.sin_port = 0; // Let system choose port + + if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { + closesocket(sock); + return -1; + } + + if (getsockname(sock, (struct sockaddr*)&addr, &addr_len) == SOCKET_ERROR) { + closesocket(sock); + return -1; + } + + if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { + closesocket(sock); + return -1; + } + + // Optional: Set socket to non-blocking mode + // This prevents send() from blocking if the receive buffer is full + u_long mode = 1; + ioctlsocket(sock, FIONBIO, &mode); // TODO: does this fail? + + *a = sock; + *b = sock; + return 0; +#else + int fds[2]; + if (pipe(fds) < 0) + return -1; + *a = fds[0]; + *b = fds[1]; + return 0; +#endif +} + +static int set_socket_blocking(NATIVE_SOCKET sock, bool value) +{ +#ifdef _WIN32 + u_long mode = !value; + if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR) + return -1; #endif +#ifdef __linux__ + int flags = fcntl(sock, F_GETFL, 0); + if (flags < 0) + return -1; + if (value) flags &= ~O_NONBLOCK; + else flags |= O_NONBLOCK; + if (fcntl(sock, F_SETFL, flags) < 0) + return -1; +#endif + + return 0; +} + +static NATIVE_SOCKET create_listen_socket(String addr, + Port port, bool reuse_addr, int backlog) +{ + NATIVE_SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == NATIVE_SOCKET_INVALID) + return NATIVE_SOCKET_INVALID; + + if (set_socket_blocking(sock, false) < 0) { + CLOSE_NATIVE_SOCKET(sock); + return NATIVE_SOCKET_INVALID; + } + + if (reuse_addr) { + int one = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &one, sizeof(one)); + } + + struct in_addr addr_buf; + if (addr.len == 0) + addr_buf.s_addr = htonl(INADDR_ANY); + else { + + char copy[100]; + if (addr.len >= (int) sizeof(copy)) { + CLOSE_NATIVE_SOCKET(sock); + return NATIVE_SOCKET_INVALID; + } + memcpy(copy, addr.ptr, addr.len); + copy[addr.len] = '\0'; + + if (inet_pton(AF_INET, copy, &addr_buf) < 0) { + CLOSE_NATIVE_SOCKET(sock); + return NATIVE_SOCKET_INVALID; + } + } + + struct sockaddr_in bind_buf; + bind_buf.sin_family = AF_INET; + bind_buf.sin_addr = addr_buf; + bind_buf.sin_port = htons(port); + if (bind(sock, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) { // TODO: how does bind fail on windows? + CLOSE_NATIVE_SOCKET(sock); + return NATIVE_SOCKET_INVALID; + } + + if (listen(sock, backlog) < 0) { // TODO: how does listen fail on windows? + CLOSE_NATIVE_SOCKET(sock); + return NATIVE_SOCKET_INVALID; + } + + return sock; +} + +static void close_socket_pair(NATIVE_SOCKET a, NATIVE_SOCKET b) +{ +#ifdef _WIN32 + closesocket(a); + (void) b; +#else + close(a); + close(b); +#endif +} + +int socket_manager_init(SocketManager *sm, Socket *socks, + int num_socks) +{ + if (mutex_init(&sm->mutex) < 0) + return -1; + sm->plain_sock = NATIVE_SOCKET_INVALID; + sm->secure_sock = NATIVE_SOCKET_INVALID; + if (create_socket_pair(&sm->wait_sock, &sm->signal_sock) < 0) + return -1; + sm->at_least_one_secure_connect = false; + + sm->num_used = 0; + sm->max_used = num_socks; + sm->sockets = socks; + return 0; +} + +void socket_manager_free(SocketManager *sm) +{ + close_socket_pair(sm->wait_sock, sm->signal_sock); + + if (sm->secure_sock != NATIVE_SOCKET_INVALID) + server_secure_context_free(&sm->server_secure_context); + + if (sm->at_least_one_secure_connect) + client_secure_context_free(&sm->client_secure_context); + + if (sm->plain_sock != NATIVE_SOCKET_INVALID) + CLOSE_NATIVE_SOCKET(sm->plain_sock); + + if (sm->secure_sock != NATIVE_SOCKET_INVALID) + CLOSE_NATIVE_SOCKET(sm->secure_sock); + + mutex_free(&sm->mutex); +} + +int socket_manager_listen_tcp(SocketManager *sm, + String addr, Port port) +{ + if (sm->plain_sock != NATIVE_SOCKET_INVALID) + return -1; + + bool reuse_addr = false; + int backlog = 32; + sm->plain_sock = create_listen_socket(addr, port, reuse_addr, backlog); + if (sm->plain_sock == NATIVE_SOCKET_INVALID) + return -1; + + return 0; +} + +int socket_manager_listen_tls(SocketManager *sm, + String addr, Port port, String cert_file_name, + String key_file_name) +{ + if (sm->secure_sock != NATIVE_SOCKET_INVALID) + return -1; + + bool reuse_addr = false; + int backlog = 32; + sm->secure_sock = create_listen_socket(addr, port, reuse_addr, backlog); + if (sm->secure_sock == NATIVE_SOCKET_INVALID) + return -1; + + if (server_secure_context_init(&sm->server_secure_context) < 0) { + CLOSE_NATIVE_SOCKET(sm->secure_sock); + sm->secure_sock = NATIVE_SOCKET_INVALID; + return -1; + } + + return 0; +} + +int socket_manager_add_certificate(SocketManager *sm, + String domain, String cert_file, String key_file) +{ + if (sm->secure_sock == NATIVE_SOCKET_INVALID) + return -1; + + int ret = server_secure_context_add_certificate( + &sm->server_secure_context, domain, cert_file, key_file); + if (ret < 0) + return -1; + + return 0; +} + +static bool connect_pending(void) +{ +#ifdef _WIN32 + return WSAGetLastError() == WSAEWOULDBLOCK; +#else + return errno == EINPROGRESS; +#endif +} + +static bool +connect_failed_because_of_peer(void) +{ +#ifdef _WIN32 + int err = WSAGetLastError(); +#else + int err = errno; +#endif + return connect_failed_because_of_peer_2(err); +} + +static bool +connect_failed_because_of_peer_2(int err) +{ +#ifdef _WIN32 + return err == WSAECONNREFUSED + || err == WSAETIMEDOUT + || err == WSAENETUNREACH + || err == WSAEHOSTUNREACH; +#else + return err == ECONNREFUSED + || err == ETIMEDOUT + || err == ENETUNREACH + || err == EHOSTUNREACH; +#endif +} + +// This function moves the socket state machine +// to the next state until an I/O event would +// be required to continue. +static void socket_update(Socket *socket) +{ + // Each case of this switch encodes a state transition. + // If the evaluated case requires a given I/O event to + // continue, the loop will exit so that the caller can + // wait for that event. If the case can continue to a + // different case, the again flag is set, which causes + // a different case to be evaluated. + bool again; + do { + again = false; + switch (socket->state) { + case SOCKET_STATE_PENDING: + { + // This point may be reached because + // 1. The socket was just created by a connect + // operation. + // 2. Connecting to a host failed and now we + // need to try the next one. + // If (2) is true, we have some resources + // to clean up. + + if (socket->sock != NATIVE_SOCKET_INVALID) { + // This is not the first attempt + + CLOSE_NATIVE_SOCKET(socket->sock); + + socket->next_addr++; + if (socket->next_addr == socket->num_addr) { + assert(0); // TODO + } + } + AddressAndPort addr = socket->addrs[socket->next_addr]; + + int family = (addr.is_ipv4 ? AF_INET : AF_INET6); + NATIVE_SOCKET sock = socket(family, SOCK_STREAM, 0); + if (sock == NATIVE_SOCKET_INVALID) { + assert(0); // TODO + } + + if (set_socket_blocking(sock, false) < 0) { + assert(0); // TODO + } + + int ret; + if (addr.is_ipv4) { + struct sockaddr_in buf; + buf.sin_family = AF_INET; + buf.sin_port = htons(addr.port); + memset(&buf.sin_addr, &addr.ipv4, sizeof(IPv4)); + ret = connect(sock, (struct sockaddr*) &connect_buf, + sizeof(connect_buf)); + } else { + struct sockaddr_in6 buf; + buf.sin6_family = AF_INET6; + buf.sin6_port = htons(addr.port); + memset(&buf.sin6_addr, &addr.ipv6, sizeof(IPv6)); + ret = connect(sock, (struct sockaddr*) &connect_buf, + sizeof(connect_buf)); + } + + if (ret == 0) { + // Connect resolved immediately + socket->sock = sock; + socket->state = SOCKET_STATE_CONNECTED; + socket->events = 0; + again = true; + } else if (connect_pending()) { + // Connect is pending, which is expected + socket->sock = sock; + socket->state = SOCKET_STATE_CONNECTING; + socket->events = POLLOUT; + } else if (connect_failed_because_of_peer()) { + // Conenct failed due to the peer host + // We should try a different address. + socket->sock = sock; + socket->state = SOCKET_STATE_PENDING; + socket->events = 0; + again = true; + } else { + // An error occurred that we can't recover from + socket->sock = sock; + socket->state = SOCKET_STATE_DIED; + socket->events = 0; + again = true; + } + } + break; + + case SOCKET_STATE_CONNECTING: + { + // This point is reached when a connect() + // operation completes. + + int err = 0; + socklen_t len = sizeof(err); + if (getsockopt(socket->sock, SOL_SOCKET, SO_ERROR, (void*) &err, &len) < 0) { + assert(0); // TODO + } + + if (err == 0) { + // Connection succeded + socket->state = SOCKET_STATE_CONNECTED; + socket->events = 0; + again = true; + } else if (connect_failed_because_of_peer_2(err)) { + // Try the next address + socket->state = SOCKET_STATE_PENDING; + socket->events = 0; + again = true; + } else { + socket->state = SOCKET_STATE_DIED; + socket->events = 0; + } + } + break; + + case SOCKET_STATE_CONNECTED: + { + // We managed to connect to the peer. + // We can free the target array if it + // was allocated dynamically. + if (socket->num_addr > 1) + free(socket->addrs); + + if (!is_secure(socket)) { + socket->events = 0; + socket->state = SOCKET_STATE_ESTABLISHED_READY; + } else { +#ifdef HTTPS_ENABLED + assert(0); // TODO +#endif + } + } + break; + + case SOCKET_STATE_ESTABLISHED_WAIT: + socket->state = SOCKET_STATE_ESTABLISHED_READY; + socket->events = 0; + break; + + case SOCKET_STATE_SHUTDOWN: + { + if (!is_secure(socket)) { + socket->state = SOCKET_STATE_DIED; + socket->events = 0; + } else { +#ifdef HTTPS_ENABLED + assert(0); // TODO +#endif + } + } + break; + + default: + // Do nothing + break; + } + } while (again); +} + +int socket_manager_wakeup(SocketManager *sm) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + + // TODO + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return 0; +} + +static int socket_manager_register_events_nolock( + SocketManager *sm, struct pollfd *polled, int max_polled) +{ + // The poll array must be able to hold descriptors + // for a socket manager at full capacity. Note that + // other than having a number of connection sockets, + // the manager also needs 2 for the listeners and + // one for the wakeup self-pipe. + if (max_polled < sm->max_used+3) + return -1; + int num_polled = 0; + + polled[num_polled].fd = sm->wait_sock; + polled[num_polled].events = 0; + polled[num_polled].revents = 0; + num_polled++; + + // If the manager isn't at full capacity, monitor + // the listener sockets for incoming connections. + if (sm->num_used < sm->max_used) { + + if (sm->plain_sock != NATIVE_SOCKET_INVALID) { + polled[num_polled].fd = sm->plain_sock; + polled[num_polled].events = POLLIN; + polled[num_polled].revents = 0; + num_polled++; + } + + if (sm->secure_sock != NATIVE_SOCKET_INVALID) { + polled[num_polled].fd = sm->secure_sock; + polled[num_polled].events = POLLIN; + polled[num_polled].revents = 0; + num_polled++; + } + } + + // Iterate over each socket and register those that + // are waiting for I/O. If at least one socket that + // is ready to be processed exists, return an empty + // event registration list so that those entries can + // be processed immediately. + for (int i = 0, j = 0; j < sm->num_used; i++) { + Socket *s = &sm->sockets[i]; + if (s->state = SOCKET_STATE_FREE) + continue; + j++; + + if (s->state == SOCKET_STATE_DIED || s->state == SOCKET_STATE_ESTABLISHED_READY) + return 0; + + if (s->events) { + polled[num_polled].fd = s->sock; + polled[num_polled].events = s->events; + polled[num_polled].revents = 0; + num_polled++; + } + } + + return num_polled; +} + +int socket_manager_register_events(SocketManager *sm, + struct pollfd *polled, int max_polled) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + + int ret = socket_manager_register_events_nolock( + sm, polled, max_polled); + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return ret; +} + +static SocketHandle +socket_to_handle(SocketManager *sm, Socket *s) +{ + assert(0); // TODO +} + +static Socket *handle_to_socket(SocketManager *sm, SocketHandle handle) +{ + assert(0); // TODO +} + +static int socket_manager_translate_events_nolock( + SocketManager *sm, SocketEvent *events, int max_events, + struct pollfd *polled, int num_polled) +{ + int num_events = 0; + for (int i = 0; i < num_polled; i++) { + + if (polled[i].fd == sm->plain_sock || + polled[i].fd == sm->secure_sock) { + + // We only listen for input events from the listener + // if the socket pool isn't fool. This ensures that + // at least one socket struct is available. Note that + // it's still possible that we were at capacity MAX-1 + // and then got events from both the TCP and TCP/TLS + // listeners, causing one to be left witout a struct. + // This means we still need to check for full capacity. + // Fortunately, poll() is level-triggered, which means + // we'll handle this at the next iteration. + if (sm->num_used == sm->max_used) + continue; + + // Determine whether the event came from + // the encrypted listener or not. + bool secure = (polled[i].fd == sm->secure_sock); + + Socket *s = sm->sockets; + while (s->type != SOCKET_FREE) { + s++; + assert(s - sm->sockets < + sm->max_used); + } + + NATIVE_SOCKET sock = accept(polled[i].fd, NULL, NULL); + if (sock == NATIVE_SOCKET_INVALID) + continue; + + if (set_socket_blocking(sock, false) < 0) { + CLOSE_NATIVE_SOCKET(sock); + continue; + } + + s->state = SOCKET_STATE_ACCEPTED; + s->sock = sock; + s->events = 0; + s->user = NULL; + + socket_update(s); + if (s->state == SOCKET_STATE_DIED) { + CLOSE_NATIVE_SOCKET(sock); + s->state = SOCKET_STATE_FREE; + continue; + } + + pool->num_used++; + + } else if (polled[i].fd == sm->wait_sock) { + + // TODO: consume + + } else { + if (polled[i].revents) + socket_update(s); + } + } + + for (int i = 0, j = 0; j < sm->num_used; i++) { + Socket *s = &sm->sockets[i]; + if (s->state == SOCKET_FREE) + continue; + j++; + + if (num_events == max_events) + break; + + if (s->state == SOCKET_DIED) { + + events[num_events++] = (SocketEvent) { + SOCKET_EVENT_DISCONNECT, + SOCKET_HANDE_INVALID, + s->user + }; + + // Free resources associated to socket + s->state = SOCKET_FREE; + if (s->sock != NATIVE_SOCKET_INVALID) + CLOSE_NATIVE_SOCKET(s->sock); + if (s->sock == SOCKET_STATE_PENDING || + s->sock == SOCKET_STATE_CONNECTING) { + if (s->num_addr > 1) + free(s->addrs); + } + s->num_used--; + + } else if (s->state == SOCKET_STATE_ESTABLISHED_READY) { + events[num_events++] = (SocketEvent) { + SOCKET_EVENT_READY, + socket_to_handle(sm, s), + s->user + }; + } + } + + return num_events; +} + +int socket_manager_translate_events(SocketManager *sm, + SocketEvent *events, int max_events, struct pollfd *polled, + int num_polled) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + + socket_manager_translate_events_nolock( + sm, events, max_events, polled, num_polled); + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return 0; +} + +static int resolve_connect_targets(ConnectTarget *targets, + int num_targets, AddressAndPort *resolved, int max_resolved) +{ + int num_resolved = 0; + for (int i = 0; i < num_targets; i++) { + switch (targets[i].type) { + case CONNECT_TARGET_NAME: + { + char portstr[16]; + int len = snprintf(portstr, sizeof(portstr), "%u", port); + if (len < 0 || len >= (int) sizeof(portstr)) + return -1; + + struct addrinfo hints = {0}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + struct addrinfo *res = NULL; + int ret = getaddrinfo(pending_connect->hostname, portstr, &hints, &res); + if (ret != 0) + return -1; + + for (struct addrinfo *rp = res; rp; rp = rp->ai_next) { + if (rp->ai_family == AF_INET) { + IPv4 ipv4 = *(IPv4*) &((struct sockaddr_in*)rp->ai_addr)->sin_addr; + if (num_resolved < max_resolved) { + resolved[num_resolved].is_ipv4 = true; + resolved[num_resolved].ipv4 = ipv4; + resolved[num_resolved].port = targets[i].port; + num_resolved++; + } + } else if (rp->ai_family == AF_INET6) { + IPv6 ipv6 = *(IPv6*) &((struct sockaddr_in6*)rp->ai_addr)->sin6_addr; + if (num_resolved < max_resolved) { + resolved[num_resolved].is_ipv4 = false; + resolved[num_resolved].ipv4 = ipv6; + resolved[num_resolved].port = targets[i].port; + num_resolved++; + } + } + } + + freeaddrinfo(res); + } + break; + case CONNECT_TARGET_IPV4: + if (num_resolved < max_resolved) { + resolved[num_resolved].is_ipv4 = true; + resolved[num_resolved].ipv4 = targets[i].ipv4; + resolved[num_resolved].port = targets[i].port; + num_resolved++; + } + break; + case CONNECT_TARGET_IPV6: + if (num_resolved < max_resolved) { + resolved[num_resolved].is_ipv4 = false; + resolved[num_resolved].ipv6 = targets[i].ipv6; + resolved[num_resolved].port = targets[i].port; + num_resolved++; + } + break; + } + } + return num_resolved; +} + +int socket_connect(SocketManager *sm, int num_targets, + ConnectTarget *targets, bool secure, void *user) +{ + if (sm->num_used == sm->max_used) + return -1; + + AddressAndPort resolved[MAX_CONNECT_TARGETS]; + int num_resolved = resolve_connect_targets( + targets, num_targets, resolved, MAX_CONNECT_TARGETS); + + if (num_resolved <= 0) + return -1; + + Socket *s = sm->sockets; + while (s->type != SOCKET_FREE) { + s++; + assert(s - sm->sockets < + sm->max_used); + } + + if (num_resolved == 1) { + s->num_addr = 1; + s->next_addr = 0; + s->addr = resolved[0]; + } else { + s->num_addr = num_resolved; + s->next_addr = 0; + s->addrs = malloc(num_resolved * sizeof(AddressAndPort)); + if (s->addrs == NULL) + return -1; + for (int i = 0; i < num_resolved; i++) + s->addrs[i] = resolved[i]; + } + + s->type = SOCKET_STATE_PENDING; + s->sock = NATIVE_SOCKET_INVALID; + s->user = user; + s->num_used++; + return 0; +} + +static int socket_recv_nolock(SocketManager *sm, SocketHandle handle, + char *dst, int max) +{ + Socket *s = handle_to_socket(sm, handle); + if (s == NULL) + return 0; + + if (s->state != SOCKET_STATE_ESTABLISHED_READY) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + return 0; + } + + if (!is_secure(s)) { + int ret = recv(s->sock, dst, max, 0); + if (ret == 0) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + } else if (ret < 0) { + if (would_block()) { + s->state = SOCKET_STATE_ESTABLISHED_WAIT; + s->events = POLLIN; + } else if (!interrupted()) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + } + ret = 0; + } + return 0; + } else { +#ifdef HTTPS_ENABLED + int ret = SSL_read(s->ssl, dst, max); + if (ret <= 0) { + int err = SSL_get_error(s->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + s->state = SOCKET_STATE_ESTABLISHED_WAIT; + s->events = POLLIN; + } else if (err == SSL_ERROR_WANT_WRITE) { + s->state = SOCKET_STATE_ESTABLISHED_WAIT; + s->events = POLLOUT; + } else { + s->state = SOCKET_STATE_DIED; + s->events = 0; + } + ret = 0; + } + return ret; +#endif + } +} + +int socket_recv(SocketManager *sm, SocketHandle handle, + char *dst, int max) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + + int ret = socket_recv_nolock(sm, handle, dst, max); + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return ret; +} + +static int socket_send_nolock(SocketManager *sm, SocketHandle handle, + char *src, int len) +{ + Socket *s = handle_to_socket(sm, handle); + if (s == NULL) + return 0; + + if (s->state != SOCKET_STATE_ESTABLISHED_READY) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + return 0; + } + + if (!socket_secure(s)) { + int ret = send(s->sock, src, len, 0); + if (ret < 0) { + if (would_block()) { + s->state = SOCKET_STATE_ESTABLISHED_WAIT; + s->events = POLLOUT; + } else if (!interrupted()) { + s->state = SOCKET_DIED; + s->events = 0; + } + ret = 0; + } + return ret; + } else { +#ifdef HTTPS_ENABLED + int ret = SSL_write(s->ssl, src, len); + if (ret <= 0) { + int err = SSL_get_error(s->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + s->state = SOCKET_STATE_ESTABLISHED_WAIT; + s->events = POLLIN; + } else if (err == SSL_ERROR_WANT_WRITE) { + s->state = SOCKET_STATE_ESTABLISHED_WAIT; + s->events = POLLOUT; + } else { + s->state = SOCKET_STATE_DIED; + s->events = 0; + } + ret = 0; + } + return ret; +#endif + } +} + +int socket_send(SocketManager *sm, SocketHandle handle, + char *src, int len) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + + int ret = socket_send_nolock(sm, handle, src, len); + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return ret; +} + +void socket_close(SocketManager *sm, SocketHandle handle) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + + // TODO: maybe we don't want to always set to SHUTDOWN. What if the socket is DIED for instance? + s->state = SOCKET_STATE_SHUTDOWN; + s->events = 0; + socket_update(s); + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return 0; +} + +int socket_is_secure(SocketManager *sm, SocketHandle handle) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + +#ifdef HTTPS_ENABLED + Socket *s = handle_to_socket(sm, handle); + + int ret; + if (s == NULL) + ret = -1; + else { + ret = (s->ssl != NULL); + } +#else + int ret = 0; +#endif + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return ret; +} + +int socket_set_user(SocketManager *sm, SocketHandle handle) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + + Socket *s = handle_to_socket(sm, handle); + if (s == NULL) + ret = -1; + else + s->user = user; + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return ret; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// src/thread.c +//////////////////////////////////////////////////////////////////////////////////////// + +int mutex_init(Mutex *mutex) +{ +#ifdef _WIN32 + InitializeCriticalSection(mutex); // TODO: mock? + return 0; +#else + if (pthread_mutex_init(mutex, NULL)) // TODO: mock + return -1; + return 0; +#endif +} + +int mutex_free(Mutex *mutex) +{ +#ifdef _WIN32 + DeleteCriticalSection(mutex); // TODO: mock? + return 0; +#else + if (pthread_mutex_destroy(mutex)) // TODO: mock + return -1; + return 0; +#endif +} + +int mutex_lock(Mutex *mutex) +{ +#ifdef _WIN32 + EnterCriticalSection(mutex); // TODO: mock? + return 0; +#else + if (pthread_mutex_lock(mutex)) // TODO: mock + return -1; + return 0; +#endif +} + +int mutex_unlock(Mutex *mutex) +{ +#ifdef _WIN32 + LeaveCriticalSection(mutex); // TODO: mock? + return 0; +#else + if (pthread_mutex_unlock(mutex)) // TODO: mock + return -1; + return 0; +#endif +} + +//////////////////////////////////////////////////////////////////////////////////////// +// src/byte_queue.c +//////////////////////////////////////////////////////////////////////////////////////// + +void byte_queue_init(ByteQueue *queue, uint32_t limit) +{ + queue->flags = 0; + queue->head = 0; + queue->size = 0; + queue->used = 0; + queue->curs = 0; + queue->limit = limit; + queue->data = NULL; + queue->read_target = NULL; +} + +// Deinitialize the queue +void byte_queue_free(ByteQueue *queue) +{ + if (queue->read_target) { + if (queue->read_target != queue->data) + free(queue->read_target); + queue->read_target = NULL; + queue->read_target_size = 0; + } + + free(queue->data); + queue->data = NULL; +} + +int byte_queue_error(ByteQueue *queue) +{ + return queue->flags & BYTE_QUEUE_ERROR; +} + +int byte_queue_empty(ByteQueue *queue) +{ + return queue->used == 0; +} + +int byte_queue_full(ByteQueue *queue) +{ + return queue->used == queue->limit; +} + +ByteView byte_queue_read_buf(ByteQueue *queue) +{ + if (queue->flags & BYTE_QUEUE_ERROR) + return (ByteView) {NULL, 0}; + + assert((queue->flags & BYTE_QUEUE_READ) == 0); + queue->flags |= BYTE_QUEUE_READ; + queue->read_target = queue->data; + queue->read_target_size = queue->size; + + if (queue->data == NULL) + return (ByteView) {NULL, 0}; + + return (ByteView) { queue->data + queue->head, queue->used }; +} + +void byte_queue_read_ack(ByteQueue *queue, uint32_t num) +{ + if (queue->flags & BYTE_QUEUE_ERROR) + return; + + if ((queue->flags & BYTE_QUEUE_READ) == 0) + return; + + queue->flags &= ~BYTE_QUEUE_READ; + + assert((uint32_t) num <= queue->used); + queue->head += (uint32_t) num; + queue->used -= (uint32_t) num; + queue->curs += (uint32_t) num; + + if (queue->read_target) { + if (queue->read_target != queue->data) + free(queue->read_target); + queue->read_target = NULL; + queue->read_target_size = 0; + } +} + +ByteView byte_queue_write_buf(ByteQueue *queue) +{ + if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL) + return (ByteView) {NULL, 0}; + + assert((queue->flags & BYTE_QUEUE_WRITE) == 0); + queue->flags |= BYTE_QUEUE_WRITE; + + return (ByteView) { + queue->data + (queue->head + queue->used), + queue->size - (queue->head + queue->used), + }; +} + +void byte_queue_write_ack(ByteQueue *queue, uint32_t num) +{ + if (queue->flags & BYTE_QUEUE_ERROR) + return; + + if ((queue->flags & BYTE_QUEUE_WRITE) == 0) + return; + + queue->flags &= ~BYTE_QUEUE_WRITE; + queue->used += num; +} + +int byte_queue_write_setmincap(ByteQueue *queue, uint32_t mincap) +{ + // Sticky error + if (queue->flags & BYTE_QUEUE_ERROR) + return 0; + + // In general, the queue's contents look like this: + // + // size + // v + // [___xxxxxxxxxxxx________] + // ^ ^ ^ + // 0 head head + used + // + // This function needs to make sure that at least [mincap] + // bytes are available on the right side of the content. + // + // We have 3 cases: + // + // 1) If there is enough memory already, this function doesn't + // need to do anything. + // + // 2) If there isn't enough memory on the right but there is + // enough free memory if we cound the left unused region, + // then the content is moved back to the + // start of the buffer. + // + // 3) If there isn't enough memory considering both sides, this + // function needs to allocate a new buffer. + // + // If there are pending read or write operations, the application + // is holding pointers to the buffer, so we need to make sure + // to not invalidate them. The only real problem is pending reads + // since this function can only be called before starting a write + // opearation. + // + // To avoid invalidating the read pointer when we allocate a new + // buffer, we don't free the old buffer. Instead, we store the + // pointer in the "old" field so that the read ack function can + // free it. + // + // To avoid invalidating the pointer when we are moving back the + // content since there is enough memory at the start of the buffer, + // we just avoid that. Even if there is enough memory considering + // left and right free regions, we allocate a new buffer. + + assert((queue->flags & BYTE_QUEUE_WRITE) == 0); + + uint32_t total_free_space = queue->size - queue->used; + uint32_t free_space_after_data = queue->size - queue->used - queue->head; + + int moved = 0; + if (free_space_after_data < mincap) { + + if (total_free_space < mincap || (queue->read_target == queue->data)) { + // Resize required + + if (queue->used + mincap > queue->limit) { + queue->flags |= BYTE_QUEUE_ERROR; + return 0; + } + + uint32_t size; + if (queue->size > UINT32_MAX / 2) + size = UINT32_MAX; + else + size = 2 * queue->size; + + if (size < queue->used + mincap) + size = queue->used + mincap; + + if (size > queue->limit) + size = queue->limit; + + uint8_t *data = malloc(size); + if (!data) { + queue->flags |= BYTE_QUEUE_ERROR; + return 0; + } + + if (queue->used > 0) + memcpy(data, queue->data + queue->head, queue->used); + + if (queue->read_target != queue->data) + free(queue->data); + + queue->data = data; + queue->head = 0; + queue->size = size; + + } else { + // Move required + memmove(queue->data, queue->data + queue->head, queue->used); + queue->head = 0; + } + + moved = 1; + } + + return moved; +} + +void byte_queue_write(ByteQueue *queue, void *ptr, uint32_t len) +{ + byte_queue_write_setmincap(queue, len); + ByteView dst = byte_queue_write_buf(queue); + if (dst.ptr) { + memcpy(dst.ptr, ptr, len); + byte_queue_write_ack(queue, len); + } +} + +ByteQueueOffset byte_queue_offset(ByteQueue *queue) +{ + if (queue->flags & BYTE_QUEUE_ERROR) + return (ByteQueueOffset) { 0 }; + return (ByteQueueOffset) { queue->curs + queue->used }; +} + +void byte_queue_patch(ByteQueue *queue, ByteQueueOffset off, + void *src, uint32_t len) +{ + if (queue->flags & BYTE_QUEUE_ERROR) + return; + + // Check that the offset is in range + assert(off >= queue->curs && off - queue->curs < queue->used); + + // Check that the length is in range + assert(len <= queue->used - (off - queue->curs)); + + // Perform the patch + uint8_t *dst = queue->data + queue->head + (off - queue->curs); + memcpy(dst, src, len); +} + +uint32_t byte_queue_size_from_offset(ByteQueue *queue, ByteQueueOffset off) +{ + return queue->curs + queue->used - off; +} + +void byte_queue_remove_from_offset(ByteQueue *queue, ByteQueueOffset offset) +{ + if (queue->flags & BYTE_QUEUE_ERROR) + return; + + uint64_t num = (queue->curs + queue->used) - offset; + assert(num <= queue->used); + + queue->used -= num; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// src/cert.c +//////////////////////////////////////////////////////////////////////////////////////// + +#ifdef HTTPS_ENABLED + +static EVP_PKEY *generate_rsa_key_pair(int key_bits) +{ + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (!ctx) + return NULL; + + if (EVP_PKEY_keygen_init(ctx) <= 0) { + EVP_PKEY_CTX_free(ctx); + return NULL; + } + + if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, key_bits) <= 0) { + EVP_PKEY_CTX_free(ctx); + return NULL; + } + + EVP_PKEY *pkey = NULL; + if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { + EVP_PKEY_CTX_free(ctx); + return NULL; + } + + EVP_PKEY_CTX_free(ctx); + return pkey; +} + +static X509 *create_certificate(EVP_PKEY *pkey, HTTP_String C, HTTP_String O, HTTP_String CN, int days) +{ + X509 *x509 = X509_new(); + if (!x509) + return NULL; + + // Set version (version 3) + X509_set_version(x509, 2); + + // Set serial number + ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); + + // Set validity period + X509_gmtime_adj(X509_get_notBefore(x509), 0); + X509_gmtime_adj(X509_get_notAfter(x509), 31536000L * days); // days * seconds_per_year + + // Set public key + X509_set_pubkey(x509, pkey); + + // Set subject name + X509_NAME *name = X509_get_subject_name(x509); + X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char*) C.ptr, C.len, -1, 0); + X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char*) O.ptr, O.len, -1, 0); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*) CN.ptr, CN.len, -1, 0); + + // Set issuer name (same as subject for self-signed) + X509_set_issuer_name(x509, name); + + if (!X509_sign(x509, pkey, EVP_sha256())) { + X509_free(x509); + return NULL; + } + + return x509; +} + +static int save_private_key(EVP_PKEY *pkey, HTTP_String file) +{ + char copy[1<<10]; + if (file.len >= (int) sizeof(copy)) + return -1; + memcpy(copy, file.ptr, file.len); + copy[file.len] = '\0'; + + FILE *fp = fopen(copy, "wb"); + if (!fp) + return -1; + + // Write private key in PEM format + if (!PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL)) { + fclose(fp); + return -1; + } + + fclose(fp); + return 0; +} + +static int save_certificate(X509 *x509, HTTP_String file) +{ + char copy[1<<10]; + if (file.len >= (int) sizeof(copy)) + return -1; + memcpy(copy, file.ptr, file.len); + copy[file.len] = '\0'; + + FILE *fp = fopen(copy, "wb"); + if (!fp) + return -1; + + // Write certificate in PEM format + if (!PEM_write_X509(fp, x509)) { + fclose(fp); + return -1; + } + + fclose(fp); + return 0; +} + +int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, + HTTP_String cert_file, HTTP_String key_file) +{ + EVP_PKEY *pkey = generate_rsa_key_pair(2048); + if (pkey == NULL) + return -1; + + X509 *x509 = create_certificate(pkey, C, O, CN, 1); + if (x509 == NULL) { + EVP_PKEY_free(pkey); + return -1; + } + + if (save_private_key(pkey, key_file) < 0) { + X509_free(x509); + EVP_PKEY_free(pkey); + return -1; + } + + if (save_certificate(x509, cert_file) < 0) { + X509_free(x509); + EVP_PKEY_free(pkey); + return -1; + } + + X509_free(x509); + EVP_PKEY_free(pkey); + return 0; +} + +#else + +int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, + HTTP_String cert_file, HTTP_String key_file) +{ + (void) C; + (void) O; + (void) CN; + (void) cert_file; + (void) key_file; + return -1; +} + +#endif + +//////////////////////////////////////////////////////////////////////////////////////// +// src/client.c +//////////////////////////////////////////////////////////////////////////////////////// + +int http_client_init(HTTP_Client *client) +{ + client->num_conns = 0; + for (int i = 0; i < HTTP_CLIENT_CAPACITY; i++) + client->conns[i].state = HTTP_CLIENT_CONN_FREE; + + client->num_ready = 0; + client->ready_head = 0; + + if (socket_manager_init(&client->sockets, + client->socket_pool, HTTP_CLIENT_CAPACITY) < 0) + return -1; + return 0; +} + +void http_client_free(HTTP_Client *client) +{ + socket_manager_free(&client->sockets); + + for (int i = 0, j = 0; j < client->num_conns; i++) { + HTTP_ClientConn *conn = &client->conns[i]; + if (conn->state == HTTP_CLIENT_CONN_FREE) + continue; + j++; + + http_client_conn_free(conn); + } +} + +int http_client_wakeup(HTTP_Client *client) +{ + if (socket_manager_wakeup(&client->sockets) < 0) + return -1; + return 0; +} + +int http_client_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder) +{ + // TODO +} + +void http_request_builder_line(HTTP_RequestBuilder builder, xxxx) +{ + // TODO +} + +void http_request_builder_header(HTTP_RequestBuilder builder, String str) +{ + // TODO +} + +void http_request_builder_body(HTTP_RequestBuilder builder, String str) +{ + // TODO +} + +int http_request_builder_send(HTTP_RequestBuilder builder) +{ + // TODO +} + +int http_client_register_events(HTTP_Client *client, + struct pollfd *polled, int max_polled) +{ + return socket_manager_register_events( + &client->sockets, polled, max_polled); +} + +int http_client_process_events(HTTP_Client *client, + struct pollfd *polled, int num_polled) +{ + SocketEvent events[HTTP_CLIENT_CAPACITY]; + int num_events = socket_manager_translate_events( + &client->sockets, polled, num_polled); + + for (int i = 0; i < num_events; i++) { + + if (events[i].type == SOCKET_EVENT_DISCONNECT) { + + // TODO + + } else if (events[i].type == SOCKET_EVENT_READY) { + + // TODO + } + + // TODO + } + + return 0; +} + +bool http_client_next_response(HTTP_Client *client, + HTTP_Response **response); +{ + if (server->num_ready == 0) + return false; + + HTTP_ClientConn *conn = &client->conns[client->ready_head]; + client->ready_head = (client->ready_head + 1) % HTTP_CLIENT_CAPACITY; + client->num_ready--; + + assert(conn->state == HTTP_CLIENT_CONN_COMPLETE); + *response = &conn->response; + return true; +} + +void http_free_response(HTTP_Response *res) +{ + // TODO +} + +//////////////////////////////////////////////////////////////////////////////////////// +// src/parse.c +//////////////////////////////////////////////////////////////////////////////////////// // From RFC 9112 // request-target = origin-form // / absolute-form @@ -881,7 +2168,7 @@ static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment) s->cur - start, }; - if (s->cur == s->len || s->src[s->cur] != ':') + if (s->cur == s->len || s->src[s->cur] != ':') return -1; // ERROR: Missing ':' after scheme s->cur++; } @@ -944,7 +2231,7 @@ static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment) static int parse_authority_form(Scanner *s, HTTP_Host *host, int *port) { int ret; - + ret = parse_host(s, host); if (ret < 0) return ret; @@ -1025,7 +2312,7 @@ static int parse_request_target(Scanner *s, HTTP_URL *url) bool consume_str(Scanner *scan, HTTP_String token) { - HTTP_ASSERT(token.len > 0); + assert(token.len > 0); if (token.len > scan->len - scan->cur) return false; @@ -1059,7 +2346,7 @@ static int parse_headers(Scanner *s, HTTP_Header *headers, int max_headers) // obs-text = %x80-FF int start; - + if (s->cur == s->len || !is_tchar(s->src[s->cur])) return -1; // ERROR start = s->cur; @@ -1107,7 +2394,7 @@ parse_transfer_encoding(HTTP_String src, TransferEncodingOption *dst, int max) int num = 0; for (;;) { - + CONSUME_OPTIONAL_SEQUENCE(&s, is_space); TransferEncodingOption opt; @@ -1671,12 +2958,12 @@ int http_get_param_i(HTTP_String body, HTTP_String str) bool http_match_host(HTTP_Request *req, HTTP_String domain, int port) { int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Host")); - HTTP_ASSERT(idx != -1); // Requests without the host header are always rejected + assert(idx != -1); // Requests without the host header are always rejected char tmp[1<<8]; if (port > -1 && port != 80) { int ret = snprintf(tmp, sizeof(tmp), "%.*s:%d", domain.len, domain.ptr, port); - HTTP_ASSERT(ret > 0); + assert(ret > 0); domain = (HTTP_String) { tmp, ret }; } @@ -1685,3439 +2972,330 @@ bool http_match_host(HTTP_Request *req, HTTP_String domain, int port) } //////////////////////////////////////////////////////////////////////////////////////// -// src/engine.c +// src/server.c //////////////////////////////////////////////////////////////////////////////////////// -#line 1 "src/engine.c" -#include -#include -#include -#include -#include -#include // TODO: remove some of these headers -#include -#include -#include -#include - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#include "engine.h" -#endif - -// This is the implementation of a byte queue useful -// for systems that need to process engs of bytes. -// -// It features sticky errors, a zero-copy interface, -// and a safe mechanism to patch previously written -// bytes. -// -// Only up to 4GB of data can be stored at once. - -enum { - BYTE_QUEUE_ERROR = 1 << 0, - BYTE_QUEUE_READ = 1 << 1, - BYTE_QUEUE_WRITE = 1 << 2, -}; - -static void* -callback_malloc(HTTP_ByteQueue *queue, int len) +int http_server_init(HTTP_Server *server) { - return queue->memfunc(HTTP_MEMFUNC_MALLOC, NULL, len, queue->memfuncdata); -} + server->num_conns = 0; + for (int i = 0; i < HTTP_SERVER_CAPACITY; i++) + server->conns[i].state = HTTP_SERVER_CONN_FREE; -static void -callback_free(HTTP_ByteQueue *queue, void *ptr, int len) -{ - queue->memfunc(HTTP_MEMFUNC_FREE, ptr, len, queue->memfuncdata); -} + server->num_ready = 0; + server->ready_head = 0; -// Initialize the queue -static void -byte_queue_init(HTTP_ByteQueue *queue, unsigned int limit, HTTP_MemoryFunc memfunc, void *memfuncdata) -{ - queue->flags = 0; - queue->head = 0; - queue->size = 0; - queue->used = 0; - queue->curs = 0; - queue->limit = limit; - queue->data = NULL; - queue->read_target = NULL; - queue->memfunc = memfunc; - queue->memfuncdata = memfuncdata; -} - -// Deinitialize the queue -static void -byte_queue_free(HTTP_ByteQueue *queue) -{ - if (queue->read_target) { - if (queue->read_target != queue->data) - callback_free(queue, queue->read_target, queue->read_target_size); - queue->read_target = NULL; - queue->read_target_size = 0; - } - - callback_free(queue, queue->data, queue->size); - queue->data = NULL; -} - -static int -byte_queue_error(HTTP_ByteQueue *queue) -{ - return queue->flags & BYTE_QUEUE_ERROR; -} - -static int -byte_queue_empty(HTTP_ByteQueue *queue) -{ - return queue->used == 0; -} - -// Start a read operation on the queue. -// -// This function returnes the pointer to the memory region containing the bytes -// to read. Callers can't read more than [*len] bytes from it. To complete the -// read, the [byte_queue_read_ack] function must be called with the number of -// bytes that were acknowledged by the caller. -// -// Note: -// - You can't have more than one pending read. -static char* -byte_queue_read_buf(HTTP_ByteQueue *queue, int *len) -{ - if (queue->flags & BYTE_QUEUE_ERROR) { - *len = 0; - return NULL; - } - - HTTP_ASSERT((queue->flags & BYTE_QUEUE_READ) == 0); - queue->flags |= BYTE_QUEUE_READ; - queue->read_target = queue->data; - queue->read_target_size = queue->size; - - *len = queue->used; - if (queue->data == NULL) - return NULL; - return queue->data + queue->head; -} - -// Complete a previously started operation on the queue. -static void -byte_queue_read_ack(HTTP_ByteQueue *queue, int num) -{ - HTTP_ASSERT(num >= 0); - - if (queue->flags & BYTE_QUEUE_ERROR) - return; - - if ((queue->flags & BYTE_QUEUE_READ) == 0) - return; - - queue->flags &= ~BYTE_QUEUE_READ; - - HTTP_ASSERT((unsigned int) num <= queue->used); - queue->head += (unsigned int) num; - queue->used -= (unsigned int) num; - queue->curs += (unsigned int) num; - - if (queue->read_target) { - if (queue->read_target != queue->data) - callback_free(queue, queue->read_target, queue->read_target_size); - queue->read_target = NULL; - queue->read_target_size = 0; - } -} - -static char* -byte_queue_write_buf(HTTP_ByteQueue *queue, int *cap) -{ - if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL) { - *cap = 0; - return NULL; - } - - HTTP_ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0); - queue->flags |= BYTE_QUEUE_WRITE; - - unsigned int ucap = queue->size - (queue->head + queue->used); - if (ucap > INT_MAX) ucap = INT_MAX; - - *cap = (int) ucap; - return queue->data + (queue->head + queue->used); -} - -static void -byte_queue_write_ack(HTTP_ByteQueue *queue, int num) -{ - HTTP_ASSERT(num >= 0); - - if (queue->flags & BYTE_QUEUE_ERROR) - return; - - if ((queue->flags & BYTE_QUEUE_WRITE) == 0) - return; - - queue->flags &= ~BYTE_QUEUE_WRITE; - queue->used += (unsigned int) num; -} - -// Sets the minimum capacity for the next write operation -// and returns 1 if the content of the queue was moved, else -// 0 is returned. -// -// You must not call this function while a write is pending. -// In other words, you must do this: -// -// byte_queue_write_setmincap(queue, mincap); -// dst = byte_queue_write_buf(queue, &cap); -// ... -// byte_queue_write_ack(num); -// -// And NOT this: -// -// dst = byte_queue_write_buf(queue, &cap); -// byte_queue_write_setmincap(queue, mincap); <-- BAD -// ... -// byte_queue_write_ack(num); -// -static int -byte_queue_write_setmincap(HTTP_ByteQueue *queue, int mincap) -{ - HTTP_ASSERT(mincap >= 0); - unsigned int umincap = (unsigned int) mincap; - - // Sticky error - if (queue->flags & BYTE_QUEUE_ERROR) - return 0; - - // In general, the queue's contents look like this: - // - // size - // v - // [___xxxxxxxxxxxx________] - // ^ ^ ^ - // 0 head head + used - // - // This function needs to make sure that at least [mincap] - // bytes are available on the right side of the content. - // - // We have 3 cases: - // - // 1) If there is enough memory already, this function doesn't - // need to do anything. - // - // 2) If there isn't enough memory on the right but there is - // enough free memory if we cound the left unused region, - // then the content is moved back to the - // start of the buffer. - // - // 3) If there isn't enough memory considering both sides, this - // function needs to allocate a new buffer. - // - // If there are pending read or write operations, the application - // is holding pointers to the buffer, so we need to make sure - // to not invalidate them. The only real problem is pending reads - // since this function can only be called before starting a write - // opearation. - // - // To avoid invalidating the read pointer when we allocate a new - // buffer, we don't free the old buffer. Instead, we store the - // pointer in the "old" field so that the read ack function can - // free it. - // - // To avoid invalidating the pointer when we are moving back the - // content since there is enough memory at the start of the buffer, - // we just avoid that. Even if there is enough memory considering - // left and right free regions, we allocate a new buffer. - - HTTP_ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0); - - unsigned int total_free_space = queue->size - queue->used; - unsigned int free_space_after_data = queue->size - queue->used - queue->head; - - int moved = 0; - if (free_space_after_data < umincap) { - - if (total_free_space < umincap || (queue->read_target == queue->data)) { - // Resize required - - if (queue->used + umincap > queue->limit) { - queue->flags |= BYTE_QUEUE_ERROR; - return 0; - } - - unsigned int size; - if (queue->size > UINT32_MAX / 2) - size = UINT32_MAX; - else - size = 2 * queue->size; - - if (size < queue->used + umincap) - size = queue->used + umincap; - - if (size > queue->limit) - size = queue->limit; - - char *data = callback_malloc(queue, size); - if (!data) { - queue->flags |= BYTE_QUEUE_ERROR; - return 0; - } - - if (queue->used > 0) - memcpy(data, queue->data + queue->head, queue->used); - - if (queue->read_target != queue->data) - callback_free(queue, queue->data, queue->size); - - queue->data = data; - queue->head = 0; - queue->size = size; - - } else { - // Move required - memmove(queue->data, queue->data + queue->head, queue->used); - queue->head = 0; - } - - moved = 1; - } - - return moved; -} - -static HTTP_ByteQueueOffset -byte_queue_offset(HTTP_ByteQueue *queue) -{ - if (queue->flags & BYTE_QUEUE_ERROR) - return (HTTP_ByteQueueOffset) { 0 }; - return (HTTP_ByteQueueOffset) { queue->curs + queue->used }; -} - -static unsigned int -byte_queue_size_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off) -{ - return queue->curs + queue->used - off; -} - -static void -byte_queue_patch(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off, - char *src, unsigned int len) -{ - if (queue->flags & BYTE_QUEUE_ERROR) - return; - - // Check that the offset is in range - HTTP_ASSERT(off >= queue->curs && off - queue->curs < queue->used); - - // Check that the length is in range - HTTP_ASSERT(len <= queue->used - (off - queue->curs)); - - // Perform the patch - char *dst = queue->data + queue->head + (off - queue->curs); - memcpy(dst, src, len); -} - -static void -byte_queue_remove_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset offset) -{ - if (queue->flags & BYTE_QUEUE_ERROR) - return; - - unsigned long long num = (queue->curs + queue->used) - offset; - HTTP_ASSERT(num <= queue->used); - - queue->used -= num; -} - -static void -byte_queue_write(HTTP_ByteQueue *queue, const char *str, int len) -{ - if (str == NULL) str = ""; - if (len < 0) len = strlen(str); - - int cap; - byte_queue_write_setmincap(queue, len); - char *dst = byte_queue_write_buf(queue, &cap); - if (dst) memcpy(dst, str, len); - byte_queue_write_ack(queue, len); -} - -static void -byte_queue_write_fmt2(HTTP_ByteQueue *queue, const char *fmt, va_list args) -{ - if (queue->flags & BYTE_QUEUE_ERROR) - return; - - va_list args2; - va_copy(args2, args); - - int cap; - byte_queue_write_setmincap(queue, 128); - char *dst = byte_queue_write_buf(queue, &cap); - - int len = vsnprintf(dst, cap, fmt, args); - if (len < 0) { - queue->flags |= BYTE_QUEUE_ERROR; - va_end(args2); - return; - } - - if (len > cap) { - byte_queue_write_ack(queue, 0); - byte_queue_write_setmincap(queue, len+1); - dst = byte_queue_write_buf(queue, &cap); - vsnprintf(dst, cap, fmt, args2); - } - - byte_queue_write_ack(queue, len); - - va_end(args2); -} - -static void -byte_queue_write_fmt(HTTP_ByteQueue *queue, const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - byte_queue_write_fmt2(queue, fmt, args); - va_end(args); -} - -#define TEN_SPACES " " - -void http_engine_init(HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata) -{ - if (client) - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_URL; - else - eng->state = HTTP_ENGINE_STATE_SERVER_RECV_BUF; - - eng->closing = 0; - eng->numexch = 0; - - byte_queue_init(&eng->input, 1<<20, memfunc, memfuncdata); - byte_queue_init(&eng->output, 1<<20, memfunc, memfuncdata); -} - -void http_engine_free(HTTP_Engine *eng) -{ - byte_queue_free(&eng->input); - byte_queue_free(&eng->output); - eng->state = HTTP_ENGINE_STATE_NONE; -} - -void http_engine_close(HTTP_Engine *eng) -{ - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - else - eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; -} - -HTTP_EngineState http_engine_state(HTTP_Engine *eng) -{ - return eng->state; -} - -const char* http_engine_statestr(HTTP_EngineState state) { // TODO: remove - switch (state) { - case HTTP_ENGINE_STATE_NONE: return "NONE"; - case HTTP_ENGINE_STATE_CLIENT_PREP_URL: return "CLIENT_PREP_URL"; - case HTTP_ENGINE_STATE_CLIENT_PREP_HEADER: return "CLIENT_PREP_HEADER"; - case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF: return "CLIENT_PREP_BODY_BUF"; - case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK: return "CLIENT_PREP_BODY_ACK"; - case HTTP_ENGINE_STATE_CLIENT_PREP_ERROR: return "CLIENT_PREP_ERROR"; - case HTTP_ENGINE_STATE_CLIENT_SEND_BUF: return "CLIENT_SEND_BUF"; - case HTTP_ENGINE_STATE_CLIENT_SEND_ACK: return "CLIENT_SEND_ACK"; - case HTTP_ENGINE_STATE_CLIENT_RECV_BUF: return "CLIENT_RECV_BUF"; - case HTTP_ENGINE_STATE_CLIENT_RECV_ACK: return "CLIENT_RECV_ACK"; - case HTTP_ENGINE_STATE_CLIENT_READY: return "CLIENT_READY"; - case HTTP_ENGINE_STATE_CLIENT_CLOSED: return "CLIENT_CLOSED"; - case HTTP_ENGINE_STATE_SERVER_RECV_BUF: return "SERVER_RECV_BUF"; - case HTTP_ENGINE_STATE_SERVER_RECV_ACK: return "SERVER_RECV_ACK"; - case HTTP_ENGINE_STATE_SERVER_PREP_STATUS: return "SERVER_PREP_STATUS"; - case HTTP_ENGINE_STATE_SERVER_PREP_HEADER: return "SERVER_PREP_HEADER"; - case HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF: return "SERVER_PREP_BODY_BUF"; - case HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK: return "SERVER_PREP_BODY_ACK"; - case HTTP_ENGINE_STATE_SERVER_PREP_ERROR: return "SERVER_PREP_ERROR"; - case HTTP_ENGINE_STATE_SERVER_SEND_BUF: return "SERVER_SEND_BUF"; - case HTTP_ENGINE_STATE_SERVER_SEND_ACK: return "SERVER_SEND_ACK"; - case HTTP_ENGINE_STATE_SERVER_CLOSED: return "SERVER_CLOSED"; - default: return "UNKNOWN"; - } -} - -char *http_engine_recvbuf(HTTP_Engine *eng, int *cap) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_RECV_BUF) == 0) { - *cap = 0; - return NULL; - } - - eng->state &= ~HTTP_ENGINE_STATEBIT_RECV_BUF; - eng->state |= HTTP_ENGINE_STATEBIT_RECV_ACK; - - byte_queue_write_setmincap(&eng->input, 1<<9); - if (byte_queue_error(&eng->input)) { - *cap = 0; - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - else - eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; - return NULL; - } - - return byte_queue_write_buf(&eng->input, cap); -} - -static int -should_keep_alive(HTTP_Engine *eng) -{ - HTTP_ASSERT(eng->state & HTTP_ENGINE_STATEBIT_PREP); - -#if 0 - // If the parent system doesn't want us to reuse - // the connection, we certainly can't keep alive. - if ((eng->state & TINYHTTP_STREAM_REUSE) == 0) - return 0; -#endif - - if (eng->numexch >= 100) // TODO: Make this a parameter - return 0; - - HTTP_Request *req = &eng->result.req; - - // If the client is using HTTP/1.0, we can't - // keep alive. - if (req->minor == 0) - return 0; - - // TODO: This assumes "Connection" can only hold a single token, - // but this is not true. - int i = http_find_header(req->headers, req->num_headers, HTTP_STR("Connection")); - if (i >= 0 && http_streqcase(req->headers[i].value, HTTP_STR("Close"))) - return 0; - - return 1; -} - -static void process_incoming_request(HTTP_Engine *eng) -{ - HTTP_ASSERT(eng->state == HTTP_ENGINE_STATE_SERVER_RECV_ACK - || eng->state == HTTP_ENGINE_STATE_SERVER_SEND_ACK - || eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF - || eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR); - - char *src; - int len; - src = byte_queue_read_buf(&eng->input, &len); - - int ret = http_parse_request(src, len, &eng->result.req); - - if (ret == 0) { - byte_queue_read_ack(&eng->input, 0); - eng->state = HTTP_ENGINE_STATE_SERVER_RECV_BUF; - return; - } - - if (ret < 0) { - byte_queue_read_ack(&eng->input, 0); - byte_queue_write(&eng->output, - "HTTP/1.1 400 Bad Request\r\n" - "Connection: Close\r\n" - "Content-Length: 0\r\n" - "\r\n", -1 - ); - if (byte_queue_error(&eng->output)) - eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; - else { - eng->closing = 1; - eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF; - } - return; - } - - HTTP_ASSERT(ret > 0); - - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_STATUS; - eng->reqsize = ret; - eng->keepalive = should_keep_alive(eng); - eng->response_offset = byte_queue_offset(&eng->output); -} - -void http_engine_recvack(HTTP_Engine *eng, int num) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_RECV_ACK) == 0) - return; - - byte_queue_write_ack(&eng->input, num); - - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) { - - char *src; - int len; - src = byte_queue_read_buf(&eng->input, &len); - - int ret = http_parse_response(src, len, &eng->result.res); - - if (ret == 0) { - byte_queue_read_ack(&eng->input, 0); - eng->state = HTTP_ENGINE_STATE_CLIENT_RECV_BUF; - return; - } - - if (ret < 0) { - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - return; - } - - HTTP_ASSERT(ret > 0); - - eng->state = HTTP_ENGINE_STATE_CLIENT_READY; - - } else { - process_incoming_request(eng); - } -} - -char *http_engine_sendbuf(HTTP_Engine *eng, int *len) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_SEND_BUF) == 0) { - *len = 0; - return NULL; - } - - eng->state &= ~HTTP_ENGINE_STATEBIT_SEND_BUF; - eng->state |= HTTP_ENGINE_STATEBIT_SEND_ACK; - - return byte_queue_read_buf(&eng->output, len); -} - -void http_engine_sendack(HTTP_Engine *eng, int num) -{ - if (eng->state != HTTP_ENGINE_STATE_SERVER_SEND_ACK && - eng->state != HTTP_ENGINE_STATE_CLIENT_SEND_ACK) - return; - - byte_queue_read_ack(&eng->output, num); - - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) { - if (byte_queue_empty(&eng->output)) - eng->state = HTTP_ENGINE_STATE_CLIENT_RECV_BUF; - else - eng->state = HTTP_ENGINE_STATE_CLIENT_SEND_BUF; - } else { - if (byte_queue_empty(&eng->output)) { - if (!eng->closing && eng->keepalive) - process_incoming_request(eng); - else - eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; - } else - eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF; - } -} - -HTTP_Request *http_engine_getreq(HTTP_Engine *eng) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_REQUEST) == 0) - return NULL; - return &eng->result.req; -} - -HTTP_Response *http_engine_getres(HTTP_Engine *eng) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_RESPONSE) == 0) - return NULL; - return &eng->result.res; -} - -void http_engine_url(HTTP_Engine *eng, HTTP_Method method, HTTP_String url, int minor) -{ - if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_URL) - return; - - eng->response_offset = byte_queue_offset(&eng->output); // TODO: rename response_offset to something that makes sense for clients - - HTTP_URL parsed_url; - int ret = http_parse_url(url.ptr, url.len, &parsed_url); - if (ret != url.len) { - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_ERROR; - return; - } - - HTTP_String method_and_space = HTTP_STR("???"); - switch (method) { - case HTTP_METHOD_GET : method_and_space = HTTP_STR("GET "); break; - case HTTP_METHOD_HEAD : method_and_space = HTTP_STR("HEAD "); break; - case HTTP_METHOD_POST : method_and_space = HTTP_STR("POST "); break; - case HTTP_METHOD_PUT : method_and_space = HTTP_STR("PUT "); break; - case HTTP_METHOD_DELETE : method_and_space = HTTP_STR("DELETE "); break; - case HTTP_METHOD_CONNECT: method_and_space = HTTP_STR("CONNECT "); break; - case HTTP_METHOD_OPTIONS: method_and_space = HTTP_STR("OPTIONS "); break; - case HTTP_METHOD_TRACE : method_and_space = HTTP_STR("TRACE "); break; - case HTTP_METHOD_PATCH : method_and_space = HTTP_STR("PATCH "); break; - } - - HTTP_String path = parsed_url.path; - if (path.len == 0) - path = HTTP_STR("/"); - - byte_queue_write(&eng->output, method_and_space.ptr, method_and_space.len); - byte_queue_write(&eng->output, path.ptr, path.len); - byte_queue_write(&eng->output, parsed_url.query.ptr, parsed_url.query.len); - byte_queue_write(&eng->output, minor ? " HTTP/1.1\r\nHost: " : " HTTP/1.0\r\nHost: ", -1); - byte_queue_write(&eng->output, parsed_url.authority.host.text.ptr, parsed_url.authority.host.text.len); - if (parsed_url.authority.port > 0) - byte_queue_write_fmt(&eng->output, "%d", parsed_url.authority.port); - byte_queue_write(&eng->output, "\r\n", 2); - - eng->keepalive = 1; // TODO - - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_HEADER; -} - - -static const char* -get_status_text(int code) -{ - switch(code) { - - case 100: return "Continue"; - case 101: return "Switching Protocols"; - case 102: return "Processing"; - - case 200: return "OK"; - case 201: return "Created"; - case 202: return "Accepted"; - case 203: return "Non-Authoritative Information"; - case 204: return "No Content"; - case 205: return "Reset Content"; - case 206: return "Partial Content"; - case 207: return "Multi-Status"; - case 208: return "Already Reported"; - - case 300: return "Multiple Choices"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 303: return "See Other"; - case 304: return "Not Modified"; - case 305: return "Use Proxy"; - case 306: return "Switch Proxy"; - case 307: return "Temporary Redirect"; - case 308: return "Permanent Redirect"; - - case 400: return "Bad Request"; - case 401: return "Unauthorized"; - case 402: return "Payment Required"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 405: return "Method Not Allowed"; - case 406: return "Not Acceptable"; - case 407: return "Proxy Authentication Required"; - case 408: return "Request Timeout"; - case 409: return "Conflict"; - case 410: return "Gone"; - case 411: return "Length Required"; - case 412: return "Precondition Failed"; - case 413: return "Request Entity Too Large"; - case 414: return "Request-URI Too Long"; - case 415: return "Unsupported Media Type"; - case 416: return "Requested Range Not Satisfiable"; - case 417: return "Expectation Failed"; - case 418: return "I'm a teapot"; - case 420: return "Enhance your calm"; - case 422: return "Unprocessable Entity"; - case 426: return "Upgrade Required"; - case 429: return "Too many requests"; - case 431: return "Request Header Fields Too Large"; - case 449: return "Retry With"; - case 451: return "Unavailable For Legal Reasons"; - - case 500: return "Internal Server Error"; - case 501: return "Not Implemented"; - case 502: return "Bad Gateway"; - case 503: return "Service Unavailable"; - case 504: return "Gateway Timeout"; - case 505: return "HTTP Version Not Supported"; - case 509: return "Bandwidth Limit Exceeded"; - } - return "???"; -} - -void http_engine_status(HTTP_Engine *eng, int status) -{ - if (eng->state != HTTP_ENGINE_STATE_SERVER_PREP_STATUS) - return; - - byte_queue_write_fmt(&eng->output, - "HTTP/1.1 %d %s\r\n", - status, get_status_text(status)); - - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_HEADER; -} - -void http_engine_header(HTTP_Engine *eng, HTTP_String str) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_PREP_HEADER) == 0) - return; - - // TODO: Check that the header is valid - - byte_queue_write(&eng->output, str.ptr, str.len); - byte_queue_write(&eng->output, "\r\n", 2); -} - -void http_engine_header_fmt2(HTTP_Engine *eng, const char *fmt, va_list args) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_PREP_HEADER) == 0) - return; - - // TODO: Check that the header is valid - - byte_queue_write_fmt2(&eng->output, fmt, args); - byte_queue_write(&eng->output, "\r\n", 2); -} - -void http_engine_header_fmt(HTTP_Engine *eng, const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - http_engine_header_fmt2(eng, fmt, args); - va_end(args); -} - -static void -complete_message_head(HTTP_Engine *eng) -{ - if (eng->keepalive) byte_queue_write(&eng->output, "Connection: Keep-Alive\r\n", -1); - else byte_queue_write(&eng->output, "Connection: Close\r\n", -1); - - byte_queue_write(&eng->output, "Content-Length: ", -1); - eng->content_length_value_offset = byte_queue_offset(&eng->output); - byte_queue_write(&eng->output, TEN_SPACES "\r\n", -1); - - byte_queue_write(&eng->output, "\r\n", -1); - eng->content_length_offset = byte_queue_offset(&eng->output); -} - -static void complete_message_body(HTTP_Engine *eng) -{ - unsigned int content_length = byte_queue_size_from_offset(&eng->output, eng->content_length_offset); - - if (content_length > UINT32_MAX) { - // TODO - } - - char tmp[10]; - - tmp[0] = '0' + content_length / 1000000000; content_length %= 1000000000; - tmp[1] = '0' + content_length / 100000000; content_length %= 100000000; - tmp[2] = '0' + content_length / 10000000; content_length %= 10000000; - tmp[3] = '0' + content_length / 1000000; content_length %= 1000000; - tmp[4] = '0' + content_length / 100000; content_length %= 100000; - tmp[5] = '0' + content_length / 10000; content_length %= 10000; - tmp[6] = '0' + content_length / 1000; content_length %= 1000; - tmp[7] = '0' + content_length / 100; content_length %= 100; - tmp[8] = '0' + content_length / 10; content_length %= 10; - tmp[9] = '0' + content_length; - - int i = 0; - while (i < 9 && tmp[i] == '0') - i++; - - byte_queue_patch(&eng->output, eng->content_length_value_offset, tmp + i, 10 - i); -} - -void http_engine_body(HTTP_Engine *eng, HTTP_String str) -{ - http_engine_bodycap(eng, str.len); - int cap; - char *buf = http_engine_bodybuf(eng, &cap); - if (buf) { - memcpy(buf, str.ptr, str.len); - http_engine_bodyack(eng, str.len); - } -} - -static void ensure_body_entered(HTTP_Engine *eng) -{ - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) { - - if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_HEADER) { - complete_message_head(eng); - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF; - } - - } else { - - if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_HEADER) { - complete_message_head(eng); - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF; - } - } -} - -void http_engine_bodycap(HTTP_Engine *eng, int mincap) -{ - ensure_body_entered(eng); - if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF && - eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) - return; - - byte_queue_write_setmincap(&eng->output, mincap); -} - -char *http_engine_bodybuf(HTTP_Engine *eng, int *cap) -{ - ensure_body_entered(eng); - if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF && - eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) { - *cap = 0; - return NULL; - } - - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK; - else - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK; - - return byte_queue_write_buf(&eng->output, cap); -} - -void http_engine_bodyack(HTTP_Engine *eng, int num) -{ - if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK && - eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK) - return; - - byte_queue_write_ack(&eng->output, num); - - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF; - else - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF; -} - -void http_engine_done(HTTP_Engine *eng) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_PREP) == 0) - return; - - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) { - - if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_URL) { - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - return; - } - - if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_HEADER) { - complete_message_head(eng); - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF; - } - - if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF) - complete_message_body(eng); - - if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_ERROR) { - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - return; - } - - if (byte_queue_error(&eng->output)) { - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - return; - } - - eng->state = HTTP_ENGINE_STATE_CLIENT_SEND_BUF; - - } else { - - if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_HEADER) { - complete_message_head(eng); - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF; - } - - if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) - complete_message_body(eng); - - if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR) { - byte_queue_remove_from_offset(&eng->output, eng->response_offset); - byte_queue_write(&eng->output, - "HTTP/1.1 500 Internal Server Error\r\n" - "Content-Length: 0\r\n" - "Connection: Close\r\n" - "\r\n", - -1 - ); - } - - if (byte_queue_error(&eng->output)) { - eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; - return; - } - - byte_queue_read_ack(&eng->input, eng->reqsize); - eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF; - } -} - -void http_engine_undo(HTTP_Engine *eng) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_PREP) == 0) - return; - - byte_queue_write_ack(&eng->output, 0); - byte_queue_remove_from_offset(&eng->output, eng->response_offset); - - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_URL; - else - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_STATUS; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// src/cert.c -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/cert.c" -#include -#include -#include - -#ifdef HTTPS_ENABLED -#include -#include -#include -#include -#include -#include -#endif - -#ifndef HTTP_AMALGAMATION -#include "cert.h" -#endif - -#ifdef HTTPS_ENABLED - -static EVP_PKEY *generate_rsa_key_pair(int key_bits) -{ - EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); - if (!ctx) - return NULL; - - if (EVP_PKEY_keygen_init(ctx) <= 0) { - EVP_PKEY_CTX_free(ctx); - return NULL; - } - - if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, key_bits) <= 0) { - EVP_PKEY_CTX_free(ctx); - return NULL; - } - - EVP_PKEY *pkey = NULL; - if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { - EVP_PKEY_CTX_free(ctx); - return NULL; - } - - EVP_PKEY_CTX_free(ctx); - return pkey; -} - -static X509 *create_certificate(EVP_PKEY *pkey, HTTP_String C, HTTP_String O, HTTP_String CN, int days) -{ - X509 *x509 = X509_new(); - if (!x509) - return NULL; - - // Set version (version 3) - X509_set_version(x509, 2); - - // Set serial number - ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); - - // Set validity period - X509_gmtime_adj(X509_get_notBefore(x509), 0); - X509_gmtime_adj(X509_get_notAfter(x509), 31536000L * days); // days * seconds_per_year - - // Set public key - X509_set_pubkey(x509, pkey); - - // Set subject name - X509_NAME *name = X509_get_subject_name(x509); - X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char*) C.ptr, C.len, -1, 0); - X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char*) O.ptr, O.len, -1, 0); - X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*) CN.ptr, CN.len, -1, 0); - - // Set issuer name (same as subject for self-signed) - X509_set_issuer_name(x509, name); - - if (!X509_sign(x509, pkey, EVP_sha256())) { - X509_free(x509); - return NULL; - } - - return x509; -} - -static int save_private_key(EVP_PKEY *pkey, HTTP_String file) -{ - char copy[1<<10]; - if (file.len >= (int) sizeof(copy)) + if (socket_manager_init(&server->sockets, + &server->socket_pool, HTTP_SERVER_CAPACITY) < 0) return -1; - memcpy(copy, file.ptr, file.len); - copy[file.len] = '\0'; - - FILE *fp = fopen(copy, "wb"); - if (!fp) - return -1; - - // Write private key in PEM format - if (!PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL)) { - fclose(fp); - return -1; - } - - fclose(fp); return 0; } -static int save_certificate(X509 *x509, HTTP_String file) +void http_server_free(HTTP_Server *server) { - char copy[1<<10]; - if (file.len >= (int) sizeof(copy)) - return -1; - memcpy(copy, file.ptr, file.len); - copy[file.len] = '\0'; + socket_manager_free(&server->sockets); - FILE *fp = fopen(copy, "wb"); - if (!fp) - return -1; - - // Write certificate in PEM format - if (!PEM_write_X509(fp, x509)) { - fclose(fp); - return -1; - } - - fclose(fp); - return 0; -} - -int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, - HTTP_String cert_file, HTTP_String key_file) -{ - EVP_PKEY *pkey = generate_rsa_key_pair(2048); - if (pkey == NULL) - return -1; - - X509 *x509 = create_certificate(pkey, C, O, CN, 1); - if (x509 == NULL) { - EVP_PKEY_free(pkey); - return -1; - } - - if (save_private_key(pkey, key_file) < 0) { - X509_free(x509); - EVP_PKEY_free(pkey); - return -1; - } - - if (save_certificate(x509, cert_file) < 0) { - X509_free(x509); - EVP_PKEY_free(pkey); - return -1; - } - - X509_free(x509); - EVP_PKEY_free(pkey); - return 0; -} - -#else - -int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, - HTTP_String cert_file, HTTP_String key_file) -{ - (void) C; - (void) O; - (void) CN; - (void) cert_file; - (void) key_file; - return -1; -} - -#endif - -//////////////////////////////////////////////////////////////////////////////////////// -// src/sec.c -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/sec.c" -#ifndef HTTP_AMALGAMATION -#include "sec.h" -#endif - -#ifndef HTTPS_ENABLED - -void secure_context_global_init(void) -{ -} - -void secure_context_global_free(void) -{ -} - -int secure_context_init_as_client(SecureContext *sec) -{ - (void) sec; - return 0; -} - -int secure_context_init_as_server(SecureContext *sec, - HTTP_String cert_file, HTTP_String key_file) -{ - (void) sec; - (void) cert_file; - (void) key_file; - return 0; -} - -int secure_context_add_cert(SecureContext *sec, - HTTP_String domain, HTTP_String cert_file, - HTTP_String key_file) -{ - (void) sec; - (void) domain; - (void) cert_file; - (void) key_file; - return -1; -} - -void secure_context_free(SecureContext *sec) -{ - (void) sec; -} - -#else - -void secure_context_global_init(void) -{ - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); -} - -void secure_context_global_free(void) -{ - EVP_cleanup(); -} - -int secure_context_init_as_client(SecureContext *sec) -{ - SSL_CTX *ctx = SSL_CTX_new(TLS_client_method()); - if (!ctx) - return -1; - - SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); - - SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); - - if (SSL_CTX_set_default_verify_paths(ctx) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - sec->is_server = false; - sec->ctx = ctx; - sec->num_certs = 0; - return 0; -} - -static int servername_callback(SSL *ssl, int *ad, void *arg) -{ - SecureContext *sec = arg; - - (void) ad; // TODO: use this? - - const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); - if (servername == NULL) - return SSL_TLSEXT_ERR_NOACK; - - for (int i = 0; i < sec->num_certs; i++) { - CertData *cert = &sec->certs[i]; - if (!strcmp(cert->domain, servername)) { - SSL_set_SSL_CTX(ssl, cert->ctx); - return SSL_TLSEXT_ERR_OK; - } - } - - return SSL_TLSEXT_ERR_NOACK; -} - -int secure_context_init_as_server(SecureContext *sec, - HTTP_String cert_file, HTTP_String key_file) -{ - SSL_CTX *ctx = SSL_CTX_new(TLS_server_method()); - if (!ctx) - return -1; - - SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); - - char cert_buffer[1024]; - if (cert_file.len >= (int) sizeof(cert_buffer)) { - SSL_CTX_free(ctx); - return -1; - } - memcpy(cert_buffer, cert_file.ptr, cert_file.len); - cert_buffer[cert_file.len] = '\0'; - - // Copy private key file path to static buffer - char key_buffer[1024]; - if (key_file.len >= (int) sizeof(key_buffer)) { - SSL_CTX_free(ctx); - return -1; - } - memcpy(key_buffer, key_file.ptr, key_file.len); - key_buffer[key_file.len] = '\0'; - - // Load certificate and private key - if (SSL_CTX_use_certificate_file(ctx, cert_buffer, SSL_FILETYPE_PEM) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - if (SSL_CTX_use_PrivateKey_file(ctx, key_buffer, SSL_FILETYPE_PEM) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - // Verify that the private key matches the certificate - if (SSL_CTX_check_private_key(ctx) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - SSL_CTX_set_tlsext_servername_callback(ctx, servername_callback); - SSL_CTX_set_tlsext_servername_arg(ctx, sec); - - sec->is_server = true; - sec->ctx = ctx; - sec->num_certs = 0; - return 0; -} - -void secure_context_free(SecureContext *sec) -{ - SSL_CTX_free(sec->ctx); - for (int i = 0; i < sec->num_certs; i++) - SSL_CTX_free(sec->certs[i].ctx); -} - -int secure_context_add_cert(SecureContext *sec, - HTTP_String domain, HTTP_String cert_file, - HTTP_String key_file) -{ - if (!sec->is_server) - return -1; - - if (sec->num_certs == MAX_CERTS) - return -1; - - SSL_CTX *ctx = SSL_CTX_new(TLS_server_method()); - if (!ctx) - return -1; - - SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); - - char cert_buffer[1024]; - if (cert_file.len >= (int) sizeof(cert_buffer)) { - SSL_CTX_free(ctx); - return -1; - } - memcpy(cert_buffer, cert_file.ptr, cert_file.len); - cert_buffer[cert_file.len] = '\0'; - - char key_buffer[1024]; - if (key_file.len >= (int) sizeof(key_buffer)) { - SSL_CTX_free(ctx); - return -1; - } - memcpy(key_buffer, key_file.ptr, key_file.len); - key_buffer[key_file.len] = '\0'; - - if (SSL_CTX_use_certificate_file(ctx, cert_buffer, SSL_FILETYPE_PEM) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - if (SSL_CTX_use_PrivateKey_file(ctx, key_buffer, SSL_FILETYPE_PEM) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - if (SSL_CTX_check_private_key(ctx) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - CertData *cert = &sec->certs[sec->num_certs]; - if (domain.len >= (int) sizeof(cert->domain)) { - SSL_CTX_free(ctx); - return -1; - } - memcpy(cert->domain, domain.ptr, domain.len); - cert->domain[domain.len] = '\0'; - cert->ctx = ctx; - sec->num_certs++; - return 0; -} - -#endif - -//////////////////////////////////////////////////////////////////////////////////////// -// src/socket_raw.c -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/socket_raw.c" -#include - -#ifdef _WIN32 -#include -#endif - -#ifdef __linux__ -#include -#include -#include -#endif - -#ifndef HTTP_AMALGAMATION -#include "socket_raw.h" -#endif - -int socket_raw_global_init(void) -{ -#ifdef _WIN32 - WSADATA wsaData; - int result = WSAStartup(MAKEWORD(2, 2), &wsaData); - if (result != 0) - return 1; -#endif - return 0; -} - -void socket_raw_global_free(void) -{ -#ifdef _WIN32 - WSACleanup(); -#endif -} - -int set_socket_blocking(RAW_SOCKET sock, bool value) -{ -#ifdef _WIN32 - u_long mode = !value; - if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR) - return -1; -#endif - -#ifdef __linux__ - int flags = fcntl(sock, F_GETFL, 0); - if (flags < 0) - return -1; - if (value) flags &= ~O_NONBLOCK; - else flags |= O_NONBLOCK; - if (fcntl(sock, F_SETFL, flags) < 0) - return -1; -#endif - - return 0; -} - -RAW_SOCKET listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int backlog) -{ - RAW_SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock == BAD_SOCKET) - return BAD_SOCKET; - - if (set_socket_blocking(sock, false) < 0) { - CLOSE_SOCKET(sock); - return BAD_SOCKET; - } - - if (reuse_addr) { - int one = 1; - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &one, sizeof(one)); - } - - struct in_addr addr_buf; - if (addr.len == 0) - addr_buf.s_addr = htonl(INADDR_ANY); - else { - - char copy[100]; - if (addr.len >= (int) sizeof(copy)) { - CLOSE_SOCKET(sock); - return BAD_SOCKET; - } - memcpy(copy, addr.ptr, addr.len); - copy[addr.len] = '\0'; - - if (inet_pton(AF_INET, copy, &addr_buf) < 0) { - CLOSE_SOCKET(sock); - return BAD_SOCKET; - } - } - - struct sockaddr_in bind_buf; - bind_buf.sin_family = AF_INET; - bind_buf.sin_addr = addr_buf; - bind_buf.sin_port = htons(port); - if (bind(sock, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) { // TODO: how does bind fail on windows? - CLOSE_SOCKET(sock); - return BAD_SOCKET; - } - - if (listen(sock, backlog) < 0) { // TODO: how does listen fail on windows? - CLOSE_SOCKET(sock); - return BAD_SOCKET; - } - - return sock; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// src/socket.c -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/socket.c" -#include // snprintf -#include -#include -#include - -#ifdef _WIN32 -#include -#endif - -#ifdef __linux__ -#include -#include -#include -#include -#endif - -#ifdef HTTPS_ENABLED -#include -#include -#endif - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#include "socket.h" -#endif - -typedef struct { - bool is_ipv4; - union { - HTTP_IPv4 ipv4; - HTTP_IPv6 ipv6; - }; -} PendingConnectAddr; - -struct PendingConnect { - uint16_t port; - int cursor; - int num_addrs; - int max_addrs; - PendingConnectAddr *addrs; - char* hostname; // null-terminated - int hostname_len; -}; - -static PendingConnect* -pending_connect_init(HTTP_String hostname, uint16_t port, int max_addrs) -{ - PendingConnect *pending_connect = malloc(sizeof(PendingConnect) + max_addrs * sizeof(PendingConnectAddr) + hostname.len + 1); - if (pending_connect == NULL) - return NULL; - pending_connect->port = port; - pending_connect->cursor = 0; - pending_connect->num_addrs = 0; - pending_connect->max_addrs = max_addrs; - pending_connect->addrs = (PendingConnectAddr*) (pending_connect + 1); - pending_connect->hostname = (char*) (pending_connect->addrs + max_addrs); - memcpy(pending_connect->hostname, hostname.ptr, hostname.len); - pending_connect->hostname[hostname.len] = '\0'; - pending_connect->hostname_len = hostname.len; - return pending_connect; -} - -static void -pending_connect_free(PendingConnect *pending_connect) -{ - free(pending_connect); -} - -static void -pending_connect_add_ipv4(PendingConnect *pending_connect, HTTP_IPv4 ipv4) -{ - if (pending_connect->num_addrs == pending_connect->max_addrs) - return; - pending_connect->addrs[pending_connect->num_addrs++] = (PendingConnectAddr) { .is_ipv4=true, .ipv4=ipv4 }; -} - -static void -pending_connect_add_ipv6(PendingConnect *pending_connect, HTTP_IPv6 ipv6) -{ - if (pending_connect->num_addrs == pending_connect->max_addrs) - return; - pending_connect->addrs[pending_connect->num_addrs++] = (PendingConnectAddr) { .is_ipv4=false, .ipv6=ipv6 }; -} - -static int -next_connect_addr(PendingConnect *pending_connect, PendingConnectAddr *addr) -{ - if (pending_connect->cursor == pending_connect->num_addrs) - return -1; - *addr = pending_connect->addrs[pending_connect->cursor++]; - return 0; -} - -// Initializes a FREE socket with the information required to -// connect to specified host name. The resulting socket state -// is DIED if an error occurred or PENDING. -void socket_connect(Socket *sock, SecureContext *sec, - HTTP_String hostname, uint16_t port, void *user_data) -{ - PendingConnect *pending_connect; - - int max_addrs = 30; - pending_connect = pending_connect_init(hostname, port, max_addrs); - if (pending_connect == NULL) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return; - } - - char portstr[16]; - int len = snprintf(portstr, sizeof(portstr), "%u", port); - if (len < 0 || len >= (int) sizeof(portstr)) { - pending_connect_free(pending_connect); - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return; - } - - // DNS query - struct addrinfo hints = {0}; - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - - struct addrinfo *res = NULL; - int ret = getaddrinfo(pending_connect->hostname, portstr, &hints, &res); - if (ret != 0) { - pending_connect_free(pending_connect); - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return; - } - - for (struct addrinfo *rp = res; rp; rp = rp->ai_next) { - if (rp->ai_family == AF_INET) { - HTTP_IPv4 *ipv4 = (void*) &((struct sockaddr_in*)rp->ai_addr)->sin_addr; - pending_connect_add_ipv4(pending_connect, *ipv4); - } else if (rp->ai_family == AF_INET6) { - HTTP_IPv6 *ipv6 = (void*) &((struct sockaddr_in6*)rp->ai_addr)->sin6_addr; - pending_connect_add_ipv6(pending_connect, *ipv6); - } - } - - freeaddrinfo(res); - - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - - sock->raw = BAD_SOCKET; - sock->user_data = user_data; - sock->pending_connect = pending_connect; - sock->sec = sec; - -#ifdef HTTPS_ENABLED - sock->ssl = NULL; -#endif - - socket_update(sock); -} - -// Just like socket_connect, but the raw IPv4 address is specified -void socket_connect_ipv4(Socket *sock, SecureContext *sec, - HTTP_IPv4 addr, uint16_t port, void *user_data) -{ - PendingConnect *pending_connect; - - pending_connect = pending_connect_init(HTTP_STR(""), port, 1); - if (pending_connect == NULL) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return; - } - - pending_connect_add_ipv4(pending_connect, addr); - - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - - sock->raw = BAD_SOCKET; - sock->user_data = user_data; - sock->pending_connect = pending_connect; - sock->sec = sec; - -#ifdef HTTPS_ENABLED - sock->ssl = NULL; -#endif - - socket_update(sock); -} - -// Just like socket_connect, but the raw IPv6 address is specified -void socket_connect_ipv6(Socket *sock, SecureContext *sec, - HTTP_IPv6 addr, uint16_t port, void *user_data) -{ - PendingConnect *pending_connect; - - pending_connect = pending_connect_init(HTTP_STR(""), port, 1); - if (pending_connect == NULL) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return; - } - - pending_connect_add_ipv6(pending_connect, addr); - - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - - sock->raw = BAD_SOCKET; - sock->user_data = user_data; - sock->pending_connect = pending_connect; - sock->sec = sec; - -#ifdef HTTPS_ENABLED - sock->ssl = NULL; -#endif - - socket_update(sock); -} - -void socket_accept(Socket *sock, SecureContext *sec, RAW_SOCKET raw) -{ - sock->state = SOCKET_STATE_ACCEPTED; - sock->raw = raw; - sock->events = 0; - sock->user_data = NULL; - sock->pending_connect = NULL; - sock->sec = sec; - -#ifdef HTTPS_ENABLED - sock->ssl = NULL; -#endif - - if (set_socket_blocking(raw, false) < 0) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return; - } - - socket_update(sock); -} - -void socket_close(Socket *sock) -{ - // TODO: maybe we don't want to always set to SHUTDOWN. What if the socket is DIED for instance? - sock->state = SOCKET_STATE_SHUTDOWN; - sock->events = 0; - socket_update(sock); -} - -bool socket_ready(Socket *sock) -{ - return sock->state == SOCKET_STATE_ESTABLISHED_READY; -} - -bool socket_died(Socket *sock) -{ - return sock->state == SOCKET_STATE_DIED; -} - -// TODO: when is the pending_connect data freed? - -static bool connect_pending(void) -{ -#ifdef _WIN32 - return WSAGetLastError() == WSAEWOULDBLOCK; -#else - return errno == EINPROGRESS; -#endif -} - -static bool -connect_failed_because_or_peer_2(int err) -{ -#ifdef _WIN32 - return err == WSAECONNREFUSED - || err == WSAETIMEDOUT - || err == WSAENETUNREACH - || err == WSAEHOSTUNREACH; -#else - return err == ECONNREFUSED - || err == ETIMEDOUT - || err == ENETUNREACH - || err == EHOSTUNREACH; -#endif -} - -static bool -connect_failed_because_or_peer(void) -{ -#ifdef _WIN32 - int err = WSAGetLastError(); -#else - int err = errno; -#endif - return connect_failed_because_or_peer_2(err); -} - -// Processes the socket until it's either ready, died, or would block -void socket_update(Socket *sock) -{ - sock->events = 0; - - bool again; - do { - - again = false; - - switch (sock->state) { - case SOCKET_STATE_PENDING: - { - // In this state we need to pop an address from the pending connect - // data and try connect to it. This state is reached when a socket - // is initialized using one of the socket_connect functions or by - // failing to connect before the established state is reached. - - // If this isn't the first connection attempt we may have old - // descriptors that need freeing before trying again. - { -#ifdef HTTPS_ENABLED - if (sock->ssl) { - SSL_free(sock->ssl); - sock->ssl = NULL; - } -#endif - if (sock->raw != BAD_SOCKET) - CLOSE_SOCKET(sock->raw); - } - - // Pop the next address from the pending connect data - PendingConnectAddr addr; - if (next_connect_addr(sock->pending_connect, &addr) < 0) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - uint16_t port = sock->pending_connect->port; - - // Create a kernel socket object - int family = addr.is_ipv4 ? AF_INET : AF_INET6; - RAW_SOCKET raw = socket(family, SOCK_STREAM, 0); - if (raw == BAD_SOCKET) { - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - again = true; - break; - } - - // Configure it - if (set_socket_blocking(raw, false) < 0) { - CLOSE_SOCKET(raw); - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - - // Now perform the connect - - struct sockaddr_in connect_buf_4; - struct sockaddr_in6 connect_buf_6; - struct sockaddr* connect_buf; - int connect_buf_len; - - if (addr.is_ipv4) { - - connect_buf = (struct sockaddr*) &connect_buf_4; - connect_buf_len = sizeof(connect_buf_4); - - connect_buf_4.sin_family = AF_INET; - connect_buf_4.sin_port = htons(port); - memcpy(&connect_buf_4.sin_addr, &addr.ipv4, sizeof(HTTP_IPv4)); - - } else { - - connect_buf = (struct sockaddr*) &connect_buf_6; - connect_buf_len = sizeof(connect_buf_6); - - connect_buf_6.sin6_family = AF_INET6; - connect_buf_6.sin6_port = htons(port); - memcpy(&connect_buf_6.sin6_addr, &addr.ipv6, sizeof(HTTP_IPv6)); - } - - int ret = connect(raw, connect_buf, connect_buf_len); - - // We divide the connect() results in four categories: - // - // 1) The connect resolved immediately. I'm not sure how this can happen, - // but we may as well handle it. This allows us to skip a step. - // - // 2) The connect operation is pending. This is what we expect most of the time. - // - // 3) The connect operation failed because the target address wasn't good - // for some reason. It make sense to try connecting to a different address - // - // 4) The connect operation failed for unknown reasons. There isn't much we - // can do at this point. - - if (ret == 0) { - // Connected immediately - sock->raw = raw; - sock->state = SOCKET_STATE_CONNECTED; - sock->events = 0; - again = true; - break; - } - - if (connect_pending()) { // TODO: I'm pretty sure all the error numbers need to be changed for windows - // Connection pending - sock->raw = raw; - sock->state = SOCKET_STATE_CONNECTING; - sock->events = POLLOUT; - break; - } - - // Connect failed - - // If remote peer not working, try next address - if (connect_failed_because_or_peer()) { - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - again = true; - } else { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - } - } - break; - - case SOCKET_STATE_CONNECTING: - { - // We reach this point when a connect() operation on the - // socket started and then the descriptor was marked as - // ready for output. This means the operation is complete. - - int err = 0; - socklen_t len = sizeof(err); - - if (getsockopt(sock->raw, SOL_SOCKET, SO_ERROR, (void*) &err, &len) < 0 || err != 0) { - - // If remote peer not working, try next address - if (connect_failed_because_or_peer_2(err)) { - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - again = true; - break; - } - - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - - // Connect succeeded - sock->state = SOCKET_STATE_CONNECTED; - sock->events = 0; - again = true; - } - break; - - case SOCKET_STATE_CONNECTED: - { - if (!socket_secure(sock)) { - - pending_connect_free(sock->pending_connect); - sock->pending_connect = NULL; - - sock->events = 0; - sock->state = SOCKET_STATE_ESTABLISHED_READY; - - } else { -#ifdef HTTPS_ENABLED - // Start SSL handshake - - if (sock->ssl == NULL) { - sock->ssl = SSL_new(sock->sec->ctx); - if (sock->ssl == NULL) { - ERR_print_errors_fp(stderr); // TODO: remove - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - - if (SSL_set_fd(sock->ssl, sock->raw) != 1) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - - char *hostname = NULL; - if (sock->pending_connect->hostname[0]) - hostname = sock->pending_connect->hostname; - - if (hostname) - SSL_set_tlsext_host_name(sock->ssl, hostname); - } - - int ret = SSL_connect(sock->ssl); - if (ret == 1) { - // Handshake done - - pending_connect_free(sock->pending_connect); - sock->pending_connect = NULL; - - sock->state = SOCKET_STATE_ESTABLISHED_READY; - sock->events = 0; - break; - } - - int err = SSL_get_error(sock->ssl, ret); - if (err == SSL_ERROR_WANT_READ) { - sock->events = POLLIN; - break; - } - - if (err == SSL_ERROR_WANT_WRITE) { - sock->events = POLLOUT; - break; - } - - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - again = true; -#else - assert(0); -#endif - } - } - break; - - case SOCKET_STATE_ACCEPTED: - { - if (!socket_secure(sock)) { - sock->state = SOCKET_STATE_ESTABLISHED_READY; - sock->events = 0; - } else { -#ifdef HTTPS_ENABLED - // Start server-side SSL handshake - if (!sock->ssl) { - - sock->ssl = SSL_new(sock->sec->ctx); - if (sock->ssl == NULL) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - - if (SSL_set_fd(sock->ssl, sock->raw) != 1) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - } - - int ret = SSL_accept(sock->ssl); - if (ret == 1) { - // Handshake done - sock->state = SOCKET_STATE_ESTABLISHED_READY; - sock->events = 0; - break; - } - - int err = SSL_get_error(sock->ssl, ret); - if (err == SSL_ERROR_WANT_READ) { - sock->events = POLLIN; - break; - } - - if (err == SSL_ERROR_WANT_WRITE) { - sock->events = POLLOUT; - break; - } - - // Server socket error - close the connection - sock->state = SOCKET_STATE_DIED; - sock->events = 0; -#else - assert(0); -#endif - } - } - break; - - case SOCKET_STATE_ESTABLISHED_WAIT: - { - sock->state = SOCKET_STATE_ESTABLISHED_READY; - sock->events = 0; - } - break; - - case SOCKET_STATE_SHUTDOWN: - { - if (!socket_secure(sock)) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - } else { -#ifdef HTTPS_ENABLED - int ret = SSL_shutdown(sock->ssl); - if (ret == 1) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - - int err = SSL_get_error(sock->ssl, ret); - if (err == SSL_ERROR_WANT_READ) { - sock->events = POLLIN; - break; - } - - if (err == SSL_ERROR_WANT_WRITE) { - sock->events = POLLOUT; - break; - } - - sock->state = SOCKET_STATE_DIED; - sock->events = 0; -#else - assert(0); -#endif - } - } - break; - - default: - // Do nothing - break; - } - - } while (again); -} - -static bool would_block(void) -{ -#ifdef _WIN32 - int err = WSAGetLastError(); - return err == WSAEWOULDBLOCK; -#else - return errno == EAGAIN || errno == EWOULDBLOCK; -#endif -} - -static bool interrupted(void) -{ -#ifdef _WIN32 - return false; -#else - return errno == EINTR; -#endif -} - -int socket_read(Socket *sock, char *dst, int max) -{ - // If not ESTABLISHED, set state to DIED and return - if (sock->state != SOCKET_STATE_ESTABLISHED_READY) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return 0; - } - - if (!socket_secure(sock)) { - int ret = recv(sock->raw, dst, max, 0); - if (ret == 0) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - } else { - if (ret < 0) { - if (would_block()) { - sock->state = SOCKET_STATE_ESTABLISHED_WAIT; - sock->events = POLLIN; - } else { - if (!interrupted()) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - } - } - ret = 0; - } - } - return ret; - } else { -#ifdef HTTPS_ENABLED - int ret = SSL_read(sock->ssl, dst, max); - if (ret <= 0) { - int err = SSL_get_error(sock->ssl, ret); - if (err == SSL_ERROR_WANT_READ) { - sock->state = SOCKET_STATE_ESTABLISHED_WAIT; - sock->events = POLLIN; - } else if (err == SSL_ERROR_WANT_WRITE) { - sock->state = SOCKET_STATE_ESTABLISHED_WAIT; - sock->events = POLLOUT; - } else { - fprintf(stderr, "OpenSSL error in socket_read: "); - ERR_print_errors_fp(stderr); - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - } - ret = 0; - } - return ret; -#else - assert(0); - return -1; -#endif - } -} - -int socket_write(Socket *sock, char *src, int len) -{ - // If not ESTABLISHED, set state to DIED and return - if (sock->state != SOCKET_STATE_ESTABLISHED_READY) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return 0; - } - - if (!socket_secure(sock)) { - int ret = send(sock->raw, src, len, 0); - if (ret < 0) { - if (would_block()) { - sock->state = SOCKET_STATE_ESTABLISHED_WAIT; - sock->events = POLLOUT; - } else { - if (!interrupted()) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - } - } - ret = 0; - } - return ret; - } else { -#ifdef HTTPS_ENABLED - int ret = SSL_write(sock->ssl, src, len); - if (ret <= 0) { - int err = SSL_get_error(sock->ssl, ret); - if (err == SSL_ERROR_WANT_READ) { - sock->state = SOCKET_STATE_ESTABLISHED_WAIT; - sock->events = POLLIN; - } else if (err == SSL_ERROR_WANT_WRITE) { - sock->state = SOCKET_STATE_ESTABLISHED_WAIT; - sock->events = POLLOUT; - } else { - fprintf(stderr, "OpenSSL error in socket_write: "); - ERR_print_errors_fp(stderr); - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - } - ret = 0; - } - return ret; -#else - assert(0); -#endif - } -} - -bool socket_secure(Socket *sock) -{ -#ifdef HTTPS_ENABLED - return sock->sec != NULL; -#else - (void) sock; - return false; -#endif -} - -void socket_free(Socket *sock) -{ - if (sock->pending_connect != NULL) - pending_connect_free(sock->pending_connect); - - if (sock->raw != BAD_SOCKET) - CLOSE_SOCKET(sock->raw); - -#ifdef HTTPS_ENABLED - if (sock->ssl) - SSL_free(sock->ssl); -#endif - - sock->state = SOCKET_STATE_FREE; -} - -void socket_set_user_data(Socket *sock, void *user_data) -{ - sock->user_data = user_data; -} - -void *socket_get_user_data(Socket *sock) -{ - return sock->user_data; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// src/socket_pool.c -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/socket_pool.c" -#include -#include - -#ifdef __linux__ -#include -#include -#endif - -#ifndef HTTP_AMALGAMATION -#include "socket_pool.h" -#endif - -#define SOCKET_HARD_LIMIT (1<<10) -#define MAX_CERTS 10 - -struct SocketPool { - - SecureContext sec; - - RAW_SOCKET listen_sock; - RAW_SOCKET secure_sock; - - int num_socks; - int max_socks; - Socket socks[]; -}; - -int socket_pool_global_init(void) -{ - int ret = socket_raw_global_init(); - if (ret < 0) - return -1; - - secure_context_global_init(); - return 0; -} - -void socket_pool_global_free(void) -{ - secure_context_global_free(); - socket_raw_global_free(); -} - -SocketPool *socket_pool_init(HTTP_String addr, - uint16_t port, uint16_t secure_port, int max_socks, - bool reuse_addr, int backlog, HTTP_String cert_file, - HTTP_String key_file) -{ - if (max_socks > SOCKET_HARD_LIMIT) - return NULL; - - SocketPool *pool = malloc(sizeof(SocketPool) + max_socks * sizeof(Socket)); - if (pool == NULL) - return NULL; - - pool->num_socks = 0; - pool->max_socks = max_socks; - - for (int i = 0; i < pool->max_socks; i++) - pool->socks[i].state = SOCKET_STATE_FREE; - - if (port == 0) - pool->listen_sock = BAD_SOCKET; - else { - pool->listen_sock = listen_socket(addr, port, reuse_addr, backlog); - if (pool->listen_sock == BAD_SOCKET) { - free(pool); - return NULL; - } - } - - if (secure_port == 0) - pool->secure_sock = BAD_SOCKET; - else { -#ifndef HTTPS_ENABLED - (void) cert_file; - (void) key_file; - if (pool->listen_sock != BAD_SOCKET) - CLOSE_SOCKET(pool->listen_sock); - free(pool); - return NULL; -#else - if (secure_context_init_as_server(&pool->sec, cert_file, key_file) < 0) { - if (pool->listen_sock != BAD_SOCKET) - CLOSE_SOCKET(pool->listen_sock); - free(pool); - return NULL; - } - - pool->secure_sock = listen_socket(addr, secure_port, reuse_addr, backlog); - if (pool->secure_sock == BAD_SOCKET) { - if (pool->listen_sock != BAD_SOCKET) CLOSE_SOCKET(pool->listen_sock); - free(pool); - return NULL; - } -#endif - } - -#ifdef HTTPS_ENABLED - if (port == 0 && secure_port == 0) { - if (secure_context_init_as_client(&pool->sec) < 0) { - if (pool->listen_sock != BAD_SOCKET) CLOSE_SOCKET(pool->listen_sock); - if (pool->secure_sock != BAD_SOCKET) CLOSE_SOCKET(pool->secure_sock); - free(pool); - return NULL; - } - } -#endif - - for (int i = 0; i < max_socks; i++) - pool->socks[i].state = SOCKET_STATE_FREE; - - return pool; -} - -void socket_pool_free(SocketPool *pool) -{ - for (int i = 0, j = 0; j < pool->num_socks; i++) { - - Socket *sock = &pool->socks[i]; - - if (sock->state == SOCKET_STATE_FREE) + for (int i = 0, j = 0; j < server->num_conns; i++) { + HTTP_ServerConn *conn = &server->conns[i]; + if (conn->state != HTTP_SERVER_CONN_FREE) continue; j++; - socket_free(sock); + http_server_conn_free(conn); } - - secure_context_free(&pool->sec); - - if (pool->secure_sock != BAD_SOCKET) CLOSE_SOCKET(pool->secure_sock); - if (pool->listen_sock != BAD_SOCKET) CLOSE_SOCKET(pool->listen_sock); } -int socket_pool_add_cert(SocketPool *pool, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) +int http_server_listen_tcp(HTTP_Server *server, + String addr, Port port) { - return secure_context_add_cert(&pool->sec, domain, cert_file, key_file); -} - -void socket_pool_set_user_data(SocketPool *pool, SocketHandle handle, void *user_data) -{ - Socket *sock = &pool->socks[handle]; - socket_set_user_data(sock, user_data); -} - -void socket_pool_close(SocketPool *pool, SocketHandle handle) -{ - Socket *sock = &pool->socks[handle]; - socket_close(sock); -} - -static Socket *find_free_socket(SocketPool *pool) -{ - if (pool->num_socks == pool->max_socks) - return NULL; - - int i = 0; - while (pool->socks[i].state != SOCKET_STATE_FREE) - i++; - - return &pool->socks[i]; -} - -int socket_pool_connect(SocketPool *pool, bool secure, - HTTP_String addr, uint16_t port, void *user_data) -{ - Socket *sock = find_free_socket(pool); - if (sock == NULL) - return -1; - - socket_connect(sock, secure ? &pool->sec : NULL, addr, port, user_data); - - if (socket_died(sock)) { - socket_free(sock); - return -1; - } - - pool->num_socks++; - return 0; -} - -int socket_pool_connect_ipv4(SocketPool *pool, bool secure, - HTTP_IPv4 addr, uint16_t port, void *user_data) -{ - Socket *sock = find_free_socket(pool); - if (sock == NULL) - return -1; - - socket_connect_ipv4(sock, secure ? &pool->sec : NULL, addr, port, user_data); - - if (socket_died(sock)) { - socket_free(sock); - return -1; - } - - pool->num_socks++; - return 0; -} - -int socket_pool_connect_ipv6(SocketPool *pool, bool secure, - HTTP_IPv6 addr, uint16_t port, void *user_data) -{ - Socket *sock = find_free_socket(pool); - if (sock == NULL) - return -1; - - socket_connect_ipv6(sock, secure ? &pool->sec : NULL, addr, port, user_data); - - if (socket_died(sock)) { - socket_free(sock); - return -1; - } - - pool->num_socks++; - return 0; -} - -#include // TODO: remove - -SocketEvent socket_pool_wait(SocketPool *pool) -{ - for (;;) { - - // First, iterate over all sockets to find one that - // died or is ready. - - for (int i = 0, j = 0; j < pool->num_socks; i++) { - - Socket *sock = &pool->socks[i]; - - if (sock->state == SOCKET_STATE_FREE) - continue; - j++; - - if (socket_died(sock)) { - void *user_data = socket_get_user_data(sock); - socket_free(sock); - pool->num_socks--; - return (SocketEvent) { SOCKET_EVENT_DIED, -1, user_data }; - } - - if (socket_ready(sock)) - return (SocketEvent) { SOCKET_EVENT_READY, i, socket_get_user_data(sock) }; - - assert(sock->events); - } - - // If we reached this point, we either have no sockets - // or all sockets need to wait for some event. Waiting - // when no sockets are available is only allowed when - // the pool is in server mode. - - int indices[SOCKET_HARD_LIMIT+2]; - struct pollfd polled[SOCKET_HARD_LIMIT+2]; - int num_polled = 0; - - if (pool->num_socks < pool->max_socks) { - - if (pool->listen_sock != BAD_SOCKET) { - indices[num_polled] = -1; - polled[num_polled].fd = pool->listen_sock; - polled[num_polled].events = POLLIN; - polled[num_polled].revents = 0; - num_polled++; - } - - if (pool->secure_sock != BAD_SOCKET) { - indices[num_polled] = -1; - polled[num_polled].fd = pool->secure_sock; - polled[num_polled].events = POLLIN; - polled[num_polled].revents = 0; - num_polled++; - } - } - - for (int i = 0, j = 0; j < pool->num_socks; i++) { - - Socket *sock = &pool->socks[i]; - - if (sock->state == SOCKET_STATE_FREE) - continue; - j++; - - indices[num_polled] = i; - polled[num_polled].fd = sock->raw; - polled[num_polled].events = sock->events; - polled[num_polled].revents = 0; - num_polled++; - } - - if (num_polled == 0) - return (SocketEvent) { SOCKET_EVENT_ERROR, -1, NULL }; - - int ret = POLL(polled, num_polled, -1); - if (ret < 0) { - - if (errno == EINTR) - return (SocketEvent) { SOCKET_EVENT_SIGNAL, -1, NULL }; - - return (SocketEvent) { SOCKET_EVENT_ERROR, -1, NULL }; - } - - for (int i = 0; i < num_polled; i++) { - - Socket *sock; - - if (polled[i].fd == pool->listen_sock || polled[i].fd == pool->secure_sock) { - - bool secure = false; - if (polled[i].fd == pool->secure_sock) - secure = true; - - Socket *sock = find_free_socket(pool); - if (sock == NULL) - continue; - - RAW_SOCKET raw = accept(polled[i].fd, NULL, NULL); - if (raw == BAD_SOCKET) - continue; - - socket_accept(sock, secure ? &pool->sec : NULL, raw); - - if (socket_died(sock)) { - socket_free(sock); - continue; - } - - pool->num_socks++; - - } else { - int j = indices[i]; - sock = &pool->socks[j]; - - if (polled[i].revents) - socket_update(sock); - } - } - } - - // This branch is unreachable - return (SocketEvent) { SOCKET_EVENT_ERROR, -1, NULL }; -} - -int socket_pool_read(SocketPool *pool, SocketHandle handle, char *dst, int len) -{ - return socket_read(&pool->socks[handle], dst, len); -} - -int socket_pool_write(SocketPool *pool, SocketHandle handle, char *src, int len) -{ - return socket_write(&pool->socks[handle], src, len); -} - -bool socket_pool_secure(SocketPool *pool, SocketHandle handle) -{ - return socket_secure(&pool->socks[handle]); -} - -//////////////////////////////////////////////////////////////////////////////////////// -// src/client.c -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/client.c" -#include -#include -#include -#include - -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#define POLL WSAPoll -#endif - -#ifdef __linux__ -#include -#define POLL poll -#endif - -#ifndef HTTP_AMALGAMATION -#include "client.h" -#include "engine.h" -#include "socket_pool.h" -#endif - -#define CLIENT_MAX_CONNS 256 - -typedef enum { - CLIENT_CONNECTION_FREE, - CLIENT_CONNECTION_INIT, - CLIENT_CONNECTION_INIT_ERROR, - CLIENT_CONNECTION_WAIT, - CLIENT_CONNECTION_DONE, -} ClientConnectionState; - -typedef struct { - ClientConnectionState state; - uint16_t gen; - SocketHandle sock; - HTTP_Engine eng; - bool trace; - void* user_data; -} ClientConnection; - -struct HTTP_Client { - - SocketPool *socket_pool; - - int num_conns; - ClientConnection conns[CLIENT_MAX_CONNS]; - - int ready_head; - int ready_count; - int ready[CLIENT_MAX_CONNS]; -}; - -int http_global_init(void) -{ - int ret = socket_pool_global_init(); - if (ret < 0) + if (socket_manager_listen_tcp(&server->sockets, addr, port) < 0) return -1; return 0; } -void http_global_free(void) +int http_server_listen_tls(HTTP_Server *server, + String addr, Port port, String cert_file_name, + String key_file_name) { - socket_pool_global_free(); -} - -// Rename the memory function -static void* client_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) { - (void)data; - switch (tag) { - case HTTP_MEMFUNC_MALLOC: - return malloc(len); - case HTTP_MEMFUNC_FREE: - free(ptr); - return NULL; - } - return NULL; -} - -HTTP_Client *http_client_init(void) -{ - HTTP_Client *client = malloc(sizeof(HTTP_Client)); - if (client == NULL) - return NULL; - - int max_socks = 100; - SocketPool *socket_pool = socket_pool_init(HTTP_STR(""), 0, 0, max_socks, false, 0, HTTP_STR(""), HTTP_STR("")); - if (socket_pool == NULL) { - free(client); - return NULL; - } - client->socket_pool = socket_pool; - - for (int i = 0; i < CLIENT_MAX_CONNS; i++) { - client->conns[i].state = CLIENT_CONNECTION_FREE; - client->conns[i].gen = 1; - } - - client->num_conns = 0; - client->ready_head = 0; - client->ready_count = 0; - - return client; -} - -void http_client_free(HTTP_Client *client) -{ - for (int i = 0, j = 0; j < client->num_conns; i++) { - - if (client->conns[i].state == CLIENT_CONNECTION_FREE) - continue; - j++; - - // TODO - } - - socket_pool_free(client->socket_pool); - free(client); -} - -int http_client_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder) -{ - if (client->num_conns == CLIENT_MAX_CONNS) + if (socket_manager_listen_tls(&server->sockets, addr, + port, cert_file_name, key_file_name) < 0) return -1; - - int i = 0; - while (client->conns[i].state != CLIENT_CONNECTION_FREE) - i++; - - client->conns[i].sock = -1; - client->conns[i].user_data = NULL; - client->conns[i].trace = false; - client->conns[i].state = CLIENT_CONNECTION_INIT; - http_engine_init(&client->conns[i].eng, 1, client_memfunc, NULL); - - client->num_conns++; - - *builder = (HTTP_RequestBuilder) { client, i, client->conns[i].gen }; return 0; } -int http_client_wait(HTTP_Client *client, HTTP_Response **result, void **user_data) +int http_server_add_certificate(HTTP_Server *server, + String domain, String cert_file, String key_file) { - while (client->ready_count == 0) { + if (socket_manager_add_certificate(&server->sockets, + domain, cert_file, key_file) < 0) + return -1; + return 0; +} - SocketEvent event = socket_pool_wait(client->socket_pool); - switch (event.type) { +int http_server_wakeup(HTTP_Server *server) +{ + if (socket_manager_wakeup(&server->sockets) < 0) + return -1; + return 0; +} - case SOCKET_EVENT_DIED: - { - ClientConnection *conn = event.user_data; - conn->state = CLIENT_CONNECTION_DONE; +int http_server_register_events(HTTP_Server *server, + struct pollfd *polled, int max_polled) +{ + return socket_manager_register_events(&server->sockets, polled, max_polled); +} - int tail = (client->ready_head + client->ready_count) % CLIENT_MAX_CONNS; - client->ready[tail] = conn - client->conns; - client->ready_count++; - } - break; +// Look at the head of the input buffer to see if +// a request was buffered. If it was, change the +// connection's status to WAIT_STATUS and push it +// to the ready queue. If the request is invalid, +// close the socket. +static void +check_request_buffer(HTTP_Server *server, HTTP_ServerConn *conn) +{ + assert(conn->state == HTTP_SERVER_CONN_BUFFERING); - case SOCKET_EVENT_READY: - { - ClientConnection *conn = event.user_data; + ByteView src = byte_queue_read_buf(&conn->input); + int ret = http_parse_request(src.ptr, src.len, &conn->request); + if (ret < 0) { - if (conn->sock == -1) - conn->sock = event.handle; + // Invalid request + byte_queue_read_ack(&conn->input, 0); + socket_close(&server->sockets, conn->handle); - HTTP_EngineState engine_state; - engine_state = http_engine_state(&conn->eng); + } else if (ret == 0) { - if (engine_state == HTTP_ENGINE_STATE_CLIENT_RECV_BUF) { - int len; - char *buf; - buf = http_engine_recvbuf(&conn->eng, &len); - if (buf) { - int ret = socket_pool_read(client->socket_pool, conn->sock, buf, len); - if (conn->trace) - print_bytes(HTTP_STR(">> "), (HTTP_String) { buf, ret }); - http_engine_recvack(&conn->eng, ret); - } - } else if (engine_state == HTTP_ENGINE_STATE_CLIENT_SEND_BUF) { - int len; - char *buf; - buf = http_engine_sendbuf(&conn->eng, &len); - if (buf) { - int ret = socket_pool_write(client->socket_pool, conn->sock, buf, len); - if (conn->trace) - print_bytes(HTTP_STR("<< "), (HTTP_String) { buf, ret }); - http_engine_sendack(&conn->eng, ret); - } - } + // Still waiting + byte_queue_read_ack(&conn->input, 0); - engine_state = http_engine_state(&conn->eng); + // If the queue reached its limit and we still didn't receive + // a complete request, abort the exchange. + if (byte_queue_full(&conn->input)) + socket_close(&server->sockets, conn->handle); - if (engine_state == HTTP_ENGINE_STATE_CLIENT_CLOSED || - engine_state == HTTP_ENGINE_STATE_CLIENT_READY) - socket_pool_close(client->socket_pool, conn->sock); - } - break; - - case SOCKET_EVENT_ERROR: - return -1; - - case SOCKET_EVENT_SIGNAL: - return 1; - } - } - - int index = client->ready[client->ready_head]; - client->ready_head = (client->ready_head + 1) % CLIENT_MAX_CONNS; - client->ready_count--; - - ClientConnection *conn = &client->conns[index]; - - HTTP_Response *result2 = http_engine_getres(&conn->eng); - - if (result) - *result = result2; - - if (user_data) - *user_data = conn->user_data; - - if (result2 == NULL) { - http_engine_free(&conn->eng); - conn->state = CLIENT_CONNECTION_FREE; - client->num_conns--; } else { - result2->context = client; + + // Ready + assert(ret == 1); + + conn->state = HTTP_SERVER_CONN_STATUS; + conn->request_len = ret; + conn->response_offset = byte_queue_offset(&conn->output); + + // Push to the ready queue + assert(server->num_ready < HTTP_SERVER_CAPACITY); + int tail = (server->ready_head + server->num_ready) % HTTP_SERVER_CAPACITY; + server->ready[tail] = conn - server->conns; + server->num_ready++; + } +} + +bool http_server_next_request(HTTP_Server *server, + HTTP_Request **request, HTTP_ResponseBuilder *builder) +{ + if (server->num_ready == 0) + return false; + + HTTP_ServerConn *conn = &server->conns[server->ready_head]; + server->ready_head = (server->ready_head + 1) % HTTP_SERVER_CAPACITY; + server->num_ready--; + + assert(conn->state == HTTP_SERVER_CONN_WAIT_STATUS); + *request = &conn->request; + *builder = (HTTP_ResponseBuilder) { server, conn - server->conns, conn->gen }; + return true; +} + +int http_server_process_events(HTTP_Server *server, + struct pollfd *polled, int num_polled) +{ + SocketEvent events[HTTP_SERVER_CAPACITY]; + int num_events = socket_manger_translate_events(&server->sockets, polled, num_polled); + if (num_events < 0) + return -1; + + for (int i = 0; i < num_events; i++) { + + HTTP_ServerConn *conn = events[i].user; + + if (events[i].type == SOCKET_EVENT_DISCONNECT) { + + http_server_conn_free(conn); + server->num_conns--; + + } else if (events[i].type == SOCKET_EVENT_READY) { + + if (events[i].user == NULL) { + + if (server->num_conns == HTTP_SERVER_CAPACITY) { + socket_close(&server->sockets, events[i].handle); + continue; + } + + int i = 0; + while (server->conns[i].state != HTTP_SERVER_CONN_FREE) { + i++; + assert(i < HTTP_SERVER_CAPACITY); + } + + conn = &server->conns[i]; + http_server_conn_init(conn, events[i].handle); + server->num_conns++; + + socket_set_user(&server->sockets, events[i].handle, conn); + } + + if (conn->state == HTTP_SERVER_CONN_BUFFERING) { + + int min_recv = 1<<10; + byte_queue_write_setmincap(&conn->input, min_recv); + + // Note that it's extra important that we don't + // buffer while the user is building the response. + // If we did that, a resize would invalidate all + // pointers on the parsed request structure. + int num = 0; + ByteView dst = byte_queue_write_buf(&conn->input); + if (dst.len) num = socket_recv(&server->sockets, conn->handle, dst.ptr, dst.len); + byte_queue_write_ack(&conn->input, num); + + if (byte_queue_error(&conn->output)) + socket_close(&server->sockets, conn->handle); + else + check_request_buffer(server, conn); + + } else if (conn->state == HTTP_SERVER_CONN_FLUSHING) { + + int num = 0; + ByteView src = byte_queue_read_buf(&conn->output); + if (src.len) num = socket_recv(&server->sockets, conn->handle, src.ptr, src.len); + byte_queue_read_ack(&conn->output, num); + + if (byte_queue_error(&conn->output)) + socket_close(&server->sockets, conn->handle); + else if (byte_queue_empty(&conn->output)) { + // We finished sending the response. Now we can + // either close the connection or process a new + // buffered request. + if (conn->closing) { + socket_close(&server->sockets, conn->handle); + } else { + check_request_buffer(server, conn); + } + } + } + } } return 0; } -static ClientConnection *client_builder_to_conn(HTTP_RequestBuilder handle) +// Get a connection pointer from a response builder. +// If the builder is invalid, returns NULL. +// Note that only connections in the responding states +// can be returned, as any builder is invalidated by +// incrementing the connection's generation counter +// when a response is completed. +static HTTP_ServerConn* +builder_to_conn(HTTP_ResponseBuilder builder) { - if (handle.data0 == NULL) + HTTP_Server *server = builder.server; + if (server == NULL) return NULL; - HTTP_Client *client = handle.data0; - - if (handle.data1 >= CLIENT_MAX_CONNS) + if (server->index > HTTP_SERVER_CAPACITY) return NULL; - ClientConnection *conn = &client->conns[handle.data1]; - - if (handle.data2 != conn->gen) + HTTP_ServerConn *conn = server->conns[server->index]; + if (conn->gen != builder.gen) return NULL; return conn; } -void http_request_builder_user_data(HTTP_RequestBuilder builder, void *user_data) +static void +write_status(HTTP_ServerConn *conn, int status) { - ClientConnection *conn = client_builder_to_conn(builder); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT) - return; - - conn->user_data = user_data; + byte_queue_write(&conn->output, xxx); } -void http_request_builder_trace(HTTP_RequestBuilder builder, bool trace) +void http_response_builder_status(HTTP_ResponseBuilder builder, int status) { - ClientConnection *conn = client_builder_to_conn(builder); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT) - return; - - conn->trace = trace; -} - -void http_request_builder_line(HTTP_RequestBuilder builder, HTTP_Method method, HTTP_String url) -{ - ClientConnection *conn = client_builder_to_conn(builder); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT) - return; - - HTTP_Client *client = builder.data0; - - HTTP_URL parsed_url; - int ret = http_parse_url(url.ptr, url.len, &parsed_url); - if (ret != url.len) { - conn->state = CLIENT_CONNECTION_INIT_ERROR; - return; - } - - bool secure = false; - if (http_streq(parsed_url.scheme, HTTP_STR("https"))) { - secure = true; - } else if (!http_streq(parsed_url.scheme, HTTP_STR("http"))) { - conn->state = CLIENT_CONNECTION_INIT_ERROR; - return; - } - - int port = parsed_url.authority.port; - if (port == 0) { - if (secure) - port = 443; - else - port = 80; - } - - switch (parsed_url.authority.host.mode) { - case HTTP_HOST_MODE_IPV4: ret = socket_pool_connect_ipv4(client->socket_pool, secure, parsed_url.authority.host.ipv4, port, conn); break; - case HTTP_HOST_MODE_IPV6: ret = socket_pool_connect_ipv6(client->socket_pool, secure, parsed_url.authority.host.ipv6, port, conn); break; - case HTTP_HOST_MODE_NAME: ret = socket_pool_connect (client->socket_pool, secure, parsed_url.authority.host.name, port, conn); break; - case HTTP_HOST_MODE_VOID: ret = -1; return; - } - - if (ret < 0) { - conn->state = CLIENT_CONNECTION_INIT_ERROR; - return; - } - - http_engine_url(&conn->eng, method, url, 1); -} - -void http_request_builder_header(HTTP_RequestBuilder handle, HTTP_String str) -{ - ClientConnection *conn = client_builder_to_conn(handle); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT) - return; - - http_engine_header(&conn->eng, str); -} - -void http_request_builder_body(HTTP_RequestBuilder handle, HTTP_String str) -{ - ClientConnection *conn = client_builder_to_conn(handle); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT) - return; - - http_engine_body(&conn->eng, str); -} - -void http_request_builder_submit(HTTP_RequestBuilder handle) -{ - HTTP_Client *client = handle.data0; - ClientConnection *conn = client_builder_to_conn(handle); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT && - conn->state != CLIENT_CONNECTION_INIT_ERROR) - return; - - // TODO: invalidate the handle - - if (conn->state == CLIENT_CONNECTION_INIT_ERROR) { - - conn->state = CLIENT_CONNECTION_DONE; - - int tail = (client->ready_head + client->ready_count) % CLIENT_MAX_CONNS; - client->ready[tail] = conn - client->conns; - client->ready_count++; - - } else { - http_engine_done(&conn->eng); - conn->state = CLIENT_CONNECTION_WAIT; - } -} - -void http_response_free(HTTP_Response *res) -{ - if (res == NULL) - return; - - HTTP_Client *client = res->context; - - ClientConnection *conn = NULL; - for (int i = 0, j = 0; j < client->num_conns; i++) { - - if (client->conns[i].state == CLIENT_CONNECTION_FREE) - continue; - j++; - - if (client->conns[i].state != CLIENT_CONNECTION_DONE) - continue; - - if (http_engine_getres(&client->conns[i].eng) == res) { - conn = &client->conns[i]; - break; - } - } - - HTTP_ASSERT(conn); - - http_engine_free(&conn->eng); - conn->state = CLIENT_CONNECTION_FREE; - client->num_conns--; -} - -static HTTP_Client *default_client___; // TODO: deinitialize the default client when http_global_free is called - -static HTTP_Client *get_default_client(void) -{ - if (default_client___ == NULL) - default_client___ = http_client_init(); - return default_client___; -} - -HTTP_Response *http_get(HTTP_String url, HTTP_String *headers, int num_headers) -{ - HTTP_Client *client = get_default_client(); - if (client == NULL) - return NULL; - - HTTP_RequestBuilder builder; - int ret = http_client_get_builder(client, &builder); - if (ret < 0) - return NULL; - http_request_builder_line(builder, HTTP_METHOD_GET, url); - for (int i = 0; i < num_headers; i++) - http_request_builder_header(builder, headers[i]); - http_request_builder_submit(builder); - - HTTP_Response *res; - ret = http_client_wait(client, &res, NULL); // TODO: it's assumed there is only one request pending - if (ret < 0) - return NULL; - - return res; -} - -HTTP_Response *http_post(HTTP_String url, HTTP_String *headers, int num_headers, HTTP_String body) -{ - HTTP_Client *client = get_default_client(); - if (client == NULL) - return NULL; - - HTTP_RequestBuilder builder; - int ret = http_client_get_builder(client, &builder); - if (ret < 0) - return NULL; - http_request_builder_line(builder, HTTP_METHOD_POST, url); - for (int i = 0; i < num_headers; i++) - http_request_builder_header(builder, headers[i]); - http_request_builder_body(builder, body); - http_request_builder_submit(builder); - - HTTP_Response *res; - ret = http_client_wait(client, &res, NULL); // TODO: it's assumed there is only one request pending - if (ret < 0) - return NULL; - - return res; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// src/server.c -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/server.c" -#include -#include -#include -#include - -#ifndef HTTP_AMALGAMATION -#include "engine.h" -#include "server.h" -#include "socket_pool.h" -#endif - -#define MAX_CONNS (1<<10) - -typedef struct { - bool used; - uint16_t gen; - HTTP_Engine engine; - SocketHandle sock; -} Connection; - -struct HTTP_Server { - - SocketPool *socket_pool; - - int num_conns; - Connection conns[MAX_CONNS]; - - int ready_head; - int ready_count; - int ready[MAX_CONNS]; -}; - -HTTP_Server *http_server_init(HTTP_String addr, uint16_t port) -{ - return http_server_init_ex(addr, port, 0, HTTP_STR(""), HTTP_STR("")); -} - -HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port, - uint16_t secure_port, HTTP_String cert_file, HTTP_String key_file) -{ - HTTP_Server *server = malloc(sizeof(HTTP_Server)); - if (server == NULL) - return NULL; - - int backlog = 32; - bool reuse_addr = true; - SocketPool *socket_pool = socket_pool_init(addr, port, secure_port, MAX_CONNS, reuse_addr, backlog, cert_file, key_file); - if (socket_pool == NULL) { - free(server); - return NULL; - } - - server->socket_pool = socket_pool; - server->num_conns = 0; - server->ready_head = 0; - server->ready_count = 0; - - for (int i = 0; i < MAX_CONNS; i++) { - server->conns[i].used = false; - server->conns[i].gen = 1; - } - - return server; -} - -void http_server_free(HTTP_Server *server) -{ - for (int i = 0, j = 0; j < server->num_conns; i++) { - - if (!server->conns[i].used) - continue; - j++; - - // TODO - } - - socket_pool_free(server->socket_pool); - free(server); -} - -int http_server_add_website(HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) -{ - return socket_pool_add_cert(server->socket_pool, domain, cert_file, key_file); -} - -static void* server_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) { - (void)data; - switch (tag) { - case HTTP_MEMFUNC_MALLOC: - return malloc(len); - case HTTP_MEMFUNC_FREE: - free(ptr); - return NULL; - } - return NULL; -} - -int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseBuilder *builder) -{ - while (server->ready_count == 0) { - - SocketEvent event = socket_pool_wait(server->socket_pool); - switch (event.type) { - - case SOCKET_EVENT_DIED: - { - Connection *conn = event.user_data; - if (conn) { - http_engine_free(&conn->engine); - conn->used = false; - conn->gen++; - server->num_conns--; - } - } - break; - - case SOCKET_EVENT_READY: - { - Connection *conn = event.user_data; - if (conn == NULL) { - - // Connection was just accepted - - if (server->num_conns == MAX_CONNS) { - socket_pool_close(server->socket_pool, event.handle); - break; - } - - int i = 0; - while (server->conns[i].used) - i++; - - conn = &server->conns[i]; - conn->used = true; - conn->sock = event.handle; - http_engine_init(&conn->engine, 0, server_memfunc, NULL); - socket_pool_set_user_data(server->socket_pool, event.handle, conn); - server->num_conns++; - } - - switch (http_engine_state(&conn->engine)) { - - int len; - char *buf; - - case HTTP_ENGINE_STATE_SERVER_RECV_BUF: - buf = http_engine_recvbuf(&conn->engine, &len); - if (buf) { - int ret = socket_pool_read(server->socket_pool, conn->sock, buf, len); - http_engine_recvack(&conn->engine, ret); - } - break; - - case HTTP_ENGINE_STATE_SERVER_SEND_BUF: - buf = http_engine_sendbuf(&conn->engine, &len); - if (buf) { - int ret = socket_pool_write(server->socket_pool, conn->sock, buf, len); - http_engine_sendack(&conn->engine, ret); - } - break; - - default: - break; - } - - switch (http_engine_state(&conn->engine)) { - - int tail; - - case HTTP_ENGINE_STATE_SERVER_PREP_STATUS: - tail = (server->ready_head + server->ready_count) % MAX_CONNS; - server->ready[tail] = conn - server->conns; - server->ready_count++; - break; - - case HTTP_ENGINE_STATE_SERVER_CLOSED: - socket_pool_close(server->socket_pool, conn->sock); - break; - - default: - break; - } - } - break; - - case SOCKET_EVENT_ERROR: - return -1; - - case SOCKET_EVENT_SIGNAL: - return 1; - } - } - - int index = server->ready[server->ready_head]; - server->ready_head = (server->ready_head + 1) % MAX_CONNS; - server->ready_count--; - - *req = http_engine_getreq(&server->conns[index].engine); - (*req)->secure = socket_pool_secure(server->socket_pool, server->conns[index].sock); - - *builder = (HTTP_ResponseBuilder) { server, index, server->conns[index].gen }; - return 0; -} - -static Connection* -server_builder_to_conn(HTTP_ResponseBuilder builder) -{ - HTTP_Server *server = builder.data0; - if (builder.data1 >= MAX_CONNS) - return NULL; - - Connection *conn = &server->conns[builder.data1]; - if (conn->gen != builder.data2) - return NULL; - - return conn; -} - -void http_response_builder_status(HTTP_ResponseBuilder res, int status) -{ - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; - - http_engine_status(&conn->engine, status); -} - -void http_response_builder_header(HTTP_ResponseBuilder res, HTTP_String str) -{ - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; - - http_engine_header(&conn->engine, str); -} - -void http_response_builder_body(HTTP_ResponseBuilder res, HTTP_String str) -{ - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; - - http_engine_body(&conn->engine, str); -} - -void http_response_builder_bodycap(HTTP_ResponseBuilder res, int mincap) -{ - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; - - http_engine_bodycap(&conn->engine, mincap); -} - -char *http_response_builder_bodybuf(HTTP_ResponseBuilder res, int *cap) -{ - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) { - *cap = 0; - return NULL; - } - - return http_engine_bodybuf(&conn->engine, cap); -} - -void http_response_builder_bodyack(HTTP_ResponseBuilder res, int num) -{ - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; - - http_engine_bodyack(&conn->engine, num); -} - -void http_response_builder_undo(HTTP_ResponseBuilder res) -{ - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; - - http_engine_undo(&conn->engine); -} - -void http_response_builder_done(HTTP_ResponseBuilder res) -{ - HTTP_Server *server = res.data0; - Connection *conn = server_builder_to_conn(res); + HTTP_ServerConn *conn = builder_to_conn(builder); if (conn == NULL) return; - http_engine_done(&conn->engine); + if (conn->state != HTTP_SERVER_CONN_WAIT_STATUS) { + // Reset all response content and start from scrach. + byte_queue_remove_from_offset(&conn->output, conn->response_offset); + conn->state = HTTP_SERVER_CONN_WAIT_STATUS; + } + write_status(conn, status); + + conn->state = HTTP_SERVER_CONN_WAIT_HEADER; +} + +void http_response_builder_header(HTTP_ResponseBuilder builder, String str) +{ + HTTP_ServerConn *conn = builder_to_conn(builder); + if (conn == NULL) + return; + + if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER) + return; + + byte_queue_write(&conn->output, xxx); +} + +static void append_special_headers(HTTP_ServerConn *conn) +{ + // TODO +} + +static void patch_special_headers(HTTP_ServerConn *conn) +{ + // TODO +} + +void http_response_builder_body(HTTP_ResponseBuilder builder, String str) +{ + HTTP_ServerConn *conn = builder_to_conn(builder); + if (conn == NULL) + return; + + if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER) { + append_special_headers(conn); + conn->state = HTTP_SERVER_CONN_WAIT_BODY; + } + + if (conn->state != HTTP_SERVER_CONN_WAIT_BODY) + return; + + byte_queue_write(&conn->output, str); +} + +void http_response_builder_send(HTTP_ResponseBuilder builder, String str) +{ + HTTP_ServerConn *conn = builder_to_conn(builder); + if (conn == NULL) + return; + + if (conn->state == HTTP_SERVER_CONN_WAIT_STATUS) { + write_status(conn, 500); + conn->state = HTTP_SERVER_CONN_WAIT_HEADER; + } + + if (conn->state == HTTP_SERVER_CONN_WAIT_HEADER) { + append_special_headers(conn); + conn->state = HTTP_SERVER_CONN_WAIT_BODY; + } + + assert(conn->state == HTTP_SERVER_CONN_WAIT_BODY); + patch_special_headers(conn); + + // Remove the buffered request + byte_queue_read_ack(&conn->input, conn->request_len); + + conn->state = HTTP_SERVER_CONN_FLUSHING; conn->gen++; - if (conn->gen == 0 || conn->gen == UINT16_MAX) - conn->gen = 1; - - HTTP_EngineState state = http_engine_state(&conn->engine); - - if (state == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) { - int tail = (server->ready_head + server->ready_count) % MAX_CONNS; - server->ready[tail] = res.data1; - server->ready_count++; - } - - if (state == HTTP_ENGINE_STATE_SERVER_CLOSED) - socket_pool_close(server->socket_pool, conn->sock); } diff --git a/chttp.h b/chttp.h index 0c8548e..80611fc 100644 --- a/chttp.h +++ b/chttp.h @@ -1,18 +1,29 @@ -#ifndef HTTP_AMALGAMATION -#define HTTP_AMALGAMATION +// cHTTP, an HTTP client and server library! +// +// This file was generated automatically. Do not modify directly. +// +// Refer to the end of this file for the license +//////////////////////////////////////////////////////////////////////////////////////// +// src/includes.h +//////////////////////////////////////////////////////////////////////////////////////// -// This file was generated automatically. Do not modify directly! +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#else +#include +#include +#include +#endif //////////////////////////////////////////////////////////////////////////////////////// // src/basic.h //////////////////////////////////////////////////////////////////////////////////////// -#line 1 "src/basic.h" -#ifndef CHTTP_BASIC_INCLUDED -#define CHTTP_BASIC_INCLUDED - -#include - // String type used throughout cHTTP. typedef struct { char *ptr; @@ -63,45 +74,642 @@ void print_bytes(HTTP_String prefix, HTTP_String src); // TODO: comment #define HTTP_UNPACK(X) (X).len, (X).ptr -// Macro used to make invariants of the code more explicit. +//////////////////////////////////////////////////////////////////////////////////////// +// src/secure_context.h +//////////////////////////////////////////////////////////////////////////////////////// + +int global_secure_context_init(void); +int global_secure_context_free(void); + +typedef struct { +#ifdef HTTPS_ENABLED + // TODO + SSL_CTX *p; +#endif +} ClientSecureContext; + +int client_secure_context_init(ClientSecureContext *ctx); +int client_secure_context_free(ClientSecureContext *ctx); + +typedef struct { + +} SecureDomain; + +typedef struct { +#ifdef HTTPS_ENABLED + // TODO + SSL_CTX *p; +#endif +} ServerSecureContext; + +int server_secure_context_init(ServerSecureContext *ctx); +int server_secure_context_free(ServerSecureContext *ctx); +int server_secure_context_add_certificate(ServerSecureContext *ctx, + String domain, String cert_file, String key_file); + +//////////////////////////////////////////////////////////////////////////////////////// +// src/socket.h +//////////////////////////////////////////////////////////////////////////////////////// +// This file (and its relative .c file) implements an asynchronous TCP/TLS +// server and client abstraction. // -// Say you have some function that operates on two integers -// and that by design their sum is always 100. This macro is -// useful to make that explicit: +// It introduces the concept of a "socket manager", which is a pool of +// connection sockets and a listener socket. The listener is managed +// internally, which means the manager automatically accepts sockets +// from it and adds them to the pool. // -// void func(int a, int b) -// { -// HTTP_ASSERT(a + b == 100); -// ... +// If the listener is configured using the function: +// +// socket_manager_listen_tcp +// +// the resulting connections will not use TLS. If instead the listener +// is configured using: +// +// socket_manager_listen_tls +// +// the listener will use TLS. Note that both functions can be used on +// the same manager to allow both plaintext and encrypted connections. +// Users may enable zero listeners, in which case only outgoing +// connections are allowed (more on this later). +// +// Once the manager is set up, one can wait for events by following +// this pattern: +// +// struct pollfd polled[...]; +// int num_polled = socket_manager_register_events(sm, polled, max_polled); +// poll(polled, num_polled, -1); +// +// #define MAX_EVENTS ... +// SocketEvent events[MAX_EVENTS]; +// int num_events = socket_manager_translate_events(sm, events, MAX_EVENTS, polled, num_polled); +// for (int i = 0; i < num_events; i++) { +// ... Here call socket_recv, socket_send, socket_close, ... // } // -// Assertions are about documentation, *not* error management. +// Note that from the user's perspective, there is no difference +// between connections that use plain TCP and those that use TCP/TLS. // -// In non-release builds (where NDEBUG is not defined) asserted -// expressions are evaluated and if not true, the program is halted. -// This is quite nice as they offer a way to document code in -// a way that can be checked at runtime, unlike regular comments -// like this one. -#ifdef NDEBUG -#define HTTP_ASSERT(X) ((void) 0) +// Users can also establish outgoing connections by calling the +// function: +// +// socket_connect +// +// Which allows the creation of a connection towards an host given +// its domain, IPv4, IPv6, or an array of them. This can be done both +// for TCP and TCP/TLS connection. Note that users that only intend +// to establish outgoing connection may omit the configuration of +// listeners entirely. + +#ifdef _WIN32 +#define NATIVE_SOCKET SOCKET +#define NATIVE_SOCKET_INVALID SOCKET_ERROR +#define CLOSE_NATIVE_SOCKET closesocket #else -#define HTTP_ASSERT(X) {if (!(X)) { __builtin_trap(); }} +#define NATIVE_SOCKET int +#define NATIVE_SOCKET_INVALID -1 +#define CLOSE_NATIVE_SOCKET close #endif -#endif // CHTTP_BASIC_INCLUDED +typedef uint32_t SocketHandle; +#define SOCKET_HANDLE_INVALID ((SocketHandle) 0) + +typedef struct { + char *ptr; + int len; +} String; + +typedef uint16_t Port; + +typedef struct { + uint32_t data; +} IPv4; + +typedef struct { + uint16_t data[8]; +} IPv6; + +typedef enum { + SOCKET_EVENT_READY, + SOCKET_EVENT_DISCONNECT, +} SocketEventType; + +typedef struct { + SocketEventType type; + SocketHandle handle; + void* user; +} SocketEvent; + +// Internal use only +typedef enum { + + // The Socket struct is unused + SOCKET_STATE_FREE, + + // The state associated to a socket created + // by a connect operation that hasn't been + // processed yet. + SOCKET_STATE_PENDING, + + // A connect() operation was started but is + // still pending. + SOCKET_STATE_CONNECTING, + + // The connection was estabished, but a TLS + // handshake may need to be performed. + SOCKET_STATE_CONNECTED, + + // The connection was esablished, but the user + // wants to perform a read or write operation that + // would block. + SOCKET_STATE_ESTABLISHED_WAIT, + + // The connection was established and it's possible + // to perform read or write operations on it without + // blocking. + SOCKET_STATE_ESTABLISHED_READY, + + // The socket was marked to be closed. + SOCKET_STATE_SHUTDOWN, + + // The current socket is was closed. The only + // valid thing to do here is free its resources. + SOCKET_STATE_DIED, +} SocketState; + +// Internal use only +typedef struct { + union { + IPv4 ipv4; + IPv6 ipv6; + }; + bool is_ipv4; + Port port; +} AddressAndPort; + +// Internal use only +typedef struct { + SocketState state; + + // OS-specific socket type + NATIVE_SOCKET sock; + + // Native socket events that need to be monitored + int events; + + // User-provided context pointer + void *user; + + // A single connect operation may involve + // trying to establish a connection towards + // one of a set of addresses. + int num_addr; + int next_addr; + union { + AddressAndPort addr; // When num_addr=1 + AddressAndPort *addrs; // Dynamically allocated when num_addr>1 + }; +} Socket; + +// Glorified array of sockets. This structure +// is private to the .c file associated to this +// header. +typedef struct { + + // This guards access to the main thread using + // the manager from other threads calling the + // wakeup function. + Mutex mutex; + + // TCP listener sockets. The first is intended + // for plaintext, while the second is for TLS. + // The socket manager will accept and add new + // sockets to the pool automatically. Note that + // either may be unset. If both are unset, users + // can only create outgoing connections. + NATIVE_SOCKET plain_sock; + NATIVE_SOCKET secure_sock; + + // Handles for the self-pipe trick necessary for + // other threads to wake up sockets blocked on + // poll(). + NATIVE_SOCKET wait_sock; + NATIVE_SOCKET signal_sock; + + // TLS contexts. One is used for outgoing connections + // (the client context) and one for incoming + // connections (server). If the secure_sock is + // set, the server context is initialized. If at + // least one connect was performed using TLS + // (and the flag is set), the client context is + // initialized. + bool at_least_one_secure_connect; + ClientSecureContext client_secure_context; + ServerSecureContext server_secure_context; + + // Array of sockets. Structs with state FREE + // are unused. + int num_used; + int max_used; + Socket *sockets; + +} SocketManager; + +// Instanciate a socket manager. Returns 0 on +// success and -1 on error. +int socket_manager_init(SocketManager *sm, Socket *socks, + int num_socks); + +// Deinitialize a socket manager +void socket_manager_free(SocketManager *sm); + +// Configure the socket manager to listen on +// the specified interface for TCP connections. +// Incoming connections will be automatically +// added to the internal pool. This function +// can only be used once per manager. +// Returns 0 on success, -1 on error. +int socket_manager_listen_tcp(SocketManager *sm, + String addr, Port port); + +// Same as the previous function, but incoming +// connections will be interpreted as TLS. You +// can only call this function once per manager, +// but you can call this and the plaintext variant +// on the same manager to accept both plaintext +// and secure connections. +// Returns 0 on success, -1 on error. +int socket_manager_listen_tls(SocketManager *sm, + String addr, Port port, String cert_file_name, + String key_file_name); + +// If the socket manager was configures to accept +// TLS connections, this adds additional certificates +// the client can use to verify the server's +// authenticity. +// Returns 0 on success, -1 on error. +int socket_manager_add_certificate(SocketManager *sm, + String domain, String cert_file, String key_file); + +// When a thread is blocked on a poll() call for +// descriptors associated to this socket manager, +// other threads can call this function to wake +// up that blocked thread. +// Returns 0 on success, -1 on error. +int socket_manager_wakeup(SocketManager *sm); + +// Writes to the polled array the list of sockets +// that the socket manager is monitoring and which +// events associated to that socket. +// Returns the number of items written to the polled +// array. +int socket_manager_register_events(SocketManager *sm, + struct pollfd *polled, int max_polled); + +// After poll() is called on the previously registered +// pollfd array and the revents fields are set, this +// function processes those events to produce higher-level +// socket events. Returns the number of socket events +// written to the output array, or -1 on error. +int socket_manager_translate_events(SocketManager *sm, + SocketEvent *events, int max_events, struct pollfd *polled, + int num_polled); + +typedef enum { + CONNECT_TARGET_NAME, + CONNECT_TARGET_IPV4, + CONNECT_TARGET_IPV6, +} ConnectTargetType; + +typedef struct { + ConnectTargetType type; + Port port; + union { + IPv4 ipv4; + IPv6 ipv6; + String name; + }; +} ConnectTarget; + +// Connect to one of the given targets. The socket +// manager will try to connecting to addresses until +// one succedes. If secure=true, the socket uses TLS. +// Returns 0 on success, -1 on error. +int socket_connect(SocketManager *sm, int num_targets, + ConnectTarget *targets, bool secure, void *user); + +int socket_recv(SocketManager *sm, SocketHandle handle, + char *dst, int max); + +int socket_send(SocketManager *sm, SocketHandle handle, + char *src, int len); + +void socket_close(SocketManager *sm, SocketHandle handle); + +// Returns -1 on error, 0 if the socket was accepted +// from the plaintext listener, or 1 if it was accepted +// by the secure listener. +int socket_is_secure(SocketManager *sm, SocketHandle handle); + +// Set the user pointer of a socket +void socket_set_user(SocketManager *sm, SocketHandle handle); + +//////////////////////////////////////////////////////////////////////////////////////// +// src/thread.h +//////////////////////////////////////////////////////////////////////////////////////// + +typedef struct { + // TODO +} Mutex; + +int mutex_init(Mutex *mutex); +int mutex_free(Mutex *mutex); +int mutex_lock(Mutex *mutex); +int mutex_unlock(Mutex *mutex); + +//////////////////////////////////////////////////////////////////////////////////////// +// src/byte_queue.h +//////////////////////////////////////////////////////////////////////////////////////// +// This is the implementation of a byte queue useful +// for systems that need to process engs of bytes. +// +// It features sticky errors, a zero-copy interface, +// and a safe mechanism to patch previously written +// bytes. +// +// Only up to 4GB of data can be stored at once. + +// Internal use only +enum { + BYTE_QUEUE_ERROR = 1 << 0, + BYTE_QUEUE_READ = 1 << 1, + BYTE_QUEUE_WRITE = 1 << 2, +}; + +typedef struct { + uint8_t *ptr; + size_t len; +} ByteView; + +// Fields are for internal use only +typedef struct { + uint64_t curs; + uint8_t* data; + uint32_t head; + uint32_t size; + uint32_t used; + uint32_t limit; + uint8_t* read_target; + uint32_t read_target_size; + int flags; +} ByteQueue; + +// Represents an offset inside the queue relative +// to the first byte ever appended to the queue, +// therefore consuming bytes from the queue does +// not invalidate this type of offset. +typedef uint64_t ByteQueueOffset; + +// Initialize the queue with a given capacity limit. +// This is just a soft limit. The queue will allocate +// dynamically as needed up to this limit and won't +// grow further. When the limit is reached, http_queue_full +// returns true. +void byte_queue_init(ByteQueue *queue, uint32_t limit); + +// Free resources associated to this queue +void byte_queue_free(ByteQueue *queue); + +// Check whether an error occurred inside the queue +int byte_queue_error(ByteQueue *queue); + +// Returns 1 if the queue has no bytes inside it, +// or 0 otherwise. +int byte_queue_empty(ByteQueue *queue); + +// Returns 1 if the queue reached its limit, or 0 +// otherwise. +int byte_queue_full(ByteQueue *queue); + +// These two functions are to be used together. +// read_buf returns a view into the queue of the +// bytes that can be read from it. The caller can +// decide how many of those bytes can be removed +// by passing the count to the read_ack function. +// If an error occurred inside the queue, this +// function returns an empty view. +// +// Note that the calls to read_buf and read_ack +// may be far apart. Other operations won't interfere +// with the read. The only rule is you can't call +// read_buf multiple times before calling read_ack. +ByteView byte_queue_read_buf(ByteQueue *queue); +void byte_queue_read_ack(ByteQueue *queue, uint32_t num); + +// Similar to the read_buf/read_ack functions, +// but write_buf returns a view of the unused +// memory inside the queue, and write_ack is +// used to tell the queue how many bytes were +// written into it. Note that to ensure there +// is a minimum amount of free space in the queue, +// the user needs to call byte_queue_setmincap. +// If an error occurred inside the queue, this +// function returns an empty view. +// +// Note that the calls to write_buf and write_ack +// may be far apart. Other operations won't interfere +// with the write (except for other byte_queue_write_* +// functions). The only rule is you can't call +// write_buf multiple times before calling write_ack. +ByteView byte_queue_write_buf(ByteQueue *queue); +void byte_queue_write_ack(ByteQueue *queue, uint32_t num); + +// Sets the minimum capacity for the next write +// operation and returns 1 if the content of the +// queue was moved, else 0 is returned. +// +// You must not call this function while a write +// is pending. In other words, you must do this: +// +// byte_queue_write_setmincap(queue, mincap); +// dst = byte_queue_write_buf(queue, &cap); +// ... +// byte_queue_write_ack(num); +// +// And NOT this: +// +// dst = byte_queue_write_buf(queue); +// byte_queue_write_setmincap(queue, mincap); <-- BAD +// ... +// byte_queue_write_ack(num); +// +int byte_queue_write_setmincap(ByteQueue *queue, uint32_t mincap); + +// Write some bytes to the queue. This is a +// short hand for write_buf/memcpy/write_ack +void byte_queue_write(ByteQueue *queue, void *ptr, uint32_t len); + +// Returns the current offset inside the queue +ByteQueueOffset byte_queue_offset(ByteQueue *queue); + +// Writes some bytes at the specified offset. It's +// the responsibility of the user to make sure that +// the offset still refers to content inside the queue. +void byte_queue_patch(ByteQueue *queue, ByteQueueOffset off, void *src, uint32_t len); + +// Returns the number of bytes from the given offset +// to the end of the queue. +uint32_t byte_queue_size_from_offset(ByteQueue *queue, ByteQueueOffset off); + +// Removes all bytes from the given offset to the the +// end of the queue. +void byte_queue_remove_from_offset(ByteQueue *queue, ByteQueueOffset offset); + +//////////////////////////////////////////////////////////////////////////////////////// +// src/cert.h +//////////////////////////////////////////////////////////////////////////////////////// +// This is an utility to create self-signed certificates +// useful when testing HTTPS servers locally. This is only +// meant to be used by people starting out with a library +// and simplifying the zero to one phase. +// +// The C, O, and CN are respectively country name, organization name, +// and common name of the certificate. For instance: +// +// C="IT" +// O="My Organization" +// CN="my_website.com" +// +// The output is a certificate file in PEM format and a private +// key file with the key used to sign the certificate. +int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, + HTTP_String cert_file, HTTP_String key_file); + +//////////////////////////////////////////////////////////////////////////////////////// +// src/client.h +//////////////////////////////////////////////////////////////////////////////////////// + +#ifndef HTTP_CLIENT_CAPACITY +// The maximum ammount of requests that can be performed +// in parallel. +#define HTTP_CLIENT_CAPACITY (1<<7) +#endif + +typedef enum { + HTTP_CLIENT_CONN_FREE, + HTTP_CLIENT_CONN_WAIT_LINE, + HTTP_CLIENT_CONN_WAIT_HEADER, + HTTP_CLIENT_CONN_WAIT_BODY, + HTTP_CLIENT_CONN_FLUSHING, + HTTP_CLIENT_CONN_BUFFERING, + HTTP_CLIENT_CONN_COMPLETE, +} HTTP_ClientConnState; + +// Fields of this struct are private +typedef struct { + HTTP_ClientConnState state; + ByteQueue input; + ByteQueue output; +} HTTP_ClientConn; + +// Fields of this struct are private +typedef struct { + + // Array of connections. The counter contains the + // number of structs such that state=FREE. + int num_conns; + HTTP_ClientConn conns[HTTP_CLIENT_CAPACITY]; + + // Queue of indices referring to connections that + // are in the COMPLETE state. + int num_ready; + int ready_head; + int ready[HTTP_CLIENT_CAPACITY]; + + // Asynchronous TCP and TLS socket abstraction + SocketManager sockets; + + // The server object doesn't interact with this + // field directly, it just initializes the socket + // manager with a pointer to it. This allows + // allocating the exact number of sockets we + // will need. + Socket socket_pool[HTTP_CLIENT_CAPACITY]; + +} HTTP_Client; + +// Initialize an HTTP client object. This allows one to +// perform a number of requests in parallel. +int http_client_init(HTTP_Client *client); + +// Release resources associated to a client object. +void http_client_free(HTTP_Client *client); + +// When a thread is blocked waiting for client events, +// other threads can call this function to wake it up. +int http_client_wakeup(HTTP_Client *client); + +typedef struct { + HTTP_Client *client; + uint16_t index; + uint16_t gen; +} HTTP_RequestBuilder; + +// Create a new request builder object. If the response +// pointer is NULL, a brand new builder is created. If +// response isn't NULL (and http_free_response wasn't +// called on it yet), the connection associated to that +// previous exchange is reused. Note that it's up to the +// user to make sure the requests are targeting the same +// host. Returns 0 on success, -1 on error. +int http_client_get_builder(HTTP_Client *client, + HTTP_Response *response, HTTP_RequestBuilder *builder); + +// Set the URL of the current request. This is the first +// function of the request builder that the user must call. +void http_request_builder_url(HTTP_RequestBuilder builder, String url); + +// After the URL, the user may set zero or more headers. +void http_request_builder_header(HTTP_RequestBuilder builder, String str); + +// Append bytes to the request's body. You can call this +// any amount of times, as long as it's after having set +// the URL. +void http_request_builder_body(HTTP_RequestBuilder builder, String str); + +// Mark this request as complete. This invalidates the +// builder. +void http_request_builder_send(HTTP_RequestBuilder builder); + +// List all low-level socket events the client is +// waiting for such that the caller can call poll() +// with it. +int http_client_register_events(HTTP_Client *client, + struct pollfd *polled, int max_polled); + +// The caller has waited for poll() to return and some +// I/O events to be triggered, so now the HTTP client +// can continue its buffering and flushing operations. +int http_client_process_events(HTTP_Client *client, + struct pollfd *polled, int num_polled); + +// After some I/O events were processes, some responses +// may be availabe. This function returns one of the +// buffered responses. If a request was available, true +// is returned. If no more are avaiable, false is returned. +// The returned response must either be freed using the +// http_free_response function or reused by passing it +// to http_client_get_builder. +bool http_client_next_response(HTTP_Client *client, + HTTP_Response **response); + +// Free a response object. You can't access its fields +// again after this. +void http_free_response(HTTP_Response *res); //////////////////////////////////////////////////////////////////////////////////////// // src/parse.h //////////////////////////////////////////////////////////////////////////////////////// -#line 1 "src/parse.h" -#ifndef PARSE_INCLUDED -#define PARSE_INCLUDED - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#endif - #define HTTP_MAX_HEADERS 32 typedef struct { @@ -197,309 +805,205 @@ int http_get_param_i (HTTP_String body, HTTP_String str); // domain an port. If port is -1, the default value of 80 is assumed. bool http_match_host(HTTP_Request *req, HTTP_String domain, int port); -#endif // PARSE_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/engine.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/engine.h" -#ifndef HTTP_ENGINE_INCLUDED -#define HTTP_ENGINE_INCLUDED - -#ifndef HTTP_AMALGAMATION -#include "parse.h" -#endif - -typedef enum { - HTTP_MEMFUNC_MALLOC, - HTTP_MEMFUNC_FREE, -} HTTP_MemoryFuncTag; - -typedef void*(*HTTP_MemoryFunc)(HTTP_MemoryFuncTag tag, - void *ptr, int len, void *data); - -typedef struct { - - HTTP_MemoryFunc memfunc; - void *memfuncdata; - - unsigned long long curs; - - char* data; - unsigned int head; - unsigned int size; - unsigned int used; - unsigned int limit; - - char* read_target; - unsigned int read_target_size; - - int flags; -} HTTP_ByteQueue; - -typedef unsigned long long HTTP_ByteQueueOffset; - -#define HTTP_ENGINE_STATEBIT_CLIENT (1 << 0) -#define HTTP_ENGINE_STATEBIT_CLOSED (1 << 1) -#define HTTP_ENGINE_STATEBIT_RECV_BUF (1 << 2) -#define HTTP_ENGINE_STATEBIT_RECV_ACK (1 << 3) -#define HTTP_ENGINE_STATEBIT_SEND_BUF (1 << 4) -#define HTTP_ENGINE_STATEBIT_SEND_ACK (1 << 5) -#define HTTP_ENGINE_STATEBIT_REQUEST (1 << 6) -#define HTTP_ENGINE_STATEBIT_RESPONSE (1 << 7) -#define HTTP_ENGINE_STATEBIT_PREP (1 << 8) -#define HTTP_ENGINE_STATEBIT_PREP_HEADER (1 << 9) -#define HTTP_ENGINE_STATEBIT_PREP_BODY_BUF (1 << 10) -#define HTTP_ENGINE_STATEBIT_PREP_BODY_ACK (1 << 11) -#define HTTP_ENGINE_STATEBIT_PREP_ERROR (1 << 12) -#define HTTP_ENGINE_STATEBIT_PREP_URL (1 << 13) -#define HTTP_ENGINE_STATEBIT_PREP_STATUS (1 << 14) -#define HTTP_ENGINE_STATEBIT_CLOSING (1 << 15) - -typedef enum { - HTTP_ENGINE_STATE_NONE = 0, - HTTP_ENGINE_STATE_CLIENT_PREP_URL = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_URL, - HTTP_ENGINE_STATE_CLIENT_PREP_HEADER = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER, - HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF, - HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK, - HTTP_ENGINE_STATE_CLIENT_PREP_ERROR = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR, - HTTP_ENGINE_STATE_CLIENT_SEND_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_BUF, - HTTP_ENGINE_STATE_CLIENT_SEND_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_ACK, - HTTP_ENGINE_STATE_CLIENT_RECV_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_BUF, - HTTP_ENGINE_STATE_CLIENT_RECV_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_ACK, - HTTP_ENGINE_STATE_CLIENT_READY = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RESPONSE, - HTTP_ENGINE_STATE_CLIENT_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_CLOSED, - HTTP_ENGINE_STATE_SERVER_RECV_BUF = HTTP_ENGINE_STATEBIT_RECV_BUF, - HTTP_ENGINE_STATE_SERVER_RECV_ACK = HTTP_ENGINE_STATEBIT_RECV_ACK, - HTTP_ENGINE_STATE_SERVER_PREP_STATUS = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_STATUS, - HTTP_ENGINE_STATE_SERVER_PREP_HEADER = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER, - HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF, - HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK, - HTTP_ENGINE_STATE_SERVER_PREP_ERROR = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR, - HTTP_ENGINE_STATE_SERVER_SEND_BUF = HTTP_ENGINE_STATEBIT_SEND_BUF, - HTTP_ENGINE_STATE_SERVER_SEND_ACK = HTTP_ENGINE_STATEBIT_SEND_ACK, - HTTP_ENGINE_STATE_SERVER_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT, -} HTTP_EngineState; - -typedef struct { - HTTP_EngineState state; - HTTP_ByteQueue input; - HTTP_ByteQueue output; - int numexch; - int reqsize; - int closing; - int keepalive; - HTTP_ByteQueueOffset response_offset; - HTTP_ByteQueueOffset content_length_offset; - HTTP_ByteQueueOffset content_length_value_offset; - union { - HTTP_Request req; - HTTP_Response res; - } result; -} HTTP_Engine; - -void http_engine_init (HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata); -void http_engine_free (HTTP_Engine *eng); - -void http_engine_close (HTTP_Engine *eng); -HTTP_EngineState http_engine_state (HTTP_Engine *eng); - -const char* http_engine_statestr(HTTP_EngineState state); // TODO: remove - -char* http_engine_recvbuf (HTTP_Engine *eng, int *cap); -void http_engine_recvack (HTTP_Engine *eng, int num); -char* http_engine_sendbuf (HTTP_Engine *eng, int *len); -void http_engine_sendack (HTTP_Engine *eng, int num); - -HTTP_Request* http_engine_getreq (HTTP_Engine *eng); -HTTP_Response* http_engine_getres (HTTP_Engine *eng); - -void http_engine_url (HTTP_Engine *eng, HTTP_Method method, HTTP_String url, int minor); -void http_engine_status (HTTP_Engine *eng, int status); -void http_engine_header (HTTP_Engine *eng, HTTP_String str); -void http_engine_body (HTTP_Engine *eng, HTTP_String str); -void http_engine_bodycap (HTTP_Engine *eng, int mincap); -char* http_engine_bodybuf (HTTP_Engine *eng, int *cap); -void http_engine_bodyack (HTTP_Engine *eng, int num); -void http_engine_done (HTTP_Engine *eng); -void http_engine_undo (HTTP_Engine *eng); - -#endif // HTTP_ENGINE_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/cert.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/cert.h" -#ifndef CERT_INCLUDED -#define CERT_INCLUDED - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#endif - -// This is an utility to create self-signed certificates -// useful when testing HTTPS servers locally. This is only -// meant to be used by people starting out with a library -// and simplifying the zero to one phase. -// -// The C, O, and CN are respectively country name, organization name, -// and common name of the certificate. For instance: -// -// C="IT" -// O="My Organization" -// CN="my_website.com" -// -// The output is a certificate file in PEM format and a private -// key file with the key used to sign the certificate. -int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, - HTTP_String cert_file, HTTP_String key_file); - -#endif // CERT_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/client.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/client.h" -#ifndef CLIENT_INCLUDED -#define CLIENT_INCLUDED - -#include - -#ifndef HTTP_AMALGAMATION -#include "parse.h" -#endif - -// Initialize the global state of cHTTP. -// -// cHTTP tries to avoid global state. What this function -// does is call the global initialization functions of -// its dependencies (OpenSSL and Winsock) -int http_global_init(void); - -// Free the global state of cHTTP. -void http_global_free(void); - -// Opaque type describing an "HTTP client". Any request -// that is started must always be associated to an HTTP -// client object. -typedef struct HTTP_Client HTTP_Client; - -// Handle for a pending request. This should be considered -// opaque. Don't read or modify its fields! -typedef struct { - void *data0; - int data1; - int data2; -} HTTP_RequestBuilder; - -// Initialize a client object. If something goes wrong, -// NULL is returned. -HTTP_Client *http_client_init(void); - -// Deinitialize a client object -void http_client_free(HTTP_Client *client); - -// Create a request object associated to the given client. -// On success, 0 is returned and the handle is initialized. -// On error, -1 is returned. -int http_client_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder); - -void http_request_builder_user_data(HTTP_RequestBuilder builder, void *user_data); - -// Enable/disable I/O tracing for the specified request. -// This must be done when the request is in the initialization -// phase. -void http_request_builder_trace(HTTP_RequestBuilder builder, bool trace); - -// Set the method and URL of the specified request object. -// This must be the first thing you do after http_client_request -// is called (you may http_request_trace before, but nothing -// else!) -void http_request_builder_line(HTTP_RequestBuilder builder, HTTP_Method method, HTTP_String url); - -// Append a header to the specified request. You must call -// this after http_request_line and may do so multiple times. -void http_request_builder_header(HTTP_RequestBuilder builder, HTTP_String str); - -// Append some data to the request's body. You must call -// this after either http_request_line or http_request_header. -void http_request_builder_body(HTTP_RequestBuilder builder, HTTP_String str); - -// Mark the initialization of the request as completed and -// perform the request. -void http_request_builder_submit(HTTP_RequestBuilder builder); - -// Free resources associated to a request. This must be called -// after the request has completed. -// -// TODO: allow aborting pending requests -void http_response_free(HTTP_Response *res); - -// Wait for the completion of one request associated to -// the client. The handle of the resolved request is returned -// through the handle output parameter. If you're not -// interested in which request completed (like when you -// have only one pending request), you can pass NULL. -// -// On error -1 is retutned, else 0 is returned and the -// handle is initialized. -// -// Note that calling this function when no requests are -// pending is considered an error. -int http_client_wait(HTTP_Client *client, HTTP_Response **res, void **user_data); - -// TODO: comment -HTTP_Response *http_get(HTTP_String url, - HTTP_String *headers, int num_headers); - -// TODO: comment -HTTP_Response *http_post(HTTP_String url, - HTTP_String *headers, int num_headers, - HTTP_String body); - -#endif // CLIENT_INCLUDED - //////////////////////////////////////////////////////////////////////////////////////// // src/server.h //////////////////////////////////////////////////////////////////////////////////////// -#line 1 "src/server.h" -#ifndef HTTP_SERVER_INCLUDED -#define HTTP_SERVER_INCLUDED - -#include - -#ifndef HTTP_AMALGAMATION -#include "parse.h" +#ifndef HTTP_SERVER_CAPACITY +// The maximum ammount of requests that can be handled +// in parallel. +#define HTTP_SERVER_CAPACITY (1<<9) #endif +typedef enum { + + // This struct is unused + HTTP_SERVER_CONN_FREE, + + // No request was buffered yet. + HTTP_SERVER_CONN_BUFFERING, + + // A request was just buffered and is waiting for + // the user to build a response. To be specific, + // it's waiting for the user to set a response status. + HTTP_SERVER_CONN_WAIT_STATUS, + + // A request is buffered and a status was set. Now + // the user can set a header or append the first + // bytes of the response body. + HTTP_SERVER_CONN_WAIT_HEADER, + + // A request is buffered and some bytes were appended + // to the response. Now the user can either append more + // bytes or send out the response. + HTTP_SERVER_CONN_WAIT_BODY, + + // A response has been produced and it's being flushed. + HTTP_SERVER_CONN_FLUSHING, + +} HTTP_ServerConnState; + +// This structure represents the HTTP connection to +// a client. typedef struct { - void *data0; - int data1; - int data2; + + // If false, this struct is unused + HTTP_ServerConnState state; + + // Handle to the socket + SocketHandle handle; + + // Data received by the client + ByteQueue input; + + // Data being sent to the client + ByteQueue output; + + // When the state is WAIT_STATUS, WAIT_HEADER, + // or WAIT_BODY, this contains the parsed version + // of the buffered request. + HTTP_Request request; + + // Length of the buffered request when the request + // field is valid. + int request_len; + + // Offset of the first response byte in the output + // buffer. This is useful when the user wants to + // undo the response it's building and start from + // scratch. + ByteQueueOffset response_offset; + +} HTTP_ServerConn; + +typedef struct { + + // Array of connections. The counter contains the + // number of structs such that state=FREE. + int num_conns; + HTTP_ServerConn conns[HTTP_SERVER_CAPACITY]; + + // Queue of indices referring to connections that + // are in the WAIT_STATUS state. + int num_ready; + int ready_head; + int ready[HTTP_SERVER_CAPACITY]; + + // Asynchronous TCP and TLS socket abstraction + SocketManager sockets; + + // The server object doesn't interact with this + // field directly, it just initializes the socket + // manager with a pointer to it. This allows + // allocating the exact number of sockets we + // will need. + Socket socket_pool[HTTP_SERVER_CAPACITY]; + +} HTTP_Server; + +// Initialize the HTTP server object. By default, it won't +// listen for connections. You need to call +// +// http_server_listen_tcp +// http_server_listen_tls +// +// to listen for connection. Note that you can have a +// single server listening for HTTP and HTTPS requests +// by calling both. +int http_server_init(HTTP_Server *server); + +// Release resources associated to the server. +void http_server_free(HTTP_Server *server); + +// Enable listening for plain HTTP requests at the +// specified interface. +int http_server_listen_tcp(HTTP_Server *server, + String addr, Port port); + +// Enable listening for HTTPS requests at the specified +// interfact, using the specified certificate and key +// to verify the connection. +int http_server_listen_tls(HTTP_Server *server, + String addr, Port port, String cert_file_name, + String key_file_name); + +// Add the certificate for an additional domain when +// the server is listening for HTTPS requests. +int http_server_add_certificate(HTTP_Server *server, + String domain, String cert_file, String key_file); + +// When a thread is blocked waiting for server events, +// other threads can call this function to wake it up. +int http_server_wakeup(HTTP_Server *server); + +// List all low-level socket events the server is +// waiting for such that the caller can call poll() +// with it. +int http_server_register_events(HTTP_Server *server, + struct pollfd *polled, int max_polled); + +// The caller has waited for poll() to return and some +// I/O events to be triggered, so now the HTTP server +// can continue its buffering and flushing operations. +int http_server_process_events(HTTP_Server *server, + struct pollfd *polled, int num_polled); + +typedef struct { + HTTP_Server *server; + uint16_t index; + uint16_t gen; } HTTP_ResponseBuilder; -typedef struct HTTP_Server HTTP_Server; +// After some I/O events were processes, some requests +// may be availabe. This function returns one of the +// buffered requests. If a request was available, true +// is returned. If no more are avaiable, false is returned. +// Note that It's possible to get multiple requests to +// respond in batches. +// For each request returned by this function, the user +// must build a response using the response builder API. +bool http_server_next_request(HTTP_Server *server, + HTTP_Request **request, HTTP_ResponseBuilder *builder); -HTTP_Server *http_server_init(HTTP_String addr, uint16_t port); +// This function is called to set the status code of +// a request's response. If this function is called +// after the other response builder functions, it will +// reset the response and set a new status. +void http_response_builder_status(HTTP_ResponseBuilder builder, int status); -HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port, - uint16_t secure_port, HTTP_String cert_key, HTTP_String private_key); +// Append a header to the response. This can only be +// used after having set the status and before appending +// to the body. +void http_response_builder_header(HTTP_ResponseBuilder builder, String str); -void http_server_free (HTTP_Server *server); -int http_server_wait (HTTP_Server *server, HTTP_Request **req, HTTP_ResponseBuilder *handle); -int http_server_add_website (HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file); -void http_response_builder_status (HTTP_ResponseBuilder res, int status); -void http_response_builder_header (HTTP_ResponseBuilder res, HTTP_String str); -void http_response_builder_body (HTTP_ResponseBuilder res, HTTP_String str); -void http_response_builder_bodycap (HTTP_ResponseBuilder res, int mincap); -char* http_response_builder_bodybuf (HTTP_ResponseBuilder res, int *cap); -void http_response_builder_bodyack (HTTP_ResponseBuilder res, int num); -void http_response_builder_undo (HTTP_ResponseBuilder res); -void http_response_builder_done (HTTP_ResponseBuilder res); +// Append some bytes to the response's body +void http_response_builder_body(HTTP_ResponseBuilder builder, String str); -#endif // HTTP_SERVER_INCLUDED -#endif // HTTP_AMALGAMATION +// Mark the response as complete. This will invalidate +// the response builder handle. +void http_response_builder_send(HTTP_ResponseBuilder builder, String str); + +//////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2025 Francesco Cozzuto +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom +// the Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall +// be included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. +//////////////////////////////////////////////////////////////////////////////////////// diff --git a/examples/client/000_get_request.c b/examples/client/000_get_request.c deleted file mode 100644 index f44b541..0000000 --- a/examples/client/000_get_request.c +++ /dev/null @@ -1,53 +0,0 @@ -#include -#include - -// This is an example of how to use cHTTP to perform -// a basic GET request. - -int main(void) -{ - http_global_init(); - - // List any headers the request should hold - HTTP_String headers[] = { - HTTP_STR("User-Agent: cHTTP"), - }; - - // Perform the request. This will block the thread - // until an error occurs or the request completes. - HTTP_Response *res = http_get( - HTTP_STR("http://example.com/index.html"), - headers, HTTP_COUNT(headers) - ); - - // The http_get function returns NULL if the request - // couldn't be performed. - if (res == NULL) return -1; - - // If the request succeded (note that responses with - // status 4xx and 5xx are not considered as errors in - // this context) the returned value holds the parsed - // version of the response and the output handle is set. - - printf("status code: %d\n", res->status); - - for (int i = 0; i < res->num_headers; i++) { - HTTP_Header header = res->headers[i]; - printf( - "header %d: [%.*s] [%.*s]\n", - i, - HTTP_UNPACK(header.name), - HTTP_UNPACK(header.value) - ); - } - - printf("body: %.*s\n", HTTP_UNPACK(res->body)); - - // When we are done reading from the response object - // we must free the request's resources. - http_response_free(res); - - // All done. Deinitialize the library. - http_global_free(); - return 0; -} \ No newline at end of file diff --git a/examples/client/010_parallel_requests.c b/examples/client/010_parallel_requests.c deleted file mode 100644 index 9e160e7..0000000 --- a/examples/client/010_parallel_requests.c +++ /dev/null @@ -1,51 +0,0 @@ -#include -#include - -int main(void) -{ - http_global_init(); - HTTP_Client *client = http_client_init(); - if (client == NULL) - return -1; - - HTTP_Response *responses[2]; - - HTTP_String urls[] = { - HTTP_STR("http://coz.is"), - HTTP_STR("http://coz.is"), - }; - - bool trace = false; - - HTTP_RequestBuilder builder; - if (http_client_get_builder(client, &builder) < 0) return -1; - http_request_builder_trace(builder, trace); - http_request_builder_user_data(builder, &responses[0]); - http_request_builder_line(builder, HTTP_METHOD_GET, urls[0]); - http_request_builder_submit(builder); - - if (http_client_get_builder(client, &builder) < 0) return -1; - http_request_builder_trace(builder, trace); - http_request_builder_user_data(builder, &responses[1]); - http_request_builder_line(builder, HTTP_METHOD_GET, urls[1]); - http_request_builder_submit(builder); - - for (int i = 0; i < 2; i++) { - void **dst; - HTTP_Response *output; - if (http_client_wait(client, &output, (void*) &dst) < 0) - return -1; - *dst = output; - } - - HTTP_Response *responseA = responses[0]; - HTTP_Response *responseB = responses[1]; - - // ... process responses ... - - http_response_free(responseA); - http_response_free(responseB); - http_client_free(client); - http_global_free(); - return 0; -} \ No newline at end of file diff --git a/examples/client/client_example.c b/examples/client/client_example.c deleted file mode 100644 index 2ee69b6..0000000 --- a/examples/client/client_example.c +++ /dev/null @@ -1,44 +0,0 @@ -#include -#include -#include -#include - -int main(int argc, char **argv) -{ - if (argc < 2) { - printf("Usage: %s [url2 ...]\n", argv[0]); - return 1; - } - - http_global_init(); - HTTP_Client *client = http_client_init(); - - for (int i = 1; i < argc; i++) { - HTTP_RequestBuilder builder; - if (http_client_get_builder(client, &builder) < 0) { - printf("request creation error\n"); - return -1; - } - http_request_builder_line(builder, HTTP_METHOD_GET, (HTTP_String) { argv[i], strlen(argv[i]) }); - http_request_builder_submit(builder); - printf("request submitted\n"); - } - - for (int i = 1; i < argc; i++) { - - HTTP_Response *res; - if (http_client_wait(client, &res, NULL) < 0) { - printf("request wait error\n"); - return -1; - } - - printf("Status: %d\n", res->status); - printf("Body: %.*s\n", HTTP_UNPACK(res->body)); - - http_response_free(res); - } - - http_client_free(client); - http_global_free(); - return 0; -} \ No newline at end of file diff --git a/examples/client/web_crawler.c b/examples/client/web_crawler.c deleted file mode 100644 index feacf22..0000000 --- a/examples/client/web_crawler.c +++ /dev/null @@ -1,194 +0,0 @@ -#include -#include -#include -#include -#include - -#define BLK "\e[0;30m" -#define RED "\e[0;31m" -#define GRN "\e[0;32m" -#define YEL "\e[0;33m" -#define BLU "\e[0;34m" -#define MAG "\e[0;35m" -#define CYN "\e[0;36m" -#define WHT "\e[0;37m" -#define RST "\e[0m" - -HTTP_String *already_crawled = NULL; -int num_already_crawled = 0; -int cap_already_crawled = 0; - -HTTP_String copystr(HTTP_String str) -{ - char *copy = malloc(str.len); - if (copy == NULL) - abort(); - memcpy(copy, str.ptr, str.len); - return (HTTP_String) { copy, str.len }; -} - -int normalize_url(HTTP_String url, char *dst, int max) -{ - HTTP_URL parsed_url; - if (http_parse_url(url.ptr, url.len, &parsed_url) <= 0) - return -1; - - int len = snprintf(dst, max, "http://%.*s%.*s", - (int) parsed_url.authority.host.text.len, - parsed_url.authority.host.text.ptr, - (int) parsed_url.path.len, - parsed_url.path.ptr - ); - if (len < 0 || len >= max) - return -1; - - return len; -} - -void add_to_crawled_list(HTTP_String url) -{ - if (num_already_crawled == cap_already_crawled) { - - int new_cap = 2 * cap_already_crawled; - if (new_cap == 0) - new_cap = 8; - - HTTP_String *new_ptr = malloc(new_cap * sizeof(HTTP_String)); - if (new_ptr == NULL) - abort(); - - if (cap_already_crawled > 0) { - for (int i = 0; i < num_already_crawled; i++) - new_ptr[i] = already_crawled[i]; - free(already_crawled); - } - - already_crawled = new_ptr; - cap_already_crawled = new_cap; - } - - char buf[1<<10]; - int len = normalize_url(url, buf, (int) sizeof(buf)); - if (len < 0) return; - - already_crawled[num_already_crawled++] = copystr((HTTP_String) { buf, len }); -} - -bool is_already_crawled(HTTP_String url) -{ - char buf[1<<10]; - int len = normalize_url(url, buf, (int) sizeof(buf)); - if (len < 0 || len >= (int) sizeof(buf)) - return false; - - for (int i = 0; i < num_already_crawled; i++) - if (http_streq(already_crawled[i], (HTTP_String) { buf, len })) - return true; - - return false; -} - -HTTP_String next_link(HTTP_String src, int *pcur) -{ - int cur = *pcur; - - for (;;) { - - while (cur < src.len && src.ptr[cur] != 'h') - cur++; - - if (cur == src.len) - break; - - int off = cur; - - HTTP_URL parsed_url; - int len = http_parse_url(src.ptr + cur, src.len - cur, &parsed_url); - if (len <= 0) { - cur++; - continue; - } - - cur += len; - - if (!http_streq(parsed_url.scheme, HTTP_STR("http")) && - !http_streq(parsed_url.scheme, HTTP_STR("https"))) - continue; - - *pcur = cur; - return (HTTP_String) { src.ptr + off, len }; - } - - *pcur = cur; - return (HTTP_String) { NULL, 0 }; -} - -int main(int argc, char **argv) -{ - http_global_init(); - - if (argc < 2) { - printf("Usage: %s \n", argv[0]); - return -1; - } - HTTP_String start_url = { argv[1], strlen(argv[1]) }; - - HTTP_Client *client = http_client_init(); - if (client == NULL) { - printf("Couldn't initialize HTTP client object\n"); - return -1; - } - - HTTP_RequestBuilder builder; - int ret = http_client_get_builder(client, &builder); - if (ret < 0) { - printf("Couldn't start request\n"); - http_client_free(client); - return -1; - } - http_request_builder_line(builder, HTTP_METHOD_GET, start_url); - http_request_builder_header(builder, HTTP_STR("User-Agent: Simple crawler")); - http_request_builder_submit(builder); - - for (;;) { - - HTTP_Response *res; - ret = http_client_wait(client, &res, NULL); - if (ret < 0) { - // TODO - return -1; - } - if (res == NULL) - continue; - - HTTP_String body = res->body; - - int cursor = 0; - for (;;) { - - HTTP_String url = next_link(body, &cursor); - if (url.len == 0) break; - - if (is_already_crawled(url)) { - printf("Ignoring " RED "%.*s" RST "\n", HTTP_UNPACK(url)); - continue; - } - - printf("Fetching " GRN "%.*s" RST "\n", HTTP_UNPACK(url)); - add_to_crawled_list(url); - - HTTP_RequestBuilder builder; - ret = http_client_get_builder(client, &builder); - if (ret < 0) - continue; - - http_request_builder_line(builder, HTTP_METHOD_GET, url); - http_request_builder_header(builder, HTTP_STR("User-Agent: Simple crawler")); - http_request_builder_submit(builder); - } - } - - http_client_free(client); - http_global_free(); - return 0; -} diff --git a/examples/engine/blocking_server.c b/examples/engine/blocking_server.c deleted file mode 100644 index 730e33d..0000000 --- a/examples/engine/blocking_server.c +++ /dev/null @@ -1,244 +0,0 @@ -#include - -#ifdef _WIN32 -#include -#include -#include -#define CLOSE_SOCKET closesocket -#else -#include -#include -#include -#include -#include -#include -#include -#define SOCKET int -#define INVALID_SOCKET -1 -#define CLOSE_SOCKET close -#endif - -#include - -// This example showcases how to use the engine interface -// to build a blocking HTTP server that works on Windows -// and Linux. - -// Callback used by the engine to manage dynamic memory -static void *memfunc(HTTP_MemoryFuncTag tag, - void *ptr, int len, void *data) -{ - (void) data; - switch (tag) { - - case HTTP_MEMFUNC_MALLOC: - return malloc(len); - - case HTTP_MEMFUNC_FREE: - free(ptr); - return NULL; - } - return NULL; -} - -static void produce_response(HTTP_Engine *eng) -{ - // All considerations in simple_server.c for how responses - // are built also applies here, where http_engine_XXX functions - // are used instead of http_response_XXX functions. - - // Set the response status - http_engine_status(eng, 200); - - // Set zero or more headers - http_engine_header(eng, HTTP_STR("Server: tinyhttp")); - - // Set some bytes in the body - http_engine_body(eng, HTTP_STR("Hello, world!")); - - // This is one difference from the http_response_XXX API. - // It's possible to write response content directly into - // the engine's output buffer. This avoids copies in some - // circumstances. - - char msg[] = " What's up??"; - - // First, set how many bytes the output buffer will need - // to hold at least: - http_engine_bodycap(eng, sizeof(msg)-1); - - // Now get the location for the write. The returned pointer - // points to a region of size "cap", which equal or greater - // to the previously set minimum capacity. - int cap; - char *dst = http_engine_bodybuf(eng, &cap); - - // If an error occurred internally, the returned pointer will - // be NULL and the capacity 0. In this case, you can just skip - // this write. The engine will automatically be closed when the - // "http_engine_done" function is called. - if (dst) { - memcpy(dst, msg, sizeof(msg)-1); - - // Tell the engine how many bytes the application wrote to the - // provided buffer. - http_engine_bodyack(eng, sizeof(msg)-1); - } - - // If an error occurs, you can undo all progress and start - // from scratch - int error = rand() & 1; - if (error) { - http_engine_undo(eng); - http_engine_status(eng, 500); - http_engine_done(eng); - return; - } - - http_engine_done(eng); -} - -int main(void) -{ - // Interface and port the server will be listening on. - char *addr = "127.0.0.1"; - int port = 8080; - -#ifdef _WIN32 - WSADATA wd; - if (WSAStartup(MAKEWORD(2, 2), &wd)) - return -1; -#endif - - // Create the listening socket - SOCKET listen_fd = socket(AF_INET, SOCK_STREAM, 0); - if (listen_fd == INVALID_SOCKET) - return -1; - - // Ignore the cooldown time for the bound interface to - // avoid that annoying "address in use" error - int reuse = 1; - setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &reuse, sizeof(reuse)); - - // Fill out the address struct - struct sockaddr_in bind_buf; - { - struct in_addr addr_buf; - if (inet_pton(AF_INET, addr, &addr_buf) != 1) - return -1; - - bind_buf.sin_family = AF_INET; - bind_buf.sin_port = htons(port); - bind_buf.sin_addr = addr_buf; - memset(&bind_buf.sin_zero, 0, sizeof(bind_buf.sin_zero)); - } - - // Associate the listening socket to the interface - if (bind(listen_fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) - return -1; - - // Allow incoming connections - if (listen(listen_fd, 32) < 0) - return -1; - - for (;;) { - - printf("Waiting for a connection\n"); - - // Get an incoming connection from the kernel - SOCKET client_fd = accept(listen_fd, NULL, NULL); - if (client_fd == INVALID_SOCKET) - continue; - - printf("New connection\n"); - - // Initialize the HTTP state machine - HTTP_Engine eng; - http_engine_init(&eng, 0, memfunc, NULL); - - for (;;) { - - // At this point, the engine can be in one - // of four states: - // 1) RECV_BUF: The engine is waiting for bytes from the network - // 2) SEND_BUF: The engine wants to write bytes from the network - // 3) CLOSED: The connection shut down at the HTTP layer - // 4) PREP_STATUS: A request was received and the associated response - // needs to be generated. - HTTP_EngineState state = http_engine_state(&eng); - - if (state == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) { - - produce_response(&eng); - - } else if (state == HTTP_ENGINE_STATE_SERVER_RECV_BUF) { - - printf("Receiving bytes\n"); - - // Get a pointer to the engine's input buffer - int cap; - char *dst = http_engine_recvbuf(&eng, &cap); - - // The application can write up to "cap" bytes - // to the "dst" buffer. - - int ret = recv(client_fd, dst, cap, 0); - if (ret <= 0) { - // If the peer disconnected or an error occurred, - // we can "close" the engine. This makes it so any - // further operation on the engine will be a no-op - // and the next time we query the state we will get - // CLOSED. - http_engine_close(&eng); - ret = 0; - } - - printf("Received %d bytes\n", ret); - - // Tell the engine how many bytes we wrote to - // the buffer. - http_engine_recvack(&eng, ret); - - } else if (state == HTTP_ENGINE_STATE_SERVER_SEND_BUF) { - - // This code is the same as the recv case except - // we read from the buffer instead of writing. - - printf("Sending bytes\n"); - - int len; - char *src = http_engine_sendbuf(&eng, &len); - - // Here "src" points to "len" bytes that need to - // be sent over the network. - int ret = send(client_fd, src, len, 0); - if (ret < 0) { - http_engine_close(&eng); - ret = 0; - } - - printf("Sent %d bytes\n", ret); - - http_engine_sendack(&eng, ret); - - } else { - // HTTP_ENGINE_STATE_SERVER_CLOSED - printf("HTTP close\n"); - break; - } - } - - printf("Closing connection\n"); - - http_engine_free(&eng); - CLOSE_SOCKET(client_fd); - } - - CLOSE_SOCKET(listen_fd); - -#ifdef _WIN32 - WSACleanup(); -#endif - - return 0; -} \ No newline at end of file diff --git a/examples/engine/iocp_server.c b/examples/engine/iocp_server.c deleted file mode 100644 index d16a9ee..0000000 --- a/examples/engine/iocp_server.c +++ /dev/null @@ -1,239 +0,0 @@ -#ifdef _WIN32 - -#include -#include -#include -#include - -#define MAX_CLIENTS (1<<10) - -// Callback used by the engine to manage dynamic memory -static void *memfunc(HTTP_MemoryFuncTag tag, - void *ptr, int len, void *data) -{ - (void) data; - switch (tag) { - - case HTTP_MEMFUNC_MALLOC: - return malloc(len); - - case HTTP_MEMFUNC_FREE: - free(ptr); - return NULL; - } - return NULL; -} - -static SOCKET start_accept(LPFN_ACCEPTEX AcceptEx, SOCKET listen_fd, OVERLAPPED *accept_ov, char *buf, int bufsize) -{ - SOCKET accept_target = socket(AF_INET, SOCK_STREAM, 0); - if (accept_target == INVALID_SOCKET) - return INVALID_SOCKET; - - unsigned long num; - memset(accept_ov, 0, sizeof(OVERLAPPED)); - BOOL ok = AcceptEx(listen_fd, accept_target, buf, bufsize - ((sizeof(struct sockaddr_in) + 16) * 2), - sizeof(struct sockaddr_in) + 16, - sizeof(struct sockaddr_in) + 16, - &num, accept_ov); - if (!ok && WSAGetLastError() != ERROR_IO_PENDING) { - closesocket(accept_target); - return INVALID_SOCKET; - } - - return accept_target; -} - -static void start_recv_or_send(SOCKET sock, OVERLAPPED *recv_ov, OVERLAPPED *send_ov, HTTP_Engine *eng) -{ - HTTP_EngineState state = http_engine_state(eng); - - if (state == HTTP_ENGINE_STATE_SERVER_RECV_BUF) { - - int cap; - char *dst = http_engine_recvbuf(eng, &cap); - - memset(recv_ov, 0, sizeof(OVERLAPPED)); - int ok = ReadFile((HANDLE) sock, dst, cap, NULL, recv_ov); - if (!ok && GetLastError() != ERROR_IO_PENDING) - http_engine_close(eng); - - } else if (state == HTTP_ENGINE_STATE_SERVER_SEND_BUF) { - - int len; - char *src = http_engine_sendbuf(eng, &len); - - memset(send_ov, 0, sizeof(OVERLAPPED)); - int ok = WriteFile((HANDLE) sock, src, len, NULL, send_ov); - if (!ok && GetLastError() != ERROR_IO_PENDING) - http_engine_close(eng); - } -} - -int main(void) -{ - // Interface and port the server will be listening on. - char *addr = "127.0.0.1"; - int port = 8080; - - // Initialize the winsock subsystem - WSADATA wd; - if (WSAStartup(MAKEWORD(2, 2), &wd)) - return -1; - - // Create the I/O completion port object - HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 1); - if (iocp == INVALID_HANDLE_VALUE) - return -1; - - // Create the listening socket - SOCKET listen_fd = socket(AF_INET, SOCK_STREAM, 0); - if (listen_fd == INVALID_SOCKET) - return -1; - - // Register the socket into the IOCP - if (CreateIoCompletionPort((HANDLE) listen_fd, iocp, 0, 0) == NULL) - return -1; - - // Ignore the cooldown time for the bound interface to - // avoid that annoying "address in use" error - int reuse = 1; - setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &reuse, sizeof(reuse)); - - // Fill out the address struct - struct sockaddr_in bind_buf; - { - struct in_addr addr_buf; - if (inet_pton(AF_INET, addr, &addr_buf) != 1) - return -1; - - bind_buf.sin_family = AF_INET; - bind_buf.sin_port = htons(port); - bind_buf.sin_addr = addr_buf; - memset(&bind_buf.sin_zero, 0, sizeof(bind_buf.sin_zero)); - } - - // Associate the listening socket to the interface - if (bind(listen_fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) - return -1; - - // Allow incoming connections - if (listen(listen_fd, 32) < 0) - return -1; - - // Get the AcceptEx function pointer - LPFN_ACCEPTEX AcceptEx = NULL; - GUID GuidAcceptEx = WSAID_ACCEPTEX; - unsigned long num; - int ret = WSAIoctl(listen_fd, - SIO_GET_EXTENSION_FUNCTION_POINTER, - &GuidAcceptEx, sizeof(GuidAcceptEx), - &AcceptEx, sizeof(AcceptEx), - &num, NULL, NULL); - if (ret == SOCKET_ERROR) - return -1; - - OVERLAPPED accept_ov; - char accept_buf[2 * (sizeof(struct sockaddr_in) + 16)]; - SOCKET accept_target = start_accept(AcceptEx, listen_fd, &accept_ov, accept_buf, (int) sizeof(accept_buf)); - if (accept_target == INVALID_SOCKET) - return -1; - - OVERLAPPED recv_overlapped[MAX_CLIENTS]; - OVERLAPPED send_overlapped[MAX_CLIENTS]; - SOCKET sockets[MAX_CLIENTS]; - HTTP_Engine engs[MAX_CLIENTS]; - - for (int i = 0; i < MAX_CLIENTS; i++) - sockets[i] = INVALID_SOCKET; - - for (;;) { - DWORD timeout = INFINITE; - - DWORD transferred; - ULONG_PTR key; - OVERLAPPED *overlapped; - BOOL result = GetQueuedCompletionStatus(iocp, &transferred, &key, &overlapped, timeout); - if (!result && overlapped == NULL) { - if (GetLastError() == WAIT_TIMEOUT) - continue; // Go back to waiting - return -1; // Error - } - - if (overlapped == &accept_ov) { - // A new client connected - - int i = 0; - while (i < MAX_CLIENTS && sockets[i] != INVALID_SOCKET) - i++; - - if (i == MAX_CLIENTS) - closesocket(accept_target); // Server limit reached - else { - - sockets[i] = accept_target; - http_engine_init(&engs[i], 0, memfunc, NULL); - - // Register the socket into the IOCP - if (CreateIoCompletionPort((HANDLE) sockets[i], iocp, i, 0) == NULL) - return -1; - - start_recv_or_send(sockets[i], &recv_overlapped[i], &send_overlapped[i], &engs[i]); - - // Check that the recv or send operation was started. - // If now, remove the connection - if (http_engine_state(&engs[i]) == HTTP_ENGINE_STATE_CLIENT_CLOSED) { - closesocket(sockets[i]); - sockets[i] = INVALID_SOCKET; - http_engine_free(&engs[i]); - } - } - - accept_target = start_accept(AcceptEx, listen_fd, &accept_ov, accept_buf, (int) sizeof(accept_buf)); - if (accept_target == INVALID_SOCKET) - return -1; - - // Go back to waiting - continue; - } - - // Complete the current operation - if (0) {} - else if (overlapped == &recv_overlapped[key]) http_engine_recvack(&engs[key], transferred); - else if (overlapped == &send_overlapped[key]) http_engine_sendack(&engs[key], transferred); - - if (http_engine_state(&engs[key]) == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) { - - // See blocking_server_with_engine.c to learn about how to - // build a response - http_engine_status(&engs[key], 200); - http_engine_body(&engs[key], HTTP_STR("Hello, world!")); - http_engine_done(&engs[key]); - } - - start_recv_or_send(sockets[key], - &recv_overlapped[key], - &send_overlapped[key], - &engs[key]); - - if (http_engine_state(&engs[key]) == HTTP_ENGINE_STATE_SERVER_CLOSED) { - http_engine_free(&engs[key]); - closesocket(sockets[key]); - sockets[key] = INVALID_SOCKET; - } - } - - return 0; -} - -#else - -#include - -int main(void) -{ - printf("This example only works on windows!\n"); - return -1; -} - -#endif \ No newline at end of file diff --git a/examples/server/000_http_server.c b/examples/server/000_http_server.c deleted file mode 100644 index 6b98969..0000000 --- a/examples/server/000_http_server.c +++ /dev/null @@ -1,99 +0,0 @@ -#include -#include -#include - -// This example shows how to set up a basic HTTP server - -int main(void) -{ - http_global_init(); - - // Choose the interface to listen on and the port. - // Currently, servers can only bind to IPv4 addresses. - HTTP_String addr = HTTP_STR("127.0.0.1"); - uint16_t port = 8080; - - bool all_interfaces = false; - - // If you want to bind to all interfaces, you can - // set the address to an empty string. - if (all_interfaces) - addr = HTTP_STR(""); - - // Instanciate the HTTP server object - HTTP_Server *server = http_server_init(addr, port); - if (server == NULL) - return -1; - - // Now we loop forever. Every iteration will serve - // a single HTTP request - for (;;) { - - HTTP_Request *req; - HTTP_ResponseBuilder builder; - - // Block until a request is available - int ret = http_server_wait(server, &req, &builder); - - // The wait functions returns 0 on success and -1 - // on error. By "error" I mean an unrecoverable - // condition. There is no other option than kill - // the process. - if (ret < 0) - return -1; - - // The request information is accessible from - // the [req] variable. Most fields in the request - // struct are reference to the original request - // string. They use type HTTP_String and are not - // null-terminated. This means you'll have to make - // sure to express the length when interacting with - // libc: - HTTP_String path = req->url.path; - printf("requested path [%.*s]\n", HTTP_UNPACK(path)); - - // To find a specific header value, you can either - // iterate over the [req->headers] array or use - // a helper function. Note that this compares header - // names case-insensitively. - int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Some-Header-Name")); - if (idx == -1) { - // Header wasn't found - } else { - // Found - HTTP_String value = req->headers[idx].value; - printf("Header has value [%.*s]\n", HTTP_UNPACK(value)); - } - - // To create a response, you will need to specify - // status code, headers, and content in the proper - // order. - - // First the status code - http_response_builder_status(builder, 200); - - // Then zero or more headers - http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain")); - - // Then you can write zero or more chunks of the response body - http_response_builder_body(builder, HTTP_STR("Hello")); - http_response_builder_body(builder, HTTP_STR(", world!")); - - // Then, mark the request as complete (Very important or the server will hang!) - http_response_builder_done(builder); - - // Note that none of the http_response_builder_* functions return errors. - // This is by design to simplify user endpoint code. If at any point - // something goes wrong, the server will send a code 4xx or 5xx to - // the client or abort the TCP connection entirely. - } - - // This program will loop forever, but if you write - // your server in a way to exit gracefully, this is - // you the server object is freed: - http_server_free(server); - http_global_free(); - - // Have fun. Bye! - return 0; -} \ No newline at end of file diff --git a/examples/server/010_zero_copy_api.c b/examples/server/010_zero_copy_api.c deleted file mode 100644 index 02440ab..0000000 --- a/examples/server/010_zero_copy_api.c +++ /dev/null @@ -1,97 +0,0 @@ -#include -#include - -// This example shows how to generate response bodies -// using the zero-copy API. - -int main(void) -{ - http_global_init(); - - // All the setup is identical to the previous example. - // The only thing that changes where "http_response_builder_body" - // is called. - - HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080); - if (server == NULL) - return -1; - - for (;;) { - - HTTP_Request *req; - HTTP_ResponseBuilder builder; - - int ret = http_server_wait(server, &req, &builder); - if (ret < 0) return -1; - - http_response_builder_status(builder, 200); - http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain")); - - // The previous example used the *_body function to - // write the response body in chunks: - // - // http_response_builder_body(builder, HTTP_STR("Hello")); - // http_response_builder_body(builder, HTTP_STR(", world!")); - // - // This function reads from an user buffer and copies - // the data in the connection's output buffer. If the - // data is not in a contiguous region that's fine as - // the function can be called repeatedly on separate - // chunks. - // - // This function assumes the user is holding in memory - // the data to be sent beforehand, but this may not - // be true. If for instance the data comes from a file, - // the user will need to read from the file, copy in - // memory and then write to the response body. - // - // The zero-copy API allows copying directly from the - // source of the data (such as the read() system call - // on a file descriptor) to the server's output buffer - - char example_data[] = "I'm some example data!"; - int example_data_len = sizeof(example_data)-1; - - // Tell the server how much data we are going to write - http_response_builder_bodycap(builder, example_data_len); - - int cap; - char *dst; - - // Get a pointer to the server's output buffer. The - // output parameter [cap] is the capacity of the region - // and is equal or larger than the data we requested - // with *_bodycap - dst = http_response_builder_bodybuf(builder, &cap); - - // Write the data directly into the output buffer. In - // this example we are copying from memory, but you could - // read from a file or a socket - if (dst) { - memcpy(dst, example_data, example_data_len); - } - - // Tell the server how much bytes we have written to - // the provided region. - http_response_builder_bodyack(builder, example_data_len); - - // The reason we had to guard the [memcpy] by checking the - // [dst] pointer is that if an error occurred internally - // then *_bodybuf will return NULL. This will cause the - // server to either return an internally generated error - // response or drop the connection. The correct thing to - // do in that situation is not access the pointer and do - // as nothing bad happened. - - // As usual, mark the response as complete - http_response_builder_done(builder); - - // If we're being being honest, this is not a zero-copy - // interface. It's more like an N-1 copy interface as in - // it just avoids one copy from userspace to userspace! - } - - http_server_free(server); - http_global_free(); - return 0; -} diff --git a/examples/server/020_response_undo.c b/examples/server/020_response_undo.c deleted file mode 100644 index f751494..0000000 --- a/examples/server/020_response_undo.c +++ /dev/null @@ -1,59 +0,0 @@ -#include -#include - -// This example shows how undo a response that is being built -// when an error occurs. - -int main(void) -{ - http_global_init(); - - HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080); - if (server == NULL) - return -1; - - for (;;) { - - HTTP_Request *req; - HTTP_ResponseBuilder builder; - - int ret = http_server_wait(server, &req, &builder); - if (ret < 0) return -1; - - // Say we are building a request.. - - http_response_builder_status(builder, 200); - http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain")); - - // .. and in the middle of building an error condition - // occurs. Maybe a file was missing or an allocation fails. - // The proper response in this case would be a code 500 - // with an error message, but we already wrote the first - // part of the response assuming the operation would succede. - // - // You can use the *_undo function to reset the response - // building process - - bool error_occurred = true; - if (error_occurred) { - - http_response_builder_undo(builder); - - // Now we are back to setting the status code - http_response_builder_status(builder, 500); - http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain")); - http_response_builder_body(builder, HTTP_STR("An error occurred!")); - http_response_builder_done(builder); - - } else { - - // If no error occures, we finish as planned - http_response_builder_body(builder, HTTP_STR("Hello, world!")); - http_response_builder_done(builder); - } - } - - http_server_free(server); - http_global_free(); - return 0; -} diff --git a/examples/server/030_using_workers.c b/examples/server/030_using_workers.c deleted file mode 100644 index 9fac4de..0000000 --- a/examples/server/030_using_workers.c +++ /dev/null @@ -1,386 +0,0 @@ -#include -#include -#include - -// !!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // -// // -// This example is just a proof of concept for now as the library // -// still isn't thread-safe. // -// // -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // - -// This example shows how to delegate the response creation -// process to other threads. -// -// Your server may have some endpoints that require considerable -// computation or may be waiting for some external system to -// complete. If we used the current pattern we've been using for -// generating requests, following request will have to wait until -// this processing has concluded. -// -// One solution for this situation is to create a separate thread -// to do the waiting or processing. When a request is received -// that requires processing, it is passed to the second thread. -// In the mean time, the main thread can process the next request. -// When the thread has finished, it can just call the usual -// functions to produce a response. - -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -typedef void* Thread; -typedef unsigned long ThreadReturn; -typedef CRITICAL_SECTION Mutex; -typedef CONDITION_VARIABLE Condvar; -#endif - -#ifdef __linux__ -#include -typedef pthread_t Thread; -typedef void* ThreadReturn; -typedef pthread_mutex_t Mutex; -typedef pthread_cond_t Condvar; -#endif - -// The following types are used to describe a job the worker -// needs to work on. -typedef enum { - - // Special value used to tell the worker the program is terminating - NO_JOB, - - // We assume jobs may be of two different types we call A and B - JOB_A, - JOB_B, - -} JobType; - -typedef struct { - JobType type; - HTTP_ResponseBuilder builder; -} Job; - -// Maximum number of jobs that can be buffered at once -#define MAX_JOBS 100 - -void init_job_queue(void); -void free_job_queue(void); - -// This function pops an item from the job queue. If the -// queue is empty, the thread will block until one is -// available. -Job pop_job(void); - -// This function adds a job to the queue. The block argument -// changes the behavior when the queue is full and there is -// no space for a new job. If the block argument is true and -// there is no space, the thread waits. If the argument is -// false the function exits immediately by returning false -// with no new job pushed. -bool push_job(Job job, bool block); - -void thread_create(Thread *thread, void *arg, ThreadReturn (*func)(void*)); -ThreadReturn thread_join(Thread thread); - -void mutex_init(Mutex *mutex); -void mutex_free(Mutex *mutex); -void mutex_lock(Mutex *mutex); -void mutex_unlock(Mutex *mutex); - -void condvar_init(Condvar *condvar); -void condvar_free(Condvar *condvar); -void condvar_wait(Condvar *condvar, Mutex *mutex); -void condvar_signal(Condvar *condvar); - -ThreadReturn worker(void*) -{ - for (bool exit = false; !exit; ) { - - Job job = pop_job(); - - switch (job.type) { - - case NO_JOB: - exit = true; - break; - - case JOB_A: - http_response_builder_status(job.builder, 200); - http_response_builder_body(job.builder, HTTP_STR("Job A completed")); - http_response_builder_done(job.builder); - break; - - case JOB_B: - http_response_builder_status(job.builder, 200); - http_response_builder_body(job.builder, HTTP_STR("Job B completed")); - http_response_builder_done(job.builder); - break; - } - } - - return 0; -} - -int main(void) -{ - http_global_free(); - init_job_queue(); - - HTTP_Server *server = http_server_init(HTTP_STR("127.0.0.1"), 8080); - if (server == NULL) - return -1; - - Thread worker_id; - thread_create(&worker_id, NULL, worker); - - for (;;) { - - HTTP_Request *req; - HTTP_ResponseBuilder builder; - - int ret = http_server_wait(server, &req, &builder); - if (ret < 0) return -1; - - if (http_streq(req->url.path, HTTP_STR("/endpoint_A"))) { - - // Endpoint A sends the job to the worker. - // If too many jobs are queued, it blocks - - Job job; - job.type = JOB_A; - job.builder = builder; - push_job(job, true); - - } else if (http_streq(req->url.path, HTTP_STR("/endpoint_B"))) { - - // Endpoint B sends the job to the worker - // but fails if the queue is full, in which - // case the "503 Service Unavailable" response - // is generated. - - Job job; - job.type = JOB_B; - job.builder = builder; - if (!push_job(job, false)) { - http_response_builder_status(builder, 503); - http_response_builder_done(builder); - } - - } else { - - // Other endpoints may resolve immediately - - http_response_builder_status(builder, 404); - http_response_builder_done(builder); - } - } - - // Stop the worker by sending an empty job - Job job; - job.type = NO_JOB; - push_job(job, true); - thread_join(worker_id); - - http_server_free(server); - free_job_queue(); - http_global_free(); - return 0; -} - -////////////////////////////////////////////// - -// This is a pretty standard condition variable-based -// producer-consumer queue. In this example we are using -// one worker, but we could easily have more than that. - -Job queue[MAX_JOBS]; -int queue_head = 0; -int queue_count = 0; -Mutex queue_lock; -Condvar queue_consume_event; -Condvar queue_produce_event; - -void init_job_queue(void) -{ - mutex_init(&queue_lock); - condvar_init(&queue_consume_event); - condvar_init(&queue_produce_event); -} - -void free_job_queue(void) -{ - condvar_free(&queue_produce_event); - condvar_free(&queue_consume_event); - mutex_free(&queue_lock); -} - -Job pop_job(void) -{ - mutex_lock(&queue_lock); - - while (queue_count == 0) - condvar_wait(&queue_produce_event, &queue_lock); - - Job job = queue[queue_head]; - queue_head = (queue_head + 1) % MAX_JOBS; - queue_count--; - - condvar_signal(&queue_consume_event); - mutex_unlock(&queue_lock); - return job; -} - -bool push_job(Job job, bool block) -{ - mutex_lock(&queue_lock); - if (queue_count == 0) { - - if (!block) { - mutex_unlock(&queue_lock); - return false; - } - - do - condvar_wait(&queue_consume_event, &queue_lock); - while (queue_count == 0); - } - - int tail = (queue_head + queue_count) % MAX_JOBS; - queue[tail] = job; - queue_count++; - - condvar_signal(&queue_produce_event); - mutex_unlock(&queue_lock); - return true; -} - -////////////////////////////////////////////// - -void thread_create(Thread *thread, void *arg, ThreadReturn (*func)(void*)) -{ -#ifdef _WIN32 - Thread thread_ = CreateThread(NULL, 0, func, arg, 0, NULL); - if (thread_ == INVALID_HANDLE_VALUE) - abort(); - *thread = thread_; -#endif - -#ifdef __linux__ - int ret = pthread_create(thread, NULL, func, arg); - if (ret) abort(); -#endif -} - -ThreadReturn thread_join(Thread thread) -{ -#ifdef _WIN32 - ThreadReturn result; - WaitForSingleObject(thread, INFINITE); - if (!GetExitCodeThread(thread, &result)) - abort(); - CloseHandle(thread); - return result; -#endif - -#ifdef __linux__ - ThreadReturn result; - int ret = pthread_join(thread, &result); - if (ret) abort(); - return result; -#endif -} - -void mutex_init(Mutex *mutex) -{ -#ifdef _WIN32 - InitializeCriticalSection(mutex); -#endif - -#ifdef __linux__ - if (pthread_mutex_init(mutex, NULL)) - abort(); -#endif -} - -void mutex_free(Mutex *mutex) -{ -#ifdef _WIN32 - DeleteCriticalSection(mutex); -#endif - -#ifdef __linux__ - if (pthread_mutex_destroy(mutex)) - abort(); -#endif -} - -void mutex_lock(Mutex *mutex) -{ -#ifdef _WIN32 - EnterCriticalSection(mutex); -#endif - -#ifdef __linux__ - if (pthread_mutex_lock(mutex)) - abort(); -#endif -} - -void mutex_unlock(Mutex *mutex) -{ -#ifdef _WIN32 - LeaveCriticalSection(mutex); -#endif - -#ifdef __linux__ - if (pthread_mutex_unlock(mutex)) - abort(); -#endif -} - -void condvar_init(Condvar *condvar) -{ -#ifdef _WIN32 - InitializeConditionVariable(condvar); -#endif - -#ifdef __linux__ - if (pthread_cond_init(condvar, NULL)) - abort(); -#endif -} - -void condvar_free(Condvar *condvar) -{ -#ifdef __linux__ - if (pthread_cond_destroy(condvar)) - abort(); -#else - (void) condvar; -#endif -} - -void condvar_wait(Condvar *condvar, Mutex *mutex) -{ -#ifdef _WIN32 - if (!SleepConditionVariableCS(condvar, mutex, INFINITE)) - abort(); -#endif - -#ifdef __linux__ - int err = pthread_cond_wait(condvar, mutex); - if (err) abort(); -#endif -} - -void condvar_signal(Condvar *condvar) -{ -#ifdef _WIN32 - WakeConditionVariable(condvar); -#endif - -#ifdef __linux__ - if (pthread_cond_signal(condvar)) - abort(); -#endif -} diff --git a/examples/server/040_virtual_hosts.c b/examples/server/040_virtual_hosts.c deleted file mode 100644 index d877a7c..0000000 --- a/examples/server/040_virtual_hosts.c +++ /dev/null @@ -1,2 +0,0 @@ - -int main(void) {} \ No newline at end of file diff --git a/examples/server/050_https_server.c b/examples/server/050_https_server.c deleted file mode 100644 index 75b9059..0000000 --- a/examples/server/050_https_server.c +++ /dev/null @@ -1,78 +0,0 @@ -#include -#include -#include - -// This example shows how to set up an HTTPS (HTTP over TLS) -// server. - -int main(void) -{ - http_global_init(); - - // To setup an HTTPS server, we need to use the *_ex variant - // of the server initialization function as it offers more - // control. Server objects can serve HTTP traffic, HTTPS - // traffic, or both at the same time. The init_ex function - // allows us to control this behavior. - - // The first argument is the local interface address. It - // works just as the other examples but is shared between - // HTTP and HTTPS. Then come the HTTP port and HTTPS port - // arguments. If you want to disable HTTP or HTTPS you can - // pass zero to its port argument. If the HTTPS port is - // not zero, you need to pass the file names of the server's - // certificate and private key. - HTTP_Server *server = http_server_init_ex( - HTTP_STR("127.0.0.1"), // HTTP and HTTPS port - 8080, // HTTP port - 8443, // HTTPS port - HTTP_STR("cert.pem"), - HTTP_STR("privkey.pem") - ); - if (server == NULL) - return -1; - - // Just to be clear, to initialize a plain HTTP server - // using the *_ex function we would do this: - // - // HTTP_Server *server = http_server_init_ex( - // HTTP_STR("127.0.0.1"), // HTTP and HTTPS port - // 8080, // HTTP port - // 0, // HTTPS disabled - // HTTP_STR(""), // ignore - // HTTP_STR("") // ignore - // ); - // - // and if we wanted and HTTPS-only server we would - // do this: - // - // HTTP_Server *server = http_server_init_ex( - // HTTP_STR("127.0.0.1"), // HTTP and HTTPS port - // 0, // HTTP disabled - // 8443, // HTTPS port - // HTTP_STR("cert.pem"), - // HTTP_STR("privkey.pem") - // ); - - // Everything else is identical to the simple HTTP server - // example. - - for (;;) { - - HTTP_Request *req; - HTTP_ResponseBuilder builder; - - int ret = http_server_wait(server, &req, &builder); - if (ret < 0) return -1; - - http_response_builder_status(builder, 200); - http_response_builder_header(builder, HTTP_STR("Content-Type: text/plain")); - http_response_builder_body(builder, HTTP_STR("Hello")); - http_response_builder_body(builder, HTTP_STR(", world!")); - http_response_builder_done(builder); - } - - http_server_free(server); - http_global_free(); - return 0; -} \ No newline at end of file diff --git a/examples/server/060_virtual_hosts_over_https.c b/examples/server/060_virtual_hosts_over_https.c deleted file mode 100644 index 4a90659..0000000 --- a/examples/server/060_virtual_hosts_over_https.c +++ /dev/null @@ -1,168 +0,0 @@ -#include -#include - -// This is an example of how to serve different websites -// over a single HTTPS server instance. - -int setup_test_certificates(void); - -int main(void) -{ - http_global_init(); - - // To test this program you need to add the following - // lines to your hosts file: - // - // 127.0.0.1 websiteA.com - // 127.0.0.1 websiteB.com - // 127.0.0.1 websiteC.com - // - // That you can find at /etc/hosts on Linux and - // C:\Windows\System32\drivers\etc\hosts on Windows - - // First, create three certificates for the domains: - // - // websiteA.com - // websiteB.com - // websiteC.com - // - // This will create a number of certificate files - // and private key files - // - // websiteA_cert.pem websiteA_key.pem - // websiteB_cert.pem websiteB_key.pem - // websiteC_cert.pem websiteC_key.pem - // - // Of course this is just for testing. It is expected - // you have your own. - int ret = setup_test_certificates(); - if (ret < 0) - return -1; - - // First, set up an HTTPS server instance with one - // of the certificate. This will act as default certificate - // when ecrypted connections don't target a specific domain. - HTTP_Server *server = http_server_init_ex( - HTTP_STR("127.0.0.1"), 8080, 8443, - HTTP_STR("websiteA_cert.pem"), - HTTP_STR("websiteA_key.pem") - ); - if (server == NULL) - return -1; - - // Then we can add an arbitrary number of additional - // certificates using the add_website function - - ret = http_server_add_website(server, - HTTP_STR("websiteB.com"), - HTTP_STR("websiteB_cert.pem"), - HTTP_STR("websiteB_key.pem") - ); - if (ret < 0) - return -1; - - ret = http_server_add_website(server, - HTTP_STR("websiteC.com"), - HTTP_STR("websiteC_cert.pem"), - HTTP_STR("websiteC_key.pem") - ); - if (ret < 0) - return -1; - - // Now the server is ready to accept incoming HTTP - // or HTTPS connections. - // - // Note that the add_website function is only used - // to serve the correct certificate to the client. - // The HTTP request itself may very well hold a - // different domain name in the host header: - // - // [client] [server] - // | | - // | TLS hanshake to domain1.com | - // | -------------------------------> | - // | | - // | cert for domain1.com | - // | <------------------------------- | - // | | - // | HTTP request to domain2.com | - // | over the encrypted connection | - // | established with domain1.com | - // | -------------------------------> | - // | | - // | response as domain2.com | - // | <------------------------------- | - // | | - - for (;;) { - - HTTP_Request *req; - HTTP_ResponseBuilder builder; - ret = http_server_wait(server, &req, &builder); - if (ret < 0) - break; - - if (http_match_host(req, HTTP_STR("websiteB.com"), 8080) || - http_match_host(req, HTTP_STR("websiteB.com"), 8443)) { - - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTTP_STR("Hello from websiteB.com!")); - http_response_builder_done(builder); - - } else if (http_match_host(req, HTTP_STR("websiteC.com"), 8080) || - http_match_host(req, HTTP_STR("websiteC.com"), 8443)) { - - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTTP_STR("Hello from websiteC.com!")); - http_response_builder_done(builder); - - } else { - - // Serve websiteA.com by default to be consistent - // with the certificate setup - - http_response_builder_status(builder, 200); - http_response_builder_body(builder, HTTP_STR("Hello from websiteA.com!")); - http_response_builder_done(builder); - } - } - - http_server_free(server); - http_global_free(); - return 0; -} - -int setup_test_certificates(void) -{ - int ret = http_create_test_certificate( - HTTP_STR("IT"), - HTTP_STR("Organization A"), - HTTP_STR("websiteA.com"), - HTTP_STR("websiteA_cert.pem"), - HTTP_STR("websiteA_key.pem") - ); - if (ret < 0) - return -1; - - ret = http_create_test_certificate( - HTTP_STR("IT"), - HTTP_STR("Organization B"), - HTTP_STR("websiteB.com"), - HTTP_STR("websiteB_cert.pem"), - HTTP_STR("websiteB_key.pem") - ); - if (ret < 0) - return -1; - - ret = http_create_test_certificate( - HTTP_STR("IT"), - HTTP_STR("Organization C"), - HTTP_STR("websiteC.com"), - HTTP_STR("websiteC_cert.pem"), - HTTP_STR("websiteC_key.pem") - ); - if (ret < 0) - return -1; - - return 0; -} diff --git a/main.c b/main.c new file mode 100644 index 0000000..2992206 --- /dev/null +++ b/main.c @@ -0,0 +1,5 @@ + +int main(void) +{ + return 0; +} diff --git a/misc/NOTES.md b/misc/NOTES.md deleted file mode 100644 index 1444113..0000000 --- a/misc/NOTES.md +++ /dev/null @@ -1,3 +0,0 @@ -(The following are thoughts that came to mind that I didn't have the time to add to docs but don't want to forget.) - -The immediate mode API allows writing directly to the library's output buffer avoiding intermediate allocations \ No newline at end of file diff --git a/misc/TODO.md b/misc/TODO.md deleted file mode 100644 index 57f57ff..0000000 --- a/misc/TODO.md +++ /dev/null @@ -1,8 +0,0 @@ -Allow resolving requests from different threads -Find a way to make sure chttp.h and chttp.c are always up to date -Find a way to compile OpenSSL on windows -handle 3xx client redirections -add discussion on string management -add discussion on error management -add timers -add debug and release builds \ No newline at end of file diff --git a/misc/amalg.py b/misc/amalg.py index 6777066..14c8409 100644 --- a/misc/amalg.py +++ b/misc/amalg.py @@ -6,56 +6,81 @@ class Amalgamator: self.out += text def append_file(self, file): - self.out += "\n" self.out += "////////////////////////////////////////////////////////////////////////////////////////\n" self.out += "// " + file + "\n" self.out += "////////////////////////////////////////////////////////////////////////////////////////\n" - self.out += "\n" - self.out += "#line 1 \"" + file + "\"\n" self.out += open(file).read() - if len(self.out) > 0 and self.out[len(self.out)-1] != '\n': + if len(self.out) > 0 and self.out[len(self.out) - 1] != "\n": self.out += "\n" def save(self, file): - open(file, 'w').write(self.out) + open(file, "w").write(self.out) -desc = """ -// This file was generated automatically. Do not modify directly! + +desc = """// cHTTP, an HTTP client and server library! +// +// This file was generated automatically. Do not modify directly. +// +// Refer to the end of this file for the license""" + +license = """ +//////////////////////////////////////////////////////////////////////////////////////// +// Copyright 2025 Francesco Cozzuto +// +// 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. +//////////////////////////////////////////////////////////////////////////////////////// """ header = Amalgamator() -header.append_text("#ifndef HTTP_AMALGAMATION\n") -header.append_text("#define HTTP_AMALGAMATION\n") header.append_text(desc) +header.append_file("src/includes.h") header.append_file("src/basic.h") -header.append_file("src/parse.h") -header.append_file("src/engine.h") +header.append_file("src/secure_context.h") +header.append_file("src/socket.h") +header.append_file("src/thread.h") +header.append_file("src/byte_queue.h") header.append_file("src/cert.h") header.append_file("src/client.h") +header.append_file("src/parse.h") header.append_file("src/server.h") -header.append_text("#endif // HTTP_AMALGAMATION\n") +header.append_text(license) header.save("chttp.h") source = Amalgamator() - -source.append_text("#ifndef HTTP_NOINCLUDE\n") -source.append_text("#include \"chttp.h\"\n") +source.append_text(desc) +source.append_text("\n") +source.append_text("#ifndef HTTP_DONT_INCLUDE\n") +source.append_text('#include "chttp.h"\n') source.append_text("#endif\n") - -source.append_file("src/sec.h") -source.append_file("src/socket_raw.h") -source.append_file("src/socket.h") -source.append_file("src/socket_pool.h") source.append_file("src/basic.c") -source.append_file("src/parse.c") -source.append_file("src/engine.c") -source.append_file("src/cert.c") -source.append_file("src/sec.c") -source.append_file("src/socket_raw.c") +source.append_file("src/secure_context.c") source.append_file("src/socket.c") -source.append_file("src/socket_pool.c") +source.append_file("src/thread.c") +source.append_file("src/byte_queue.c") +source.append_file("src/cert.c") source.append_file("src/client.c") +source.append_file("src/parse.c") source.append_file("src/server.c") +header.append_text(license) source.save("chttp.c") diff --git a/src/basic.c b/src/basic.c index e3502e5..961ed6b 100644 --- a/src/basic.c +++ b/src/basic.c @@ -1,9 +1,3 @@ -#include -#include - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#endif bool http_streq(HTTP_String s1, HTTP_String s2) { @@ -100,4 +94,4 @@ void print_bytes(HTTP_String prefix, HTTP_String src) cur++; } putc('\n', stream); -} \ No newline at end of file +} diff --git a/src/basic.h b/src/basic.h index 6c1c892..41a9f28 100644 --- a/src/basic.h +++ b/src/basic.h @@ -1,7 +1,3 @@ -#ifndef CHTTP_BASIC_INCLUDED -#define CHTTP_BASIC_INCLUDED - -#include // String type used throughout cHTTP. typedef struct { @@ -52,30 +48,3 @@ void print_bytes(HTTP_String prefix, HTTP_String src); // TODO: comment #define HTTP_UNPACK(X) (X).len, (X).ptr - -// Macro used to make invariants of the code more explicit. -// -// Say you have some function that operates on two integers -// and that by design their sum is always 100. This macro is -// useful to make that explicit: -// -// void func(int a, int b) -// { -// HTTP_ASSERT(a + b == 100); -// ... -// } -// -// Assertions are about documentation, *not* error management. -// -// In non-release builds (where NDEBUG is not defined) asserted -// expressions are evaluated and if not true, the program is halted. -// This is quite nice as they offer a way to document code in -// a way that can be checked at runtime, unlike regular comments -// like this one. -#ifdef NDEBUG -#define HTTP_ASSERT(X) ((void) 0) -#else -#define HTTP_ASSERT(X) {if (!(X)) { __builtin_trap(); }} -#endif - -#endif // CHTTP_BASIC_INCLUDED \ No newline at end of file diff --git a/src/byte_queue.c b/src/byte_queue.c new file mode 100644 index 0000000..ff16aad --- /dev/null +++ b/src/byte_queue.c @@ -0,0 +1,258 @@ + +void byte_queue_init(ByteQueue *queue, uint32_t limit) +{ + queue->flags = 0; + queue->head = 0; + queue->size = 0; + queue->used = 0; + queue->curs = 0; + queue->limit = limit; + queue->data = NULL; + queue->read_target = NULL; +} + +// Deinitialize the queue +void byte_queue_free(ByteQueue *queue) +{ + if (queue->read_target) { + if (queue->read_target != queue->data) + free(queue->read_target); + queue->read_target = NULL; + queue->read_target_size = 0; + } + + free(queue->data); + queue->data = NULL; +} + +int byte_queue_error(ByteQueue *queue) +{ + return queue->flags & BYTE_QUEUE_ERROR; +} + +int byte_queue_empty(ByteQueue *queue) +{ + return queue->used == 0; +} + +int byte_queue_full(ByteQueue *queue) +{ + return queue->used == queue->limit; +} + +ByteView byte_queue_read_buf(ByteQueue *queue) +{ + if (queue->flags & BYTE_QUEUE_ERROR) + return (ByteView) {NULL, 0}; + + assert((queue->flags & BYTE_QUEUE_READ) == 0); + queue->flags |= BYTE_QUEUE_READ; + queue->read_target = queue->data; + queue->read_target_size = queue->size; + + if (queue->data == NULL) + return (ByteView) {NULL, 0}; + + return (ByteView) { queue->data + queue->head, queue->used }; +} + +void byte_queue_read_ack(ByteQueue *queue, uint32_t num) +{ + if (queue->flags & BYTE_QUEUE_ERROR) + return; + + if ((queue->flags & BYTE_QUEUE_READ) == 0) + return; + + queue->flags &= ~BYTE_QUEUE_READ; + + assert((uint32_t) num <= queue->used); + queue->head += (uint32_t) num; + queue->used -= (uint32_t) num; + queue->curs += (uint32_t) num; + + if (queue->read_target) { + if (queue->read_target != queue->data) + free(queue->read_target); + queue->read_target = NULL; + queue->read_target_size = 0; + } +} + +ByteView byte_queue_write_buf(ByteQueue *queue) +{ + if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL) + return (ByteView) {NULL, 0}; + + assert((queue->flags & BYTE_QUEUE_WRITE) == 0); + queue->flags |= BYTE_QUEUE_WRITE; + + return (ByteView) { + queue->data + (queue->head + queue->used), + queue->size - (queue->head + queue->used), + }; +} + +void byte_queue_write_ack(ByteQueue *queue, uint32_t num) +{ + if (queue->flags & BYTE_QUEUE_ERROR) + return; + + if ((queue->flags & BYTE_QUEUE_WRITE) == 0) + return; + + queue->flags &= ~BYTE_QUEUE_WRITE; + queue->used += num; +} + +int byte_queue_write_setmincap(ByteQueue *queue, uint32_t mincap) +{ + // Sticky error + if (queue->flags & BYTE_QUEUE_ERROR) + return 0; + + // In general, the queue's contents look like this: + // + // size + // v + // [___xxxxxxxxxxxx________] + // ^ ^ ^ + // 0 head head + used + // + // This function needs to make sure that at least [mincap] + // bytes are available on the right side of the content. + // + // We have 3 cases: + // + // 1) If there is enough memory already, this function doesn't + // need to do anything. + // + // 2) If there isn't enough memory on the right but there is + // enough free memory if we cound the left unused region, + // then the content is moved back to the + // start of the buffer. + // + // 3) If there isn't enough memory considering both sides, this + // function needs to allocate a new buffer. + // + // If there are pending read or write operations, the application + // is holding pointers to the buffer, so we need to make sure + // to not invalidate them. The only real problem is pending reads + // since this function can only be called before starting a write + // opearation. + // + // To avoid invalidating the read pointer when we allocate a new + // buffer, we don't free the old buffer. Instead, we store the + // pointer in the "old" field so that the read ack function can + // free it. + // + // To avoid invalidating the pointer when we are moving back the + // content since there is enough memory at the start of the buffer, + // we just avoid that. Even if there is enough memory considering + // left and right free regions, we allocate a new buffer. + + assert((queue->flags & BYTE_QUEUE_WRITE) == 0); + + uint32_t total_free_space = queue->size - queue->used; + uint32_t free_space_after_data = queue->size - queue->used - queue->head; + + int moved = 0; + if (free_space_after_data < mincap) { + + if (total_free_space < mincap || (queue->read_target == queue->data)) { + // Resize required + + if (queue->used + mincap > queue->limit) { + queue->flags |= BYTE_QUEUE_ERROR; + return 0; + } + + uint32_t size; + if (queue->size > UINT32_MAX / 2) + size = UINT32_MAX; + else + size = 2 * queue->size; + + if (size < queue->used + mincap) + size = queue->used + mincap; + + if (size > queue->limit) + size = queue->limit; + + uint8_t *data = malloc(size); + if (!data) { + queue->flags |= BYTE_QUEUE_ERROR; + return 0; + } + + if (queue->used > 0) + memcpy(data, queue->data + queue->head, queue->used); + + if (queue->read_target != queue->data) + free(queue->data); + + queue->data = data; + queue->head = 0; + queue->size = size; + + } else { + // Move required + memmove(queue->data, queue->data + queue->head, queue->used); + queue->head = 0; + } + + moved = 1; + } + + return moved; +} + +void byte_queue_write(ByteQueue *queue, void *ptr, uint32_t len) +{ + byte_queue_write_setmincap(queue, len); + ByteView dst = byte_queue_write_buf(queue); + if (dst.ptr) { + memcpy(dst.ptr, ptr, len); + byte_queue_write_ack(queue, len); + } +} + +ByteQueueOffset byte_queue_offset(ByteQueue *queue) +{ + if (queue->flags & BYTE_QUEUE_ERROR) + return (ByteQueueOffset) { 0 }; + return (ByteQueueOffset) { queue->curs + queue->used }; +} + +void byte_queue_patch(ByteQueue *queue, ByteQueueOffset off, + void *src, uint32_t len) +{ + if (queue->flags & BYTE_QUEUE_ERROR) + return; + + // Check that the offset is in range + assert(off >= queue->curs && off - queue->curs < queue->used); + + // Check that the length is in range + assert(len <= queue->used - (off - queue->curs)); + + // Perform the patch + uint8_t *dst = queue->data + queue->head + (off - queue->curs); + memcpy(dst, src, len); +} + +uint32_t byte_queue_size_from_offset(ByteQueue *queue, ByteQueueOffset off) +{ + return queue->curs + queue->used - off; +} + +void byte_queue_remove_from_offset(ByteQueue *queue, ByteQueueOffset offset) +{ + if (queue->flags & BYTE_QUEUE_ERROR) + return; + + uint64_t num = (queue->curs + queue->used) - offset; + assert(num <= queue->used); + + queue->used -= num; +} diff --git a/src/byte_queue.h b/src/byte_queue.h new file mode 100644 index 0000000..b685713 --- /dev/null +++ b/src/byte_queue.h @@ -0,0 +1,134 @@ +// This is the implementation of a byte queue useful +// for systems that need to process engs of bytes. +// +// It features sticky errors, a zero-copy interface, +// and a safe mechanism to patch previously written +// bytes. +// +// Only up to 4GB of data can be stored at once. + +// Internal use only +enum { + BYTE_QUEUE_ERROR = 1 << 0, + BYTE_QUEUE_READ = 1 << 1, + BYTE_QUEUE_WRITE = 1 << 2, +}; + +typedef struct { + uint8_t *ptr; + size_t len; +} ByteView; + +// Fields are for internal use only +typedef struct { + uint64_t curs; + uint8_t* data; + uint32_t head; + uint32_t size; + uint32_t used; + uint32_t limit; + uint8_t* read_target; + uint32_t read_target_size; + int flags; +} ByteQueue; + +// Represents an offset inside the queue relative +// to the first byte ever appended to the queue, +// therefore consuming bytes from the queue does +// not invalidate this type of offset. +typedef uint64_t ByteQueueOffset; + +// Initialize the queue with a given capacity limit. +// This is just a soft limit. The queue will allocate +// dynamically as needed up to this limit and won't +// grow further. When the limit is reached, http_queue_full +// returns true. +void byte_queue_init(ByteQueue *queue, uint32_t limit); + +// Free resources associated to this queue +void byte_queue_free(ByteQueue *queue); + +// Check whether an error occurred inside the queue +int byte_queue_error(ByteQueue *queue); + +// Returns 1 if the queue has no bytes inside it, +// or 0 otherwise. +int byte_queue_empty(ByteQueue *queue); + +// Returns 1 if the queue reached its limit, or 0 +// otherwise. +int byte_queue_full(ByteQueue *queue); + +// These two functions are to be used together. +// read_buf returns a view into the queue of the +// bytes that can be read from it. The caller can +// decide how many of those bytes can be removed +// by passing the count to the read_ack function. +// If an error occurred inside the queue, this +// function returns an empty view. +// +// Note that the calls to read_buf and read_ack +// may be far apart. Other operations won't interfere +// with the read. The only rule is you can't call +// read_buf multiple times before calling read_ack. +ByteView byte_queue_read_buf(ByteQueue *queue); +void byte_queue_read_ack(ByteQueue *queue, uint32_t num); + +// Similar to the read_buf/read_ack functions, +// but write_buf returns a view of the unused +// memory inside the queue, and write_ack is +// used to tell the queue how many bytes were +// written into it. Note that to ensure there +// is a minimum amount of free space in the queue, +// the user needs to call byte_queue_setmincap. +// If an error occurred inside the queue, this +// function returns an empty view. +// +// Note that the calls to write_buf and write_ack +// may be far apart. Other operations won't interfere +// with the write (except for other byte_queue_write_* +// functions). The only rule is you can't call +// write_buf multiple times before calling write_ack. +ByteView byte_queue_write_buf(ByteQueue *queue); +void byte_queue_write_ack(ByteQueue *queue, uint32_t num); + +// Sets the minimum capacity for the next write +// operation and returns 1 if the content of the +// queue was moved, else 0 is returned. +// +// You must not call this function while a write +// is pending. In other words, you must do this: +// +// byte_queue_write_setmincap(queue, mincap); +// dst = byte_queue_write_buf(queue, &cap); +// ... +// byte_queue_write_ack(num); +// +// And NOT this: +// +// dst = byte_queue_write_buf(queue); +// byte_queue_write_setmincap(queue, mincap); <-- BAD +// ... +// byte_queue_write_ack(num); +// +int byte_queue_write_setmincap(ByteQueue *queue, uint32_t mincap); + +// Write some bytes to the queue. This is a +// short hand for write_buf/memcpy/write_ack +void byte_queue_write(ByteQueue *queue, void *ptr, uint32_t len); + +// Returns the current offset inside the queue +ByteQueueOffset byte_queue_offset(ByteQueue *queue); + +// Writes some bytes at the specified offset. It's +// the responsibility of the user to make sure that +// the offset still refers to content inside the queue. +void byte_queue_patch(ByteQueue *queue, ByteQueueOffset off, void *src, uint32_t len); + +// Returns the number of bytes from the given offset +// to the end of the queue. +uint32_t byte_queue_size_from_offset(ByteQueue *queue, ByteQueueOffset off); + +// Removes all bytes from the given offset to the the +// end of the queue. +void byte_queue_remove_from_offset(ByteQueue *queue, ByteQueueOffset offset); diff --git a/src/cert.c b/src/cert.c index 10e665a..67bc005 100644 --- a/src/cert.c +++ b/src/cert.c @@ -1,19 +1,3 @@ -#include -#include -#include - -#ifdef HTTPS_ENABLED -#include -#include -#include -#include -#include -#include -#endif - -#ifndef HTTP_AMALGAMATION -#include "cert.h" -#endif #ifdef HTTPS_ENABLED @@ -51,10 +35,10 @@ static X509 *create_certificate(EVP_PKEY *pkey, HTTP_String C, HTTP_String O, HT // Set version (version 3) X509_set_version(x509, 2); - + // Set serial number ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); - + // Set validity period X509_gmtime_adj(X509_get_notBefore(x509), 0); X509_gmtime_adj(X509_get_notAfter(x509), 31536000L * days); // days * seconds_per_year @@ -166,4 +150,4 @@ int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, return -1; } -#endif \ No newline at end of file +#endif diff --git a/src/cert.h b/src/cert.h index 4d1f9d1..b56b4ae 100644 --- a/src/cert.h +++ b/src/cert.h @@ -1,10 +1,3 @@ -#ifndef CERT_INCLUDED -#define CERT_INCLUDED - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#endif - // This is an utility to create self-signed certificates // useful when testing HTTPS servers locally. This is only // meant to be used by people starting out with a library @@ -21,5 +14,3 @@ // key file with the key used to sign the certificate. int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, HTTP_String cert_file, HTTP_String key_file); - -#endif // CERT_INCLUDED \ No newline at end of file diff --git a/src/client.c b/src/client.c index b7eb716..8ec236c 100644 --- a/src/client.c +++ b/src/client.c @@ -1,453 +1,112 @@ -#include -#include -#include -#include -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#define POLL WSAPoll -#endif - -#ifdef __linux__ -#include -#define POLL poll -#endif - -#ifndef HTTP_AMALGAMATION -#include "client.h" -#include "engine.h" -#include "socket_pool.h" -#endif - -#define CLIENT_MAX_CONNS 256 - -typedef enum { - CLIENT_CONNECTION_FREE, - CLIENT_CONNECTION_INIT, - CLIENT_CONNECTION_INIT_ERROR, - CLIENT_CONNECTION_WAIT, - CLIENT_CONNECTION_DONE, -} ClientConnectionState; - -typedef struct { - ClientConnectionState state; - uint16_t gen; - SocketHandle sock; - HTTP_Engine eng; - bool trace; - void* user_data; -} ClientConnection; - -struct HTTP_Client { - - SocketPool *socket_pool; - - int num_conns; - ClientConnection conns[CLIENT_MAX_CONNS]; - - int ready_head; - int ready_count; - int ready[CLIENT_MAX_CONNS]; -}; - -int http_global_init(void) +int http_client_init(HTTP_Client *client) { - int ret = socket_pool_global_init(); - if (ret < 0) + client->num_conns = 0; + for (int i = 0; i < HTTP_CLIENT_CAPACITY; i++) + client->conns[i].state = HTTP_CLIENT_CONN_FREE; + + client->num_ready = 0; + client->ready_head = 0; + + if (socket_manager_init(&client->sockets, + client->socket_pool, HTTP_CLIENT_CAPACITY) < 0) return -1; return 0; } -void http_global_free(void) -{ - socket_pool_global_free(); -} - -// Rename the memory function -static void* client_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) { - (void)data; - switch (tag) { - case HTTP_MEMFUNC_MALLOC: - return malloc(len); - case HTTP_MEMFUNC_FREE: - free(ptr); - return NULL; - } - return NULL; -} - -HTTP_Client *http_client_init(void) -{ - HTTP_Client *client = malloc(sizeof(HTTP_Client)); - if (client == NULL) - return NULL; - - int max_socks = 100; - SocketPool *socket_pool = socket_pool_init(HTTP_STR(""), 0, 0, max_socks, false, 0, HTTP_STR(""), HTTP_STR("")); - if (socket_pool == NULL) { - free(client); - return NULL; - } - client->socket_pool = socket_pool; - - for (int i = 0; i < CLIENT_MAX_CONNS; i++) { - client->conns[i].state = CLIENT_CONNECTION_FREE; - client->conns[i].gen = 1; - } - - client->num_conns = 0; - client->ready_head = 0; - client->ready_count = 0; - - return client; -} - void http_client_free(HTTP_Client *client) { - for (int i = 0, j = 0; j < client->num_conns; i++) { + socket_manager_free(&client->sockets); - if (client->conns[i].state == CLIENT_CONNECTION_FREE) + for (int i = 0, j = 0; j < client->num_conns; i++) { + HTTP_ClientConn *conn = &client->conns[i]; + if (conn->state == HTTP_CLIENT_CONN_FREE) continue; j++; - // TODO + http_client_conn_free(conn); } +} - socket_pool_free(client->socket_pool); - free(client); +int http_client_wakeup(HTTP_Client *client) +{ + if (socket_manager_wakeup(&client->sockets) < 0) + return -1; + return 0; } int http_client_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder) { - if (client->num_conns == CLIENT_MAX_CONNS) - return -1; - - int i = 0; - while (client->conns[i].state != CLIENT_CONNECTION_FREE) - i++; - - client->conns[i].sock = -1; - client->conns[i].user_data = NULL; - client->conns[i].trace = false; - client->conns[i].state = CLIENT_CONNECTION_INIT; - http_engine_init(&client->conns[i].eng, 1, client_memfunc, NULL); - - client->num_conns++; - - *builder = (HTTP_RequestBuilder) { client, i, client->conns[i].gen }; - return 0; + // TODO } -int http_client_wait(HTTP_Client *client, HTTP_Response **result, void **user_data) +void http_request_builder_line(HTTP_RequestBuilder builder, xxxx) { - while (client->ready_count == 0) { + // TODO +} - SocketEvent event = socket_pool_wait(client->socket_pool); - switch (event.type) { +void http_request_builder_header(HTTP_RequestBuilder builder, String str) +{ + // TODO +} - case SOCKET_EVENT_DIED: - { - ClientConnection *conn = event.user_data; - conn->state = CLIENT_CONNECTION_DONE; +void http_request_builder_body(HTTP_RequestBuilder builder, String str) +{ + // TODO +} - int tail = (client->ready_head + client->ready_count) % CLIENT_MAX_CONNS; - client->ready[tail] = conn - client->conns; - client->ready_count++; - } - break; +int http_request_builder_send(HTTP_RequestBuilder builder) +{ + // TODO +} - case SOCKET_EVENT_READY: - { - ClientConnection *conn = event.user_data; +int http_client_register_events(HTTP_Client *client, + struct pollfd *polled, int max_polled) +{ + return socket_manager_register_events( + &client->sockets, polled, max_polled); +} - if (conn->sock == -1) - conn->sock = event.handle; +int http_client_process_events(HTTP_Client *client, + struct pollfd *polled, int num_polled) +{ + SocketEvent events[HTTP_CLIENT_CAPACITY]; + int num_events = socket_manager_translate_events( + &client->sockets, polled, num_polled); - HTTP_EngineState engine_state; - engine_state = http_engine_state(&conn->eng); + for (int i = 0; i < num_events; i++) { - if (engine_state == HTTP_ENGINE_STATE_CLIENT_RECV_BUF) { - int len; - char *buf; - buf = http_engine_recvbuf(&conn->eng, &len); - if (buf) { - int ret = socket_pool_read(client->socket_pool, conn->sock, buf, len); - if (conn->trace) - print_bytes(HTTP_STR(">> "), (HTTP_String) { buf, ret }); - http_engine_recvack(&conn->eng, ret); - } - } else if (engine_state == HTTP_ENGINE_STATE_CLIENT_SEND_BUF) { - int len; - char *buf; - buf = http_engine_sendbuf(&conn->eng, &len); - if (buf) { - int ret = socket_pool_write(client->socket_pool, conn->sock, buf, len); - if (conn->trace) - print_bytes(HTTP_STR("<< "), (HTTP_String) { buf, ret }); - http_engine_sendack(&conn->eng, ret); - } - } + if (events[i].type == SOCKET_EVENT_DISCONNECT) { - engine_state = http_engine_state(&conn->eng); + // TODO - if (engine_state == HTTP_ENGINE_STATE_CLIENT_CLOSED || - engine_state == HTTP_ENGINE_STATE_CLIENT_READY) - socket_pool_close(client->socket_pool, conn->sock); - } - break; + } else if (events[i].type == SOCKET_EVENT_READY) { - case SOCKET_EVENT_ERROR: - return -1; - - case SOCKET_EVENT_SIGNAL: - return 1; + // TODO } - } - int index = client->ready[client->ready_head]; - client->ready_head = (client->ready_head + 1) % CLIENT_MAX_CONNS; - client->ready_count--; - - ClientConnection *conn = &client->conns[index]; - - HTTP_Response *result2 = http_engine_getres(&conn->eng); - - if (result) - *result = result2; - - if (user_data) - *user_data = conn->user_data; - - if (result2 == NULL) { - http_engine_free(&conn->eng); - conn->state = CLIENT_CONNECTION_FREE; - client->num_conns--; - } else { - result2->context = client; + // TODO } return 0; } -static ClientConnection *client_builder_to_conn(HTTP_RequestBuilder handle) +bool http_client_next_response(HTTP_Client *client, + HTTP_Response **response); { - if (handle.data0 == NULL) - return NULL; + if (server->num_ready == 0) + return false; - HTTP_Client *client = handle.data0; + HTTP_ClientConn *conn = &client->conns[client->ready_head]; + client->ready_head = (client->ready_head + 1) % HTTP_CLIENT_CAPACITY; + client->num_ready--; - if (handle.data1 >= CLIENT_MAX_CONNS) - return NULL; - - ClientConnection *conn = &client->conns[handle.data1]; - - if (handle.data2 != conn->gen) - return NULL; - - return conn; + assert(conn->state == HTTP_CLIENT_CONN_COMPLETE); + *response = &conn->response; + return true; } -void http_request_builder_user_data(HTTP_RequestBuilder builder, void *user_data) +void http_free_response(HTTP_Response *res) { - ClientConnection *conn = client_builder_to_conn(builder); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT) - return; - - conn->user_data = user_data; + // TODO } - -void http_request_builder_trace(HTTP_RequestBuilder builder, bool trace) -{ - ClientConnection *conn = client_builder_to_conn(builder); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT) - return; - - conn->trace = trace; -} - -void http_request_builder_line(HTTP_RequestBuilder builder, HTTP_Method method, HTTP_String url) -{ - ClientConnection *conn = client_builder_to_conn(builder); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT) - return; - - HTTP_Client *client = builder.data0; - - HTTP_URL parsed_url; - int ret = http_parse_url(url.ptr, url.len, &parsed_url); - if (ret != url.len) { - conn->state = CLIENT_CONNECTION_INIT_ERROR; - return; - } - - bool secure = false; - if (http_streq(parsed_url.scheme, HTTP_STR("https"))) { - secure = true; - } else if (!http_streq(parsed_url.scheme, HTTP_STR("http"))) { - conn->state = CLIENT_CONNECTION_INIT_ERROR; - return; - } - - int port = parsed_url.authority.port; - if (port == 0) { - if (secure) - port = 443; - else - port = 80; - } - - switch (parsed_url.authority.host.mode) { - case HTTP_HOST_MODE_IPV4: ret = socket_pool_connect_ipv4(client->socket_pool, secure, parsed_url.authority.host.ipv4, port, conn); break; - case HTTP_HOST_MODE_IPV6: ret = socket_pool_connect_ipv6(client->socket_pool, secure, parsed_url.authority.host.ipv6, port, conn); break; - case HTTP_HOST_MODE_NAME: ret = socket_pool_connect (client->socket_pool, secure, parsed_url.authority.host.name, port, conn); break; - case HTTP_HOST_MODE_VOID: ret = -1; return; - } - - if (ret < 0) { - conn->state = CLIENT_CONNECTION_INIT_ERROR; - return; - } - - http_engine_url(&conn->eng, method, url, 1); -} - -void http_request_builder_header(HTTP_RequestBuilder handle, HTTP_String str) -{ - ClientConnection *conn = client_builder_to_conn(handle); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT) - return; - - http_engine_header(&conn->eng, str); -} - -void http_request_builder_body(HTTP_RequestBuilder handle, HTTP_String str) -{ - ClientConnection *conn = client_builder_to_conn(handle); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT) - return; - - http_engine_body(&conn->eng, str); -} - -void http_request_builder_submit(HTTP_RequestBuilder handle) -{ - HTTP_Client *client = handle.data0; - ClientConnection *conn = client_builder_to_conn(handle); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT && - conn->state != CLIENT_CONNECTION_INIT_ERROR) - return; - - // TODO: invalidate the handle - - if (conn->state == CLIENT_CONNECTION_INIT_ERROR) { - - conn->state = CLIENT_CONNECTION_DONE; - - int tail = (client->ready_head + client->ready_count) % CLIENT_MAX_CONNS; - client->ready[tail] = conn - client->conns; - client->ready_count++; - - } else { - http_engine_done(&conn->eng); - conn->state = CLIENT_CONNECTION_WAIT; - } -} - -void http_response_free(HTTP_Response *res) -{ - if (res == NULL) - return; - - HTTP_Client *client = res->context; - - ClientConnection *conn = NULL; - for (int i = 0, j = 0; j < client->num_conns; i++) { - - if (client->conns[i].state == CLIENT_CONNECTION_FREE) - continue; - j++; - - if (client->conns[i].state != CLIENT_CONNECTION_DONE) - continue; - - if (http_engine_getres(&client->conns[i].eng) == res) { - conn = &client->conns[i]; - break; - } - } - - HTTP_ASSERT(conn); - - http_engine_free(&conn->eng); - conn->state = CLIENT_CONNECTION_FREE; - client->num_conns--; -} - -static HTTP_Client *default_client___; // TODO: deinitialize the default client when http_global_free is called - -static HTTP_Client *get_default_client(void) -{ - if (default_client___ == NULL) - default_client___ = http_client_init(); - return default_client___; -} - -HTTP_Response *http_get(HTTP_String url, HTTP_String *headers, int num_headers) -{ - HTTP_Client *client = get_default_client(); - if (client == NULL) - return NULL; - - HTTP_RequestBuilder builder; - int ret = http_client_get_builder(client, &builder); - if (ret < 0) - return NULL; - http_request_builder_line(builder, HTTP_METHOD_GET, url); - for (int i = 0; i < num_headers; i++) - http_request_builder_header(builder, headers[i]); - http_request_builder_submit(builder); - - HTTP_Response *res; - ret = http_client_wait(client, &res, NULL); // TODO: it's assumed there is only one request pending - if (ret < 0) - return NULL; - - return res; -} - -HTTP_Response *http_post(HTTP_String url, HTTP_String *headers, int num_headers, HTTP_String body) -{ - HTTP_Client *client = get_default_client(); - if (client == NULL) - return NULL; - - HTTP_RequestBuilder builder; - int ret = http_client_get_builder(client, &builder); - if (ret < 0) - return NULL; - http_request_builder_line(builder, HTTP_METHOD_POST, url); - for (int i = 0; i < num_headers; i++) - http_request_builder_header(builder, headers[i]); - http_request_builder_body(builder, body); - http_request_builder_submit(builder); - - HTTP_Response *res; - ret = http_client_wait(client, &res, NULL); // TODO: it's assumed there is only one request pending - if (ret < 0) - return NULL; - - return res; -} \ No newline at end of file diff --git a/src/client.h b/src/client.h index b0228cc..ac42d2e 100644 --- a/src/client.h +++ b/src/client.h @@ -1,98 +1,118 @@ -#ifndef CLIENT_INCLUDED -#define CLIENT_INCLUDED -#include - -#ifndef HTTP_AMALGAMATION -#include "parse.h" +#ifndef HTTP_CLIENT_CAPACITY +// The maximum ammount of requests that can be performed +// in parallel. +#define HTTP_CLIENT_CAPACITY (1<<7) #endif -// Initialize the global state of cHTTP. -// -// cHTTP tries to avoid global state. What this function -// does is call the global initialization functions of -// its dependencies (OpenSSL and Winsock) -int http_global_init(void); +typedef enum { + HTTP_CLIENT_CONN_FREE, + HTTP_CLIENT_CONN_WAIT_LINE, + HTTP_CLIENT_CONN_WAIT_HEADER, + HTTP_CLIENT_CONN_WAIT_BODY, + HTTP_CLIENT_CONN_FLUSHING, + HTTP_CLIENT_CONN_BUFFERING, + HTTP_CLIENT_CONN_COMPLETE, +} HTTP_ClientConnState; -// Free the global state of cHTTP. -void http_global_free(void); - -// Opaque type describing an "HTTP client". Any request -// that is started must always be associated to an HTTP -// client object. -typedef struct HTTP_Client HTTP_Client; - -// Handle for a pending request. This should be considered -// opaque. Don't read or modify its fields! +// Fields of this struct are private typedef struct { - void *data0; - int data1; - int data2; -} HTTP_RequestBuilder; + HTTP_ClientConnState state; + ByteQueue input; + ByteQueue output; +} HTTP_ClientConn; -// Initialize a client object. If something goes wrong, -// NULL is returned. -HTTP_Client *http_client_init(void); +// Fields of this struct are private +typedef struct { -// Deinitialize a client object + // Array of connections. The counter contains the + // number of structs such that state=FREE. + int num_conns; + HTTP_ClientConn conns[HTTP_CLIENT_CAPACITY]; + + // Queue of indices referring to connections that + // are in the COMPLETE state. + int num_ready; + int ready_head; + int ready[HTTP_CLIENT_CAPACITY]; + + // Asynchronous TCP and TLS socket abstraction + SocketManager sockets; + + // The server object doesn't interact with this + // field directly, it just initializes the socket + // manager with a pointer to it. This allows + // allocating the exact number of sockets we + // will need. + Socket socket_pool[HTTP_CLIENT_CAPACITY]; + +} HTTP_Client; + +// Initialize an HTTP client object. This allows one to +// perform a number of requests in parallel. +int http_client_init(HTTP_Client *client); + +// Release resources associated to a client object. void http_client_free(HTTP_Client *client); -// Create a request object associated to the given client. -// On success, 0 is returned and the handle is initialized. -// On error, -1 is returned. -int http_client_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder); +// When a thread is blocked waiting for client events, +// other threads can call this function to wake it up. +int http_client_wakeup(HTTP_Client *client); -void http_request_builder_user_data(HTTP_RequestBuilder builder, void *user_data); +typedef struct { + HTTP_Client *client; + uint16_t index; + uint16_t gen; +} HTTP_RequestBuilder; -// Enable/disable I/O tracing for the specified request. -// This must be done when the request is in the initialization -// phase. -void http_request_builder_trace(HTTP_RequestBuilder builder, bool trace); +// Create a new request builder object. If the response +// pointer is NULL, a brand new builder is created. If +// response isn't NULL (and http_free_response wasn't +// called on it yet), the connection associated to that +// previous exchange is reused. Note that it's up to the +// user to make sure the requests are targeting the same +// host. Returns 0 on success, -1 on error. +int http_client_get_builder(HTTP_Client *client, + HTTP_Response *response, HTTP_RequestBuilder *builder); -// Set the method and URL of the specified request object. -// This must be the first thing you do after http_client_request -// is called (you may http_request_trace before, but nothing -// else!) -void http_request_builder_line(HTTP_RequestBuilder builder, HTTP_Method method, HTTP_String url); +// Set the URL of the current request. This is the first +// function of the request builder that the user must call. +void http_request_builder_url(HTTP_RequestBuilder builder, String url); -// Append a header to the specified request. You must call -// this after http_request_line and may do so multiple times. -void http_request_builder_header(HTTP_RequestBuilder builder, HTTP_String str); +// After the URL, the user may set zero or more headers. +void http_request_builder_header(HTTP_RequestBuilder builder, String str); -// Append some data to the request's body. You must call -// this after either http_request_line or http_request_header. -void http_request_builder_body(HTTP_RequestBuilder builder, HTTP_String str); +// Append bytes to the request's body. You can call this +// any amount of times, as long as it's after having set +// the URL. +void http_request_builder_body(HTTP_RequestBuilder builder, String str); -// Mark the initialization of the request as completed and -// perform the request. -void http_request_builder_submit(HTTP_RequestBuilder builder); +// Mark this request as complete. This invalidates the +// builder. +void http_request_builder_send(HTTP_RequestBuilder builder); -// Free resources associated to a request. This must be called -// after the request has completed. -// -// TODO: allow aborting pending requests -void http_response_free(HTTP_Response *res); +// List all low-level socket events the client is +// waiting for such that the caller can call poll() +// with it. +int http_client_register_events(HTTP_Client *client, + struct pollfd *polled, int max_polled); -// Wait for the completion of one request associated to -// the client. The handle of the resolved request is returned -// through the handle output parameter. If you're not -// interested in which request completed (like when you -// have only one pending request), you can pass NULL. -// -// On error -1 is retutned, else 0 is returned and the -// handle is initialized. -// -// Note that calling this function when no requests are -// pending is considered an error. -int http_client_wait(HTTP_Client *client, HTTP_Response **res, void **user_data); +// The caller has waited for poll() to return and some +// I/O events to be triggered, so now the HTTP client +// can continue its buffering and flushing operations. +int http_client_process_events(HTTP_Client *client, + struct pollfd *polled, int num_polled); -// TODO: comment -HTTP_Response *http_get(HTTP_String url, - HTTP_String *headers, int num_headers); +// After some I/O events were processes, some responses +// may be availabe. This function returns one of the +// buffered responses. If a request was available, true +// is returned. If no more are avaiable, false is returned. +// The returned response must either be freed using the +// http_free_response function or reused by passing it +// to http_client_get_builder. +bool http_client_next_response(HTTP_Client *client, + HTTP_Response **response); -// TODO: comment -HTTP_Response *http_post(HTTP_String url, - HTTP_String *headers, int num_headers, - HTTP_String body); - -#endif // CLIENT_INCLUDED \ No newline at end of file +// Free a response object. You can't access its fields +// again after this. +void http_free_response(HTTP_Response *res); diff --git a/src/engine.c b/src/engine.c deleted file mode 100644 index d49b138..0000000 --- a/src/engine.c +++ /dev/null @@ -1,988 +0,0 @@ -#include -#include -#include -#include -#include -#include // TODO: remove some of these headers -#include -#include -#include -#include - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#include "engine.h" -#endif - -// This is the implementation of a byte queue useful -// for systems that need to process engs of bytes. -// -// It features sticky errors, a zero-copy interface, -// and a safe mechanism to patch previously written -// bytes. -// -// Only up to 4GB of data can be stored at once. - -enum { - BYTE_QUEUE_ERROR = 1 << 0, - BYTE_QUEUE_READ = 1 << 1, - BYTE_QUEUE_WRITE = 1 << 2, -}; - -static void* -callback_malloc(HTTP_ByteQueue *queue, int len) -{ - return queue->memfunc(HTTP_MEMFUNC_MALLOC, NULL, len, queue->memfuncdata); -} - -static void -callback_free(HTTP_ByteQueue *queue, void *ptr, int len) -{ - queue->memfunc(HTTP_MEMFUNC_FREE, ptr, len, queue->memfuncdata); -} - -// Initialize the queue -static void -byte_queue_init(HTTP_ByteQueue *queue, unsigned int limit, HTTP_MemoryFunc memfunc, void *memfuncdata) -{ - queue->flags = 0; - queue->head = 0; - queue->size = 0; - queue->used = 0; - queue->curs = 0; - queue->limit = limit; - queue->data = NULL; - queue->read_target = NULL; - queue->memfunc = memfunc; - queue->memfuncdata = memfuncdata; -} - -// Deinitialize the queue -static void -byte_queue_free(HTTP_ByteQueue *queue) -{ - if (queue->read_target) { - if (queue->read_target != queue->data) - callback_free(queue, queue->read_target, queue->read_target_size); - queue->read_target = NULL; - queue->read_target_size = 0; - } - - callback_free(queue, queue->data, queue->size); - queue->data = NULL; -} - -static int -byte_queue_error(HTTP_ByteQueue *queue) -{ - return queue->flags & BYTE_QUEUE_ERROR; -} - -static int -byte_queue_empty(HTTP_ByteQueue *queue) -{ - return queue->used == 0; -} - -// Start a read operation on the queue. -// -// This function returnes the pointer to the memory region containing the bytes -// to read. Callers can't read more than [*len] bytes from it. To complete the -// read, the [byte_queue_read_ack] function must be called with the number of -// bytes that were acknowledged by the caller. -// -// Note: -// - You can't have more than one pending read. -static char* -byte_queue_read_buf(HTTP_ByteQueue *queue, int *len) -{ - if (queue->flags & BYTE_QUEUE_ERROR) { - *len = 0; - return NULL; - } - - HTTP_ASSERT((queue->flags & BYTE_QUEUE_READ) == 0); - queue->flags |= BYTE_QUEUE_READ; - queue->read_target = queue->data; - queue->read_target_size = queue->size; - - *len = queue->used; - if (queue->data == NULL) - return NULL; - return queue->data + queue->head; -} - -// Complete a previously started operation on the queue. -static void -byte_queue_read_ack(HTTP_ByteQueue *queue, int num) -{ - HTTP_ASSERT(num >= 0); - - if (queue->flags & BYTE_QUEUE_ERROR) - return; - - if ((queue->flags & BYTE_QUEUE_READ) == 0) - return; - - queue->flags &= ~BYTE_QUEUE_READ; - - HTTP_ASSERT((unsigned int) num <= queue->used); - queue->head += (unsigned int) num; - queue->used -= (unsigned int) num; - queue->curs += (unsigned int) num; - - if (queue->read_target) { - if (queue->read_target != queue->data) - callback_free(queue, queue->read_target, queue->read_target_size); - queue->read_target = NULL; - queue->read_target_size = 0; - } -} - -static char* -byte_queue_write_buf(HTTP_ByteQueue *queue, int *cap) -{ - if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL) { - *cap = 0; - return NULL; - } - - HTTP_ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0); - queue->flags |= BYTE_QUEUE_WRITE; - - unsigned int ucap = queue->size - (queue->head + queue->used); - if (ucap > INT_MAX) ucap = INT_MAX; - - *cap = (int) ucap; - return queue->data + (queue->head + queue->used); -} - -static void -byte_queue_write_ack(HTTP_ByteQueue *queue, int num) -{ - HTTP_ASSERT(num >= 0); - - if (queue->flags & BYTE_QUEUE_ERROR) - return; - - if ((queue->flags & BYTE_QUEUE_WRITE) == 0) - return; - - queue->flags &= ~BYTE_QUEUE_WRITE; - queue->used += (unsigned int) num; -} - -// Sets the minimum capacity for the next write operation -// and returns 1 if the content of the queue was moved, else -// 0 is returned. -// -// You must not call this function while a write is pending. -// In other words, you must do this: -// -// byte_queue_write_setmincap(queue, mincap); -// dst = byte_queue_write_buf(queue, &cap); -// ... -// byte_queue_write_ack(num); -// -// And NOT this: -// -// dst = byte_queue_write_buf(queue, &cap); -// byte_queue_write_setmincap(queue, mincap); <-- BAD -// ... -// byte_queue_write_ack(num); -// -static int -byte_queue_write_setmincap(HTTP_ByteQueue *queue, int mincap) -{ - HTTP_ASSERT(mincap >= 0); - unsigned int umincap = (unsigned int) mincap; - - // Sticky error - if (queue->flags & BYTE_QUEUE_ERROR) - return 0; - - // In general, the queue's contents look like this: - // - // size - // v - // [___xxxxxxxxxxxx________] - // ^ ^ ^ - // 0 head head + used - // - // This function needs to make sure that at least [mincap] - // bytes are available on the right side of the content. - // - // We have 3 cases: - // - // 1) If there is enough memory already, this function doesn't - // need to do anything. - // - // 2) If there isn't enough memory on the right but there is - // enough free memory if we cound the left unused region, - // then the content is moved back to the - // start of the buffer. - // - // 3) If there isn't enough memory considering both sides, this - // function needs to allocate a new buffer. - // - // If there are pending read or write operations, the application - // is holding pointers to the buffer, so we need to make sure - // to not invalidate them. The only real problem is pending reads - // since this function can only be called before starting a write - // opearation. - // - // To avoid invalidating the read pointer when we allocate a new - // buffer, we don't free the old buffer. Instead, we store the - // pointer in the "old" field so that the read ack function can - // free it. - // - // To avoid invalidating the pointer when we are moving back the - // content since there is enough memory at the start of the buffer, - // we just avoid that. Even if there is enough memory considering - // left and right free regions, we allocate a new buffer. - - HTTP_ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0); - - unsigned int total_free_space = queue->size - queue->used; - unsigned int free_space_after_data = queue->size - queue->used - queue->head; - - int moved = 0; - if (free_space_after_data < umincap) { - - if (total_free_space < umincap || (queue->read_target == queue->data)) { - // Resize required - - if (queue->used + umincap > queue->limit) { - queue->flags |= BYTE_QUEUE_ERROR; - return 0; - } - - unsigned int size; - if (queue->size > UINT32_MAX / 2) - size = UINT32_MAX; - else - size = 2 * queue->size; - - if (size < queue->used + umincap) - size = queue->used + umincap; - - if (size > queue->limit) - size = queue->limit; - - char *data = callback_malloc(queue, size); - if (!data) { - queue->flags |= BYTE_QUEUE_ERROR; - return 0; - } - - if (queue->used > 0) - memcpy(data, queue->data + queue->head, queue->used); - - if (queue->read_target != queue->data) - callback_free(queue, queue->data, queue->size); - - queue->data = data; - queue->head = 0; - queue->size = size; - - } else { - // Move required - memmove(queue->data, queue->data + queue->head, queue->used); - queue->head = 0; - } - - moved = 1; - } - - return moved; -} - -static HTTP_ByteQueueOffset -byte_queue_offset(HTTP_ByteQueue *queue) -{ - if (queue->flags & BYTE_QUEUE_ERROR) - return (HTTP_ByteQueueOffset) { 0 }; - return (HTTP_ByteQueueOffset) { queue->curs + queue->used }; -} - -static unsigned int -byte_queue_size_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off) -{ - return queue->curs + queue->used - off; -} - -static void -byte_queue_patch(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off, - char *src, unsigned int len) -{ - if (queue->flags & BYTE_QUEUE_ERROR) - return; - - // Check that the offset is in range - HTTP_ASSERT(off >= queue->curs && off - queue->curs < queue->used); - - // Check that the length is in range - HTTP_ASSERT(len <= queue->used - (off - queue->curs)); - - // Perform the patch - char *dst = queue->data + queue->head + (off - queue->curs); - memcpy(dst, src, len); -} - -static void -byte_queue_remove_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset offset) -{ - if (queue->flags & BYTE_QUEUE_ERROR) - return; - - unsigned long long num = (queue->curs + queue->used) - offset; - HTTP_ASSERT(num <= queue->used); - - queue->used -= num; -} - -static void -byte_queue_write(HTTP_ByteQueue *queue, const char *str, int len) -{ - if (str == NULL) str = ""; - if (len < 0) len = strlen(str); - - int cap; - byte_queue_write_setmincap(queue, len); - char *dst = byte_queue_write_buf(queue, &cap); - if (dst) memcpy(dst, str, len); - byte_queue_write_ack(queue, len); -} - -static void -byte_queue_write_fmt2(HTTP_ByteQueue *queue, const char *fmt, va_list args) -{ - if (queue->flags & BYTE_QUEUE_ERROR) - return; - - va_list args2; - va_copy(args2, args); - - int cap; - byte_queue_write_setmincap(queue, 128); - char *dst = byte_queue_write_buf(queue, &cap); - - int len = vsnprintf(dst, cap, fmt, args); - if (len < 0) { - queue->flags |= BYTE_QUEUE_ERROR; - va_end(args2); - return; - } - - if (len > cap) { - byte_queue_write_ack(queue, 0); - byte_queue_write_setmincap(queue, len+1); - dst = byte_queue_write_buf(queue, &cap); - vsnprintf(dst, cap, fmt, args2); - } - - byte_queue_write_ack(queue, len); - - va_end(args2); -} - -static void -byte_queue_write_fmt(HTTP_ByteQueue *queue, const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - byte_queue_write_fmt2(queue, fmt, args); - va_end(args); -} - -#define TEN_SPACES " " - -void http_engine_init(HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata) -{ - if (client) - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_URL; - else - eng->state = HTTP_ENGINE_STATE_SERVER_RECV_BUF; - - eng->closing = 0; - eng->numexch = 0; - - byte_queue_init(&eng->input, 1<<20, memfunc, memfuncdata); - byte_queue_init(&eng->output, 1<<20, memfunc, memfuncdata); -} - -void http_engine_free(HTTP_Engine *eng) -{ - byte_queue_free(&eng->input); - byte_queue_free(&eng->output); - eng->state = HTTP_ENGINE_STATE_NONE; -} - -void http_engine_close(HTTP_Engine *eng) -{ - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - else - eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; -} - -HTTP_EngineState http_engine_state(HTTP_Engine *eng) -{ - return eng->state; -} - -const char* http_engine_statestr(HTTP_EngineState state) { // TODO: remove - switch (state) { - case HTTP_ENGINE_STATE_NONE: return "NONE"; - case HTTP_ENGINE_STATE_CLIENT_PREP_URL: return "CLIENT_PREP_URL"; - case HTTP_ENGINE_STATE_CLIENT_PREP_HEADER: return "CLIENT_PREP_HEADER"; - case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF: return "CLIENT_PREP_BODY_BUF"; - case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK: return "CLIENT_PREP_BODY_ACK"; - case HTTP_ENGINE_STATE_CLIENT_PREP_ERROR: return "CLIENT_PREP_ERROR"; - case HTTP_ENGINE_STATE_CLIENT_SEND_BUF: return "CLIENT_SEND_BUF"; - case HTTP_ENGINE_STATE_CLIENT_SEND_ACK: return "CLIENT_SEND_ACK"; - case HTTP_ENGINE_STATE_CLIENT_RECV_BUF: return "CLIENT_RECV_BUF"; - case HTTP_ENGINE_STATE_CLIENT_RECV_ACK: return "CLIENT_RECV_ACK"; - case HTTP_ENGINE_STATE_CLIENT_READY: return "CLIENT_READY"; - case HTTP_ENGINE_STATE_CLIENT_CLOSED: return "CLIENT_CLOSED"; - case HTTP_ENGINE_STATE_SERVER_RECV_BUF: return "SERVER_RECV_BUF"; - case HTTP_ENGINE_STATE_SERVER_RECV_ACK: return "SERVER_RECV_ACK"; - case HTTP_ENGINE_STATE_SERVER_PREP_STATUS: return "SERVER_PREP_STATUS"; - case HTTP_ENGINE_STATE_SERVER_PREP_HEADER: return "SERVER_PREP_HEADER"; - case HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF: return "SERVER_PREP_BODY_BUF"; - case HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK: return "SERVER_PREP_BODY_ACK"; - case HTTP_ENGINE_STATE_SERVER_PREP_ERROR: return "SERVER_PREP_ERROR"; - case HTTP_ENGINE_STATE_SERVER_SEND_BUF: return "SERVER_SEND_BUF"; - case HTTP_ENGINE_STATE_SERVER_SEND_ACK: return "SERVER_SEND_ACK"; - case HTTP_ENGINE_STATE_SERVER_CLOSED: return "SERVER_CLOSED"; - default: return "UNKNOWN"; - } -} - -char *http_engine_recvbuf(HTTP_Engine *eng, int *cap) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_RECV_BUF) == 0) { - *cap = 0; - return NULL; - } - - eng->state &= ~HTTP_ENGINE_STATEBIT_RECV_BUF; - eng->state |= HTTP_ENGINE_STATEBIT_RECV_ACK; - - byte_queue_write_setmincap(&eng->input, 1<<9); - if (byte_queue_error(&eng->input)) { - *cap = 0; - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - else - eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; - return NULL; - } - - return byte_queue_write_buf(&eng->input, cap); -} - -static int -should_keep_alive(HTTP_Engine *eng) -{ - HTTP_ASSERT(eng->state & HTTP_ENGINE_STATEBIT_PREP); - -#if 0 - // If the parent system doesn't want us to reuse - // the connection, we certainly can't keep alive. - if ((eng->state & TINYHTTP_STREAM_REUSE) == 0) - return 0; -#endif - - if (eng->numexch >= 100) // TODO: Make this a parameter - return 0; - - HTTP_Request *req = &eng->result.req; - - // If the client is using HTTP/1.0, we can't - // keep alive. - if (req->minor == 0) - return 0; - - // TODO: This assumes "Connection" can only hold a single token, - // but this is not true. - int i = http_find_header(req->headers, req->num_headers, HTTP_STR("Connection")); - if (i >= 0 && http_streqcase(req->headers[i].value, HTTP_STR("Close"))) - return 0; - - return 1; -} - -static void process_incoming_request(HTTP_Engine *eng) -{ - HTTP_ASSERT(eng->state == HTTP_ENGINE_STATE_SERVER_RECV_ACK - || eng->state == HTTP_ENGINE_STATE_SERVER_SEND_ACK - || eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF - || eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR); - - char *src; - int len; - src = byte_queue_read_buf(&eng->input, &len); - - int ret = http_parse_request(src, len, &eng->result.req); - - if (ret == 0) { - byte_queue_read_ack(&eng->input, 0); - eng->state = HTTP_ENGINE_STATE_SERVER_RECV_BUF; - return; - } - - if (ret < 0) { - byte_queue_read_ack(&eng->input, 0); - byte_queue_write(&eng->output, - "HTTP/1.1 400 Bad Request\r\n" - "Connection: Close\r\n" - "Content-Length: 0\r\n" - "\r\n", -1 - ); - if (byte_queue_error(&eng->output)) - eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; - else { - eng->closing = 1; - eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF; - } - return; - } - - HTTP_ASSERT(ret > 0); - - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_STATUS; - eng->reqsize = ret; - eng->keepalive = should_keep_alive(eng); - eng->response_offset = byte_queue_offset(&eng->output); -} - -void http_engine_recvack(HTTP_Engine *eng, int num) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_RECV_ACK) == 0) - return; - - byte_queue_write_ack(&eng->input, num); - - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) { - - char *src; - int len; - src = byte_queue_read_buf(&eng->input, &len); - - int ret = http_parse_response(src, len, &eng->result.res); - - if (ret == 0) { - byte_queue_read_ack(&eng->input, 0); - eng->state = HTTP_ENGINE_STATE_CLIENT_RECV_BUF; - return; - } - - if (ret < 0) { - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - return; - } - - HTTP_ASSERT(ret > 0); - - eng->state = HTTP_ENGINE_STATE_CLIENT_READY; - - } else { - process_incoming_request(eng); - } -} - -char *http_engine_sendbuf(HTTP_Engine *eng, int *len) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_SEND_BUF) == 0) { - *len = 0; - return NULL; - } - - eng->state &= ~HTTP_ENGINE_STATEBIT_SEND_BUF; - eng->state |= HTTP_ENGINE_STATEBIT_SEND_ACK; - - return byte_queue_read_buf(&eng->output, len); -} - -void http_engine_sendack(HTTP_Engine *eng, int num) -{ - if (eng->state != HTTP_ENGINE_STATE_SERVER_SEND_ACK && - eng->state != HTTP_ENGINE_STATE_CLIENT_SEND_ACK) - return; - - byte_queue_read_ack(&eng->output, num); - - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) { - if (byte_queue_empty(&eng->output)) - eng->state = HTTP_ENGINE_STATE_CLIENT_RECV_BUF; - else - eng->state = HTTP_ENGINE_STATE_CLIENT_SEND_BUF; - } else { - if (byte_queue_empty(&eng->output)) { - if (!eng->closing && eng->keepalive) - process_incoming_request(eng); - else - eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; - } else - eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF; - } -} - -HTTP_Request *http_engine_getreq(HTTP_Engine *eng) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_REQUEST) == 0) - return NULL; - return &eng->result.req; -} - -HTTP_Response *http_engine_getres(HTTP_Engine *eng) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_RESPONSE) == 0) - return NULL; - return &eng->result.res; -} - -void http_engine_url(HTTP_Engine *eng, HTTP_Method method, HTTP_String url, int minor) -{ - if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_URL) - return; - - eng->response_offset = byte_queue_offset(&eng->output); // TODO: rename response_offset to something that makes sense for clients - - HTTP_URL parsed_url; - int ret = http_parse_url(url.ptr, url.len, &parsed_url); - if (ret != url.len) { - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_ERROR; - return; - } - - HTTP_String method_and_space = HTTP_STR("???"); - switch (method) { - case HTTP_METHOD_GET : method_and_space = HTTP_STR("GET "); break; - case HTTP_METHOD_HEAD : method_and_space = HTTP_STR("HEAD "); break; - case HTTP_METHOD_POST : method_and_space = HTTP_STR("POST "); break; - case HTTP_METHOD_PUT : method_and_space = HTTP_STR("PUT "); break; - case HTTP_METHOD_DELETE : method_and_space = HTTP_STR("DELETE "); break; - case HTTP_METHOD_CONNECT: method_and_space = HTTP_STR("CONNECT "); break; - case HTTP_METHOD_OPTIONS: method_and_space = HTTP_STR("OPTIONS "); break; - case HTTP_METHOD_TRACE : method_and_space = HTTP_STR("TRACE "); break; - case HTTP_METHOD_PATCH : method_and_space = HTTP_STR("PATCH "); break; - } - - HTTP_String path = parsed_url.path; - if (path.len == 0) - path = HTTP_STR("/"); - - byte_queue_write(&eng->output, method_and_space.ptr, method_and_space.len); - byte_queue_write(&eng->output, path.ptr, path.len); - byte_queue_write(&eng->output, parsed_url.query.ptr, parsed_url.query.len); - byte_queue_write(&eng->output, minor ? " HTTP/1.1\r\nHost: " : " HTTP/1.0\r\nHost: ", -1); - byte_queue_write(&eng->output, parsed_url.authority.host.text.ptr, parsed_url.authority.host.text.len); - if (parsed_url.authority.port > 0) - byte_queue_write_fmt(&eng->output, "%d", parsed_url.authority.port); - byte_queue_write(&eng->output, "\r\n", 2); - - eng->keepalive = 1; // TODO - - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_HEADER; -} - - -static const char* -get_status_text(int code) -{ - switch(code) { - - case 100: return "Continue"; - case 101: return "Switching Protocols"; - case 102: return "Processing"; - - case 200: return "OK"; - case 201: return "Created"; - case 202: return "Accepted"; - case 203: return "Non-Authoritative Information"; - case 204: return "No Content"; - case 205: return "Reset Content"; - case 206: return "Partial Content"; - case 207: return "Multi-Status"; - case 208: return "Already Reported"; - - case 300: return "Multiple Choices"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 303: return "See Other"; - case 304: return "Not Modified"; - case 305: return "Use Proxy"; - case 306: return "Switch Proxy"; - case 307: return "Temporary Redirect"; - case 308: return "Permanent Redirect"; - - case 400: return "Bad Request"; - case 401: return "Unauthorized"; - case 402: return "Payment Required"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 405: return "Method Not Allowed"; - case 406: return "Not Acceptable"; - case 407: return "Proxy Authentication Required"; - case 408: return "Request Timeout"; - case 409: return "Conflict"; - case 410: return "Gone"; - case 411: return "Length Required"; - case 412: return "Precondition Failed"; - case 413: return "Request Entity Too Large"; - case 414: return "Request-URI Too Long"; - case 415: return "Unsupported Media Type"; - case 416: return "Requested Range Not Satisfiable"; - case 417: return "Expectation Failed"; - case 418: return "I'm a teapot"; - case 420: return "Enhance your calm"; - case 422: return "Unprocessable Entity"; - case 426: return "Upgrade Required"; - case 429: return "Too many requests"; - case 431: return "Request Header Fields Too Large"; - case 449: return "Retry With"; - case 451: return "Unavailable For Legal Reasons"; - - case 500: return "Internal Server Error"; - case 501: return "Not Implemented"; - case 502: return "Bad Gateway"; - case 503: return "Service Unavailable"; - case 504: return "Gateway Timeout"; - case 505: return "HTTP Version Not Supported"; - case 509: return "Bandwidth Limit Exceeded"; - } - return "???"; -} - -void http_engine_status(HTTP_Engine *eng, int status) -{ - if (eng->state != HTTP_ENGINE_STATE_SERVER_PREP_STATUS) - return; - - byte_queue_write_fmt(&eng->output, - "HTTP/1.1 %d %s\r\n", - status, get_status_text(status)); - - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_HEADER; -} - -void http_engine_header(HTTP_Engine *eng, HTTP_String str) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_PREP_HEADER) == 0) - return; - - // TODO: Check that the header is valid - - byte_queue_write(&eng->output, str.ptr, str.len); - byte_queue_write(&eng->output, "\r\n", 2); -} - -void http_engine_header_fmt2(HTTP_Engine *eng, const char *fmt, va_list args) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_PREP_HEADER) == 0) - return; - - // TODO: Check that the header is valid - - byte_queue_write_fmt2(&eng->output, fmt, args); - byte_queue_write(&eng->output, "\r\n", 2); -} - -void http_engine_header_fmt(HTTP_Engine *eng, const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - http_engine_header_fmt2(eng, fmt, args); - va_end(args); -} - -static void -complete_message_head(HTTP_Engine *eng) -{ - if (eng->keepalive) byte_queue_write(&eng->output, "Connection: Keep-Alive\r\n", -1); - else byte_queue_write(&eng->output, "Connection: Close\r\n", -1); - - byte_queue_write(&eng->output, "Content-Length: ", -1); - eng->content_length_value_offset = byte_queue_offset(&eng->output); - byte_queue_write(&eng->output, TEN_SPACES "\r\n", -1); - - byte_queue_write(&eng->output, "\r\n", -1); - eng->content_length_offset = byte_queue_offset(&eng->output); -} - -static void complete_message_body(HTTP_Engine *eng) -{ - unsigned int content_length = byte_queue_size_from_offset(&eng->output, eng->content_length_offset); - - if (content_length > UINT32_MAX) { - // TODO - } - - char tmp[10]; - - tmp[0] = '0' + content_length / 1000000000; content_length %= 1000000000; - tmp[1] = '0' + content_length / 100000000; content_length %= 100000000; - tmp[2] = '0' + content_length / 10000000; content_length %= 10000000; - tmp[3] = '0' + content_length / 1000000; content_length %= 1000000; - tmp[4] = '0' + content_length / 100000; content_length %= 100000; - tmp[5] = '0' + content_length / 10000; content_length %= 10000; - tmp[6] = '0' + content_length / 1000; content_length %= 1000; - tmp[7] = '0' + content_length / 100; content_length %= 100; - tmp[8] = '0' + content_length / 10; content_length %= 10; - tmp[9] = '0' + content_length; - - int i = 0; - while (i < 9 && tmp[i] == '0') - i++; - - byte_queue_patch(&eng->output, eng->content_length_value_offset, tmp + i, 10 - i); -} - -void http_engine_body(HTTP_Engine *eng, HTTP_String str) -{ - http_engine_bodycap(eng, str.len); - int cap; - char *buf = http_engine_bodybuf(eng, &cap); - if (buf) { - memcpy(buf, str.ptr, str.len); - http_engine_bodyack(eng, str.len); - } -} - -static void ensure_body_entered(HTTP_Engine *eng) -{ - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) { - - if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_HEADER) { - complete_message_head(eng); - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF; - } - - } else { - - if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_HEADER) { - complete_message_head(eng); - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF; - } - } -} - -void http_engine_bodycap(HTTP_Engine *eng, int mincap) -{ - ensure_body_entered(eng); - if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF && - eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) - return; - - byte_queue_write_setmincap(&eng->output, mincap); -} - -char *http_engine_bodybuf(HTTP_Engine *eng, int *cap) -{ - ensure_body_entered(eng); - if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF && - eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) { - *cap = 0; - return NULL; - } - - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK; - else - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK; - - return byte_queue_write_buf(&eng->output, cap); -} - -void http_engine_bodyack(HTTP_Engine *eng, int num) -{ - if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK && - eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK) - return; - - byte_queue_write_ack(&eng->output, num); - - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF; - else - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF; -} - -void http_engine_done(HTTP_Engine *eng) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_PREP) == 0) - return; - - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) { - - if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_URL) { - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - return; - } - - if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_HEADER) { - complete_message_head(eng); - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF; - } - - if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF) - complete_message_body(eng); - - if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_ERROR) { - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - return; - } - - if (byte_queue_error(&eng->output)) { - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - return; - } - - eng->state = HTTP_ENGINE_STATE_CLIENT_SEND_BUF; - - } else { - - if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_HEADER) { - complete_message_head(eng); - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF; - } - - if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) - complete_message_body(eng); - - if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR) { - byte_queue_remove_from_offset(&eng->output, eng->response_offset); - byte_queue_write(&eng->output, - "HTTP/1.1 500 Internal Server Error\r\n" - "Content-Length: 0\r\n" - "Connection: Close\r\n" - "\r\n", - -1 - ); - } - - if (byte_queue_error(&eng->output)) { - eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; - return; - } - - byte_queue_read_ack(&eng->input, eng->reqsize); - eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF; - } -} - -void http_engine_undo(HTTP_Engine *eng) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_PREP) == 0) - return; - - byte_queue_write_ack(&eng->output, 0); - byte_queue_remove_from_offset(&eng->output, eng->response_offset); - - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_URL; - else - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_STATUS; -} \ No newline at end of file diff --git a/src/engine.h b/src/engine.h deleted file mode 100644 index 70926a6..0000000 --- a/src/engine.h +++ /dev/null @@ -1,122 +0,0 @@ -#ifndef HTTP_ENGINE_INCLUDED -#define HTTP_ENGINE_INCLUDED - -#ifndef HTTP_AMALGAMATION -#include "parse.h" -#endif - -typedef enum { - HTTP_MEMFUNC_MALLOC, - HTTP_MEMFUNC_FREE, -} HTTP_MemoryFuncTag; - -typedef void*(*HTTP_MemoryFunc)(HTTP_MemoryFuncTag tag, - void *ptr, int len, void *data); - -typedef struct { - - HTTP_MemoryFunc memfunc; - void *memfuncdata; - - unsigned long long curs; - - char* data; - unsigned int head; - unsigned int size; - unsigned int used; - unsigned int limit; - - char* read_target; - unsigned int read_target_size; - - int flags; -} HTTP_ByteQueue; - -typedef unsigned long long HTTP_ByteQueueOffset; - -#define HTTP_ENGINE_STATEBIT_CLIENT (1 << 0) -#define HTTP_ENGINE_STATEBIT_CLOSED (1 << 1) -#define HTTP_ENGINE_STATEBIT_RECV_BUF (1 << 2) -#define HTTP_ENGINE_STATEBIT_RECV_ACK (1 << 3) -#define HTTP_ENGINE_STATEBIT_SEND_BUF (1 << 4) -#define HTTP_ENGINE_STATEBIT_SEND_ACK (1 << 5) -#define HTTP_ENGINE_STATEBIT_REQUEST (1 << 6) -#define HTTP_ENGINE_STATEBIT_RESPONSE (1 << 7) -#define HTTP_ENGINE_STATEBIT_PREP (1 << 8) -#define HTTP_ENGINE_STATEBIT_PREP_HEADER (1 << 9) -#define HTTP_ENGINE_STATEBIT_PREP_BODY_BUF (1 << 10) -#define HTTP_ENGINE_STATEBIT_PREP_BODY_ACK (1 << 11) -#define HTTP_ENGINE_STATEBIT_PREP_ERROR (1 << 12) -#define HTTP_ENGINE_STATEBIT_PREP_URL (1 << 13) -#define HTTP_ENGINE_STATEBIT_PREP_STATUS (1 << 14) -#define HTTP_ENGINE_STATEBIT_CLOSING (1 << 15) - -typedef enum { - HTTP_ENGINE_STATE_NONE = 0, - HTTP_ENGINE_STATE_CLIENT_PREP_URL = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_URL, - HTTP_ENGINE_STATE_CLIENT_PREP_HEADER = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER, - HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF, - HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK, - HTTP_ENGINE_STATE_CLIENT_PREP_ERROR = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR, - HTTP_ENGINE_STATE_CLIENT_SEND_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_BUF, - HTTP_ENGINE_STATE_CLIENT_SEND_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_ACK, - HTTP_ENGINE_STATE_CLIENT_RECV_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_BUF, - HTTP_ENGINE_STATE_CLIENT_RECV_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_ACK, - HTTP_ENGINE_STATE_CLIENT_READY = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RESPONSE, - HTTP_ENGINE_STATE_CLIENT_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_CLOSED, - HTTP_ENGINE_STATE_SERVER_RECV_BUF = HTTP_ENGINE_STATEBIT_RECV_BUF, - HTTP_ENGINE_STATE_SERVER_RECV_ACK = HTTP_ENGINE_STATEBIT_RECV_ACK, - HTTP_ENGINE_STATE_SERVER_PREP_STATUS = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_STATUS, - HTTP_ENGINE_STATE_SERVER_PREP_HEADER = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER, - HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF, - HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK, - HTTP_ENGINE_STATE_SERVER_PREP_ERROR = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR, - HTTP_ENGINE_STATE_SERVER_SEND_BUF = HTTP_ENGINE_STATEBIT_SEND_BUF, - HTTP_ENGINE_STATE_SERVER_SEND_ACK = HTTP_ENGINE_STATEBIT_SEND_ACK, - HTTP_ENGINE_STATE_SERVER_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT, -} HTTP_EngineState; - -typedef struct { - HTTP_EngineState state; - HTTP_ByteQueue input; - HTTP_ByteQueue output; - int numexch; - int reqsize; - int closing; - int keepalive; - HTTP_ByteQueueOffset response_offset; - HTTP_ByteQueueOffset content_length_offset; - HTTP_ByteQueueOffset content_length_value_offset; - union { - HTTP_Request req; - HTTP_Response res; - } result; -} HTTP_Engine; - -void http_engine_init (HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata); -void http_engine_free (HTTP_Engine *eng); - -void http_engine_close (HTTP_Engine *eng); -HTTP_EngineState http_engine_state (HTTP_Engine *eng); - -const char* http_engine_statestr(HTTP_EngineState state); // TODO: remove - -char* http_engine_recvbuf (HTTP_Engine *eng, int *cap); -void http_engine_recvack (HTTP_Engine *eng, int num); -char* http_engine_sendbuf (HTTP_Engine *eng, int *len); -void http_engine_sendack (HTTP_Engine *eng, int num); - -HTTP_Request* http_engine_getreq (HTTP_Engine *eng); -HTTP_Response* http_engine_getres (HTTP_Engine *eng); - -void http_engine_url (HTTP_Engine *eng, HTTP_Method method, HTTP_String url, int minor); -void http_engine_status (HTTP_Engine *eng, int status); -void http_engine_header (HTTP_Engine *eng, HTTP_String str); -void http_engine_body (HTTP_Engine *eng, HTTP_String str); -void http_engine_bodycap (HTTP_Engine *eng, int mincap); -char* http_engine_bodybuf (HTTP_Engine *eng, int *cap); -void http_engine_bodyack (HTTP_Engine *eng, int num); -void http_engine_done (HTTP_Engine *eng); -void http_engine_undo (HTTP_Engine *eng); - -#endif // HTTP_ENGINE_INCLUDED \ No newline at end of file diff --git a/src/includes.h b/src/includes.h new file mode 100644 index 0000000..04a13f6 --- /dev/null +++ b/src/includes.h @@ -0,0 +1,13 @@ + +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#else +#include +#include +#include +#endif diff --git a/src/parse.c b/src/parse.c index 8d4c6dd..edf6e63 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1,16 +1,3 @@ -#include // snprintf -#include -#include -#include -#include -#include -#include - -#ifndef HTTP_AMALGAMATION -#include "parse.h" -#include "basic.h" -#endif - // From RFC 9112 // request-target = origin-form // / absolute-form @@ -514,7 +501,7 @@ static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment) s->cur - start, }; - if (s->cur == s->len || s->src[s->cur] != ':') + if (s->cur == s->len || s->src[s->cur] != ':') return -1; // ERROR: Missing ':' after scheme s->cur++; } @@ -577,7 +564,7 @@ static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment) static int parse_authority_form(Scanner *s, HTTP_Host *host, int *port) { int ret; - + ret = parse_host(s, host); if (ret < 0) return ret; @@ -658,7 +645,7 @@ static int parse_request_target(Scanner *s, HTTP_URL *url) bool consume_str(Scanner *scan, HTTP_String token) { - HTTP_ASSERT(token.len > 0); + assert(token.len > 0); if (token.len > scan->len - scan->cur) return false; @@ -692,7 +679,7 @@ static int parse_headers(Scanner *s, HTTP_Header *headers, int max_headers) // obs-text = %x80-FF int start; - + if (s->cur == s->len || !is_tchar(s->src[s->cur])) return -1; // ERROR start = s->cur; @@ -740,7 +727,7 @@ parse_transfer_encoding(HTTP_String src, TransferEncodingOption *dst, int max) int num = 0; for (;;) { - + CONSUME_OPTIONAL_SEQUENCE(&s, is_space); TransferEncodingOption opt; @@ -1304,12 +1291,12 @@ int http_get_param_i(HTTP_String body, HTTP_String str) bool http_match_host(HTTP_Request *req, HTTP_String domain, int port) { int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Host")); - HTTP_ASSERT(idx != -1); // Requests without the host header are always rejected + assert(idx != -1); // Requests without the host header are always rejected char tmp[1<<8]; if (port > -1 && port != 80) { int ret = snprintf(tmp, sizeof(tmp), "%.*s:%d", domain.len, domain.ptr, port); - HTTP_ASSERT(ret > 0); + assert(ret > 0); domain = (HTTP_String) { tmp, ret }; } diff --git a/src/parse.h b/src/parse.h index f63e8f4..bdbfe8b 100644 --- a/src/parse.h +++ b/src/parse.h @@ -1,9 +1,3 @@ -#ifndef PARSE_INCLUDED -#define PARSE_INCLUDED - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#endif #define HTTP_MAX_HEADERS 32 @@ -99,5 +93,3 @@ int http_get_param_i (HTTP_String body, HTTP_String str); // Checks whether the request was meant for the host with the given // domain an port. If port is -1, the default value of 80 is assumed. bool http_match_host(HTTP_Request *req, HTTP_String domain, int port); - -#endif // PARSE_INCLUDED \ No newline at end of file diff --git a/src/sec.c b/src/sec.c deleted file mode 100644 index fa98412..0000000 --- a/src/sec.c +++ /dev/null @@ -1,220 +0,0 @@ -#ifndef HTTP_AMALGAMATION -#include "sec.h" -#endif - -#ifndef HTTPS_ENABLED - -void secure_context_global_init(void) -{ -} - -void secure_context_global_free(void) -{ -} - -int secure_context_init_as_client(SecureContext *sec) -{ - (void) sec; - return 0; -} - -int secure_context_init_as_server(SecureContext *sec, - HTTP_String cert_file, HTTP_String key_file) -{ - (void) sec; - (void) cert_file; - (void) key_file; - return 0; -} - -int secure_context_add_cert(SecureContext *sec, - HTTP_String domain, HTTP_String cert_file, - HTTP_String key_file) -{ - (void) sec; - (void) domain; - (void) cert_file; - (void) key_file; - return -1; -} - -void secure_context_free(SecureContext *sec) -{ - (void) sec; -} - -#else - -void secure_context_global_init(void) -{ - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); -} - -void secure_context_global_free(void) -{ - EVP_cleanup(); -} - -int secure_context_init_as_client(SecureContext *sec) -{ - SSL_CTX *ctx = SSL_CTX_new(TLS_client_method()); - if (!ctx) - return -1; - - SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); - - SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); - - if (SSL_CTX_set_default_verify_paths(ctx) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - sec->is_server = false; - sec->ctx = ctx; - sec->num_certs = 0; - return 0; -} - -static int servername_callback(SSL *ssl, int *ad, void *arg) -{ - SecureContext *sec = arg; - - (void) ad; // TODO: use this? - - const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); - if (servername == NULL) - return SSL_TLSEXT_ERR_NOACK; - - for (int i = 0; i < sec->num_certs; i++) { - CertData *cert = &sec->certs[i]; - if (!strcmp(cert->domain, servername)) { - SSL_set_SSL_CTX(ssl, cert->ctx); - return SSL_TLSEXT_ERR_OK; - } - } - - return SSL_TLSEXT_ERR_NOACK; -} - -int secure_context_init_as_server(SecureContext *sec, - HTTP_String cert_file, HTTP_String key_file) -{ - SSL_CTX *ctx = SSL_CTX_new(TLS_server_method()); - if (!ctx) - return -1; - - SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); - - char cert_buffer[1024]; - if (cert_file.len >= (int) sizeof(cert_buffer)) { - SSL_CTX_free(ctx); - return -1; - } - memcpy(cert_buffer, cert_file.ptr, cert_file.len); - cert_buffer[cert_file.len] = '\0'; - - // Copy private key file path to static buffer - char key_buffer[1024]; - if (key_file.len >= (int) sizeof(key_buffer)) { - SSL_CTX_free(ctx); - return -1; - } - memcpy(key_buffer, key_file.ptr, key_file.len); - key_buffer[key_file.len] = '\0'; - - // Load certificate and private key - if (SSL_CTX_use_certificate_file(ctx, cert_buffer, SSL_FILETYPE_PEM) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - if (SSL_CTX_use_PrivateKey_file(ctx, key_buffer, SSL_FILETYPE_PEM) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - // Verify that the private key matches the certificate - if (SSL_CTX_check_private_key(ctx) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - SSL_CTX_set_tlsext_servername_callback(ctx, servername_callback); - SSL_CTX_set_tlsext_servername_arg(ctx, sec); - - sec->is_server = true; - sec->ctx = ctx; - sec->num_certs = 0; - return 0; -} - -void secure_context_free(SecureContext *sec) -{ - SSL_CTX_free(sec->ctx); - for (int i = 0; i < sec->num_certs; i++) - SSL_CTX_free(sec->certs[i].ctx); -} - -int secure_context_add_cert(SecureContext *sec, - HTTP_String domain, HTTP_String cert_file, - HTTP_String key_file) -{ - if (!sec->is_server) - return -1; - - if (sec->num_certs == MAX_CERTS) - return -1; - - SSL_CTX *ctx = SSL_CTX_new(TLS_server_method()); - if (!ctx) - return -1; - - SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); - - char cert_buffer[1024]; - if (cert_file.len >= (int) sizeof(cert_buffer)) { - SSL_CTX_free(ctx); - return -1; - } - memcpy(cert_buffer, cert_file.ptr, cert_file.len); - cert_buffer[cert_file.len] = '\0'; - - char key_buffer[1024]; - if (key_file.len >= (int) sizeof(key_buffer)) { - SSL_CTX_free(ctx); - return -1; - } - memcpy(key_buffer, key_file.ptr, key_file.len); - key_buffer[key_file.len] = '\0'; - - if (SSL_CTX_use_certificate_file(ctx, cert_buffer, SSL_FILETYPE_PEM) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - if (SSL_CTX_use_PrivateKey_file(ctx, key_buffer, SSL_FILETYPE_PEM) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - if (SSL_CTX_check_private_key(ctx) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - CertData *cert = &sec->certs[sec->num_certs]; - if (domain.len >= (int) sizeof(cert->domain)) { - SSL_CTX_free(ctx); - return -1; - } - memcpy(cert->domain, domain.ptr, domain.len); - cert->domain[domain.len] = '\0'; - cert->ctx = ctx; - sec->num_certs++; - return 0; -} - -#endif \ No newline at end of file diff --git a/src/sec.h b/src/sec.h deleted file mode 100644 index deed1d6..0000000 --- a/src/sec.h +++ /dev/null @@ -1,55 +0,0 @@ -#ifndef SEC_INCLUDED -#define SEC_INCLUDED - - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#endif - -#ifndef HTTPS_ENABLED - -typedef struct { -} SecureContext; - -#else - -#define MAX_CERTS 10 - -#include - -#include - -typedef struct { - char domain[128]; - SSL_CTX *ctx; -} CertData; - -typedef struct { - - bool is_server; - - SSL_CTX *ctx; - - // Only used when server - int num_certs; - CertData certs[MAX_CERTS]; - -} SecureContext; - -#endif - -void secure_context_global_init(void); -void secure_context_global_free(void); - -int secure_context_init_as_client(SecureContext *sec); - -int secure_context_init_as_server(SecureContext *sec, - HTTP_String cert_file, HTTP_String key_file); - -int secure_context_add_cert(SecureContext *sec, - HTTP_String domain, HTTP_String cert_file, - HTTP_String key_file); - -void secure_context_free(SecureContext *sec); - -#endif // SEC_INCLUDED \ No newline at end of file diff --git a/src/secure_context.c b/src/secure_context.c new file mode 100644 index 0000000..a559ec8 --- /dev/null +++ b/src/secure_context.c @@ -0,0 +1,36 @@ + +int global_secure_context_init(void) +{ + // TODO +} + +int global_secure_context_free(void) +{ + // TODO +} + +int client_secure_context_init(ClientSecureContext *ctx) +{ + // TODO +} + +int client_secure_context_free(ClientSecureContext *ctx) +{ + // TODO +} + +int server_secure_context_init(ServerSecureContext *ctx) +{ + // TODO +} + +int server_secure_context_free(ServerSecureContext *ctx) +{ + // TODO +} + +int server_secure_context_add_certificate(ServerSecureContext *ctx, + String domain, String cert_file) +{ + // TODO +} diff --git a/src/secure_context.h b/src/secure_context.h new file mode 100644 index 0000000..6c2fad3 --- /dev/null +++ b/src/secure_context.h @@ -0,0 +1,29 @@ + +int global_secure_context_init(void); +int global_secure_context_free(void); + +typedef struct { +#ifdef HTTPS_ENABLED + // TODO + SSL_CTX *p; +#endif +} ClientSecureContext; + +int client_secure_context_init(ClientSecureContext *ctx); +int client_secure_context_free(ClientSecureContext *ctx); + +typedef struct { + +} SecureDomain; + +typedef struct { +#ifdef HTTPS_ENABLED + // TODO + SSL_CTX *p; +#endif +} ServerSecureContext; + +int server_secure_context_init(ServerSecureContext *ctx); +int server_secure_context_free(ServerSecureContext *ctx); +int server_secure_context_add_certificate(ServerSecureContext *ctx, + String domain, String cert_file, String key_file); diff --git a/src/server.c b/src/server.c index 69fa69e..a732bb5 100644 --- a/src/server.c +++ b/src/server.c @@ -1,307 +1,325 @@ -#include -#include -#include -#include -#ifndef HTTP_AMALGAMATION -#include "engine.h" -#include "server.h" -#include "socket_pool.h" -#endif - -#define MAX_CONNS (1<<10) - -typedef struct { - bool used; - uint16_t gen; - HTTP_Engine engine; - SocketHandle sock; -} Connection; - -struct HTTP_Server { - - SocketPool *socket_pool; - - int num_conns; - Connection conns[MAX_CONNS]; - - int ready_head; - int ready_count; - int ready[MAX_CONNS]; -}; - -HTTP_Server *http_server_init(HTTP_String addr, uint16_t port) +int http_server_init(HTTP_Server *server) { - return http_server_init_ex(addr, port, 0, HTTP_STR(""), HTTP_STR("")); -} - -HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port, - uint16_t secure_port, HTTP_String cert_file, HTTP_String key_file) -{ - HTTP_Server *server = malloc(sizeof(HTTP_Server)); - if (server == NULL) - return NULL; - - int backlog = 32; - bool reuse_addr = true; - SocketPool *socket_pool = socket_pool_init(addr, port, secure_port, MAX_CONNS, reuse_addr, backlog, cert_file, key_file); - if (socket_pool == NULL) { - free(server); - return NULL; - } - - server->socket_pool = socket_pool; server->num_conns = 0; + for (int i = 0; i < HTTP_SERVER_CAPACITY; i++) + server->conns[i].state = HTTP_SERVER_CONN_FREE; + + server->num_ready = 0; server->ready_head = 0; - server->ready_count = 0; - for (int i = 0; i < MAX_CONNS; i++) { - server->conns[i].used = false; - server->conns[i].gen = 1; - } - - return server; + if (socket_manager_init(&server->sockets, + &server->socket_pool, HTTP_SERVER_CAPACITY) < 0) + return -1; + return 0; } void http_server_free(HTTP_Server *server) { - for (int i = 0, j = 0; j < server->num_conns; i++) { + socket_manager_free(&server->sockets); - if (!server->conns[i].used) + for (int i = 0, j = 0; j < server->num_conns; i++) { + HTTP_ServerConn *conn = &server->conns[i]; + if (conn->state != HTTP_SERVER_CONN_FREE) continue; j++; - // TODO + http_server_conn_free(conn); } - - socket_pool_free(server->socket_pool); - free(server); } -int http_server_add_website(HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) +int http_server_listen_tcp(HTTP_Server *server, + String addr, Port port) { - return socket_pool_add_cert(server->socket_pool, domain, cert_file, key_file); -} - -static void* server_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) { - (void)data; - switch (tag) { - case HTTP_MEMFUNC_MALLOC: - return malloc(len); - case HTTP_MEMFUNC_FREE: - free(ptr); - return NULL; - } - return NULL; -} - -int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseBuilder *builder) -{ - while (server->ready_count == 0) { - - SocketEvent event = socket_pool_wait(server->socket_pool); - switch (event.type) { - - case SOCKET_EVENT_DIED: - { - Connection *conn = event.user_data; - if (conn) { - http_engine_free(&conn->engine); - conn->used = false; - conn->gen++; - server->num_conns--; - } - } - break; - - case SOCKET_EVENT_READY: - { - Connection *conn = event.user_data; - if (conn == NULL) { - - // Connection was just accepted - - if (server->num_conns == MAX_CONNS) { - socket_pool_close(server->socket_pool, event.handle); - break; - } - - int i = 0; - while (server->conns[i].used) - i++; - - conn = &server->conns[i]; - conn->used = true; - conn->sock = event.handle; - http_engine_init(&conn->engine, 0, server_memfunc, NULL); - socket_pool_set_user_data(server->socket_pool, event.handle, conn); - server->num_conns++; - } - - switch (http_engine_state(&conn->engine)) { - - int len; - char *buf; - - case HTTP_ENGINE_STATE_SERVER_RECV_BUF: - buf = http_engine_recvbuf(&conn->engine, &len); - if (buf) { - int ret = socket_pool_read(server->socket_pool, conn->sock, buf, len); - http_engine_recvack(&conn->engine, ret); - } - break; - - case HTTP_ENGINE_STATE_SERVER_SEND_BUF: - buf = http_engine_sendbuf(&conn->engine, &len); - if (buf) { - int ret = socket_pool_write(server->socket_pool, conn->sock, buf, len); - http_engine_sendack(&conn->engine, ret); - } - break; - - default: - break; - } - - switch (http_engine_state(&conn->engine)) { - - int tail; - - case HTTP_ENGINE_STATE_SERVER_PREP_STATUS: - tail = (server->ready_head + server->ready_count) % MAX_CONNS; - server->ready[tail] = conn - server->conns; - server->ready_count++; - break; - - case HTTP_ENGINE_STATE_SERVER_CLOSED: - socket_pool_close(server->socket_pool, conn->sock); - break; - - default: - break; - } - } - break; - - case SOCKET_EVENT_ERROR: - return -1; - - case SOCKET_EVENT_SIGNAL: - return 1; - } - } - - int index = server->ready[server->ready_head]; - server->ready_head = (server->ready_head + 1) % MAX_CONNS; - server->ready_count--; - - *req = http_engine_getreq(&server->conns[index].engine); - (*req)->secure = socket_pool_secure(server->socket_pool, server->conns[index].sock); - - *builder = (HTTP_ResponseBuilder) { server, index, server->conns[index].gen }; + if (socket_manager_listen_tcp(&server->sockets, addr, port) < 0) + return -1; return 0; } -static Connection* -server_builder_to_conn(HTTP_ResponseBuilder builder) +int http_server_listen_tls(HTTP_Server *server, + String addr, Port port, String cert_file_name, + String key_file_name) { - HTTP_Server *server = builder.data0; - if (builder.data1 >= MAX_CONNS) - return NULL; - - Connection *conn = &server->conns[builder.data1]; - if (conn->gen != builder.data2) - return NULL; - - return conn; + if (socket_manager_listen_tls(&server->sockets, addr, + port, cert_file_name, key_file_name) < 0) + return -1; + return 0; } -void http_response_builder_status(HTTP_ResponseBuilder res, int status) +int http_server_add_certificate(HTTP_Server *server, + String domain, String cert_file, String key_file) { - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; - - http_engine_status(&conn->engine, status); + if (socket_manager_add_certificate(&server->sockets, + domain, cert_file, key_file) < 0) + return -1; + return 0; } -void http_response_builder_header(HTTP_ResponseBuilder res, HTTP_String str) +int http_server_wakeup(HTTP_Server *server) { - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; - - http_engine_header(&conn->engine, str); + if (socket_manager_wakeup(&server->sockets) < 0) + return -1; + return 0; } -void http_response_builder_body(HTTP_ResponseBuilder res, HTTP_String str) +int http_server_register_events(HTTP_Server *server, + struct pollfd *polled, int max_polled) { - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; - - http_engine_body(&conn->engine, str); + return socket_manager_register_events(&server->sockets, polled, max_polled); } -void http_response_builder_bodycap(HTTP_ResponseBuilder res, int mincap) +// Look at the head of the input buffer to see if +// a request was buffered. If it was, change the +// connection's status to WAIT_STATUS and push it +// to the ready queue. If the request is invalid, +// close the socket. +static void +check_request_buffer(HTTP_Server *server, HTTP_ServerConn *conn) { - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; + assert(conn->state == HTTP_SERVER_CONN_BUFFERING); - http_engine_bodycap(&conn->engine, mincap); + ByteView src = byte_queue_read_buf(&conn->input); + int ret = http_parse_request(src.ptr, src.len, &conn->request); + if (ret < 0) { + + // Invalid request + byte_queue_read_ack(&conn->input, 0); + socket_close(&server->sockets, conn->handle); + + } else if (ret == 0) { + + // Still waiting + byte_queue_read_ack(&conn->input, 0); + + // If the queue reached its limit and we still didn't receive + // a complete request, abort the exchange. + if (byte_queue_full(&conn->input)) + socket_close(&server->sockets, conn->handle); + + } else { + + // Ready + assert(ret == 1); + + conn->state = HTTP_SERVER_CONN_STATUS; + conn->request_len = ret; + conn->response_offset = byte_queue_offset(&conn->output); + + // Push to the ready queue + assert(server->num_ready < HTTP_SERVER_CAPACITY); + int tail = (server->ready_head + server->num_ready) % HTTP_SERVER_CAPACITY; + server->ready[tail] = conn - server->conns; + server->num_ready++; + } } -char *http_response_builder_bodybuf(HTTP_ResponseBuilder res, int *cap) +bool http_server_next_request(HTTP_Server *server, + HTTP_Request **request, HTTP_ResponseBuilder *builder) { - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) { - *cap = 0; - return NULL; - } + if (server->num_ready == 0) + return false; - return http_engine_bodybuf(&conn->engine, cap); + HTTP_ServerConn *conn = &server->conns[server->ready_head]; + server->ready_head = (server->ready_head + 1) % HTTP_SERVER_CAPACITY; + server->num_ready--; + + assert(conn->state == HTTP_SERVER_CONN_WAIT_STATUS); + *request = &conn->request; + *builder = (HTTP_ResponseBuilder) { server, conn - server->conns, conn->gen }; + return true; } -void http_response_builder_bodyack(HTTP_ResponseBuilder res, int num) +int http_server_process_events(HTTP_Server *server, + struct pollfd *polled, int num_polled) { - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; + SocketEvent events[HTTP_SERVER_CAPACITY]; + int num_events = socket_manger_translate_events(&server->sockets, polled, num_polled); + if (num_events < 0) + return -1; - http_engine_bodyack(&conn->engine, num); + for (int i = 0; i < num_events; i++) { + + HTTP_ServerConn *conn = events[i].user; + + if (events[i].type == SOCKET_EVENT_DISCONNECT) { + + http_server_conn_free(conn); + server->num_conns--; + + } else if (events[i].type == SOCKET_EVENT_READY) { + + if (events[i].user == NULL) { + + if (server->num_conns == HTTP_SERVER_CAPACITY) { + socket_close(&server->sockets, events[i].handle); + continue; + } + + int i = 0; + while (server->conns[i].state != HTTP_SERVER_CONN_FREE) { + i++; + assert(i < HTTP_SERVER_CAPACITY); + } + + conn = &server->conns[i]; + http_server_conn_init(conn, events[i].handle); + server->num_conns++; + + socket_set_user(&server->sockets, events[i].handle, conn); + } + + if (conn->state == HTTP_SERVER_CONN_BUFFERING) { + + int min_recv = 1<<10; + byte_queue_write_setmincap(&conn->input, min_recv); + + // Note that it's extra important that we don't + // buffer while the user is building the response. + // If we did that, a resize would invalidate all + // pointers on the parsed request structure. + int num = 0; + ByteView dst = byte_queue_write_buf(&conn->input); + if (dst.len) num = socket_recv(&server->sockets, conn->handle, dst.ptr, dst.len); + byte_queue_write_ack(&conn->input, num); + + if (byte_queue_error(&conn->output)) + socket_close(&server->sockets, conn->handle); + else + check_request_buffer(server, conn); + + } else if (conn->state == HTTP_SERVER_CONN_FLUSHING) { + + int num = 0; + ByteView src = byte_queue_read_buf(&conn->output); + if (src.len) num = socket_recv(&server->sockets, conn->handle, src.ptr, src.len); + byte_queue_read_ack(&conn->output, num); + + if (byte_queue_error(&conn->output)) + socket_close(&server->sockets, conn->handle); + else if (byte_queue_empty(&conn->output)) { + // We finished sending the response. Now we can + // either close the connection or process a new + // buffered request. + if (conn->closing) { + socket_close(&server->sockets, conn->handle); + } else { + check_request_buffer(server, conn); + } + } + } + } + } + + return 0; } -void http_response_builder_undo(HTTP_ResponseBuilder res) +// Get a connection pointer from a response builder. +// If the builder is invalid, returns NULL. +// Note that only connections in the responding states +// can be returned, as any builder is invalidated by +// incrementing the connection's generation counter +// when a response is completed. +static HTTP_ServerConn* +builder_to_conn(HTTP_ResponseBuilder builder) { - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; + HTTP_Server *server = builder.server; + if (server == NULL) + return NULL; - http_engine_undo(&conn->engine); + if (server->index > HTTP_SERVER_CAPACITY) + return NULL; + + HTTP_ServerConn *conn = server->conns[server->index]; + if (conn->gen != builder.gen) + return NULL; + + return conn; } -void http_response_builder_done(HTTP_ResponseBuilder res) +static void +write_status(HTTP_ServerConn *conn, int status) { - HTTP_Server *server = res.data0; - Connection *conn = server_builder_to_conn(res); + byte_queue_write(&conn->output, xxx); +} + +void http_response_builder_status(HTTP_ResponseBuilder builder, int status) +{ + HTTP_ServerConn *conn = builder_to_conn(builder); if (conn == NULL) return; - http_engine_done(&conn->engine); - - conn->gen++; - if (conn->gen == 0 || conn->gen == UINT16_MAX) - conn->gen = 1; - - HTTP_EngineState state = http_engine_state(&conn->engine); - - if (state == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) { - int tail = (server->ready_head + server->ready_count) % MAX_CONNS; - server->ready[tail] = res.data1; - server->ready_count++; + if (conn->state != HTTP_SERVER_CONN_WAIT_STATUS) { + // Reset all response content and start from scrach. + byte_queue_remove_from_offset(&conn->output, conn->response_offset); + conn->state = HTTP_SERVER_CONN_WAIT_STATUS; } - if (state == HTTP_ENGINE_STATE_SERVER_CLOSED) - socket_pool_close(server->socket_pool, conn->sock); -} \ No newline at end of file + write_status(conn, status); + + conn->state = HTTP_SERVER_CONN_WAIT_HEADER; +} + +void http_response_builder_header(HTTP_ResponseBuilder builder, String str) +{ + HTTP_ServerConn *conn = builder_to_conn(builder); + if (conn == NULL) + return; + + if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER) + return; + + byte_queue_write(&conn->output, xxx); +} + +static void append_special_headers(HTTP_ServerConn *conn) +{ + // TODO +} + +static void patch_special_headers(HTTP_ServerConn *conn) +{ + // TODO +} + +void http_response_builder_body(HTTP_ResponseBuilder builder, String str) +{ + HTTP_ServerConn *conn = builder_to_conn(builder); + if (conn == NULL) + return; + + if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER) { + append_special_headers(conn); + conn->state = HTTP_SERVER_CONN_WAIT_BODY; + } + + if (conn->state != HTTP_SERVER_CONN_WAIT_BODY) + return; + + byte_queue_write(&conn->output, str); +} + +void http_response_builder_send(HTTP_ResponseBuilder builder, String str) +{ + HTTP_ServerConn *conn = builder_to_conn(builder); + if (conn == NULL) + return; + + if (conn->state == HTTP_SERVER_CONN_WAIT_STATUS) { + write_status(conn, 500); + conn->state = HTTP_SERVER_CONN_WAIT_HEADER; + } + + if (conn->state == HTTP_SERVER_CONN_WAIT_HEADER) { + append_special_headers(conn); + conn->state = HTTP_SERVER_CONN_WAIT_BODY; + } + + assert(conn->state == HTTP_SERVER_CONN_WAIT_BODY); + patch_special_headers(conn); + + // Remove the buffered request + byte_queue_read_ack(&conn->input, conn->request_len); + + conn->state = HTTP_SERVER_CONN_FLUSHING; + conn->gen++; +} diff --git a/src/server.h b/src/server.h index 2a21262..3554731 100644 --- a/src/server.h +++ b/src/server.h @@ -1,35 +1,174 @@ -#ifndef HTTP_SERVER_INCLUDED -#define HTTP_SERVER_INCLUDED -#include - -#ifndef HTTP_AMALGAMATION -#include "parse.h" +#ifndef HTTP_SERVER_CAPACITY +// The maximum ammount of requests that can be handled +// in parallel. +#define HTTP_SERVER_CAPACITY (1<<9) #endif +typedef enum { + + // This struct is unused + HTTP_SERVER_CONN_FREE, + + // No request was buffered yet. + HTTP_SERVER_CONN_BUFFERING, + + // A request was just buffered and is waiting for + // the user to build a response. To be specific, + // it's waiting for the user to set a response status. + HTTP_SERVER_CONN_WAIT_STATUS, + + // A request is buffered and a status was set. Now + // the user can set a header or append the first + // bytes of the response body. + HTTP_SERVER_CONN_WAIT_HEADER, + + // A request is buffered and some bytes were appended + // to the response. Now the user can either append more + // bytes or send out the response. + HTTP_SERVER_CONN_WAIT_BODY, + + // A response has been produced and it's being flushed. + HTTP_SERVER_CONN_FLUSHING, + +} HTTP_ServerConnState; + +// This structure represents the HTTP connection to +// a client. typedef struct { - void *data0; - int data1; - int data2; + + // If false, this struct is unused + HTTP_ServerConnState state; + + // Handle to the socket + SocketHandle handle; + + // Data received by the client + ByteQueue input; + + // Data being sent to the client + ByteQueue output; + + // When the state is WAIT_STATUS, WAIT_HEADER, + // or WAIT_BODY, this contains the parsed version + // of the buffered request. + HTTP_Request request; + + // Length of the buffered request when the request + // field is valid. + int request_len; + + // Offset of the first response byte in the output + // buffer. This is useful when the user wants to + // undo the response it's building and start from + // scratch. + ByteQueueOffset response_offset; + +} HTTP_ServerConn; + +typedef struct { + + // Array of connections. The counter contains the + // number of structs such that state=FREE. + int num_conns; + HTTP_ServerConn conns[HTTP_SERVER_CAPACITY]; + + // Queue of indices referring to connections that + // are in the WAIT_STATUS state. + int num_ready; + int ready_head; + int ready[HTTP_SERVER_CAPACITY]; + + // Asynchronous TCP and TLS socket abstraction + SocketManager sockets; + + // The server object doesn't interact with this + // field directly, it just initializes the socket + // manager with a pointer to it. This allows + // allocating the exact number of sockets we + // will need. + Socket socket_pool[HTTP_SERVER_CAPACITY]; + +} HTTP_Server; + +// Initialize the HTTP server object. By default, it won't +// listen for connections. You need to call +// +// http_server_listen_tcp +// http_server_listen_tls +// +// to listen for connection. Note that you can have a +// single server listening for HTTP and HTTPS requests +// by calling both. +int http_server_init(HTTP_Server *server); + +// Release resources associated to the server. +void http_server_free(HTTP_Server *server); + +// Enable listening for plain HTTP requests at the +// specified interface. +int http_server_listen_tcp(HTTP_Server *server, + String addr, Port port); + +// Enable listening for HTTPS requests at the specified +// interfact, using the specified certificate and key +// to verify the connection. +int http_server_listen_tls(HTTP_Server *server, + String addr, Port port, String cert_file_name, + String key_file_name); + +// Add the certificate for an additional domain when +// the server is listening for HTTPS requests. +int http_server_add_certificate(HTTP_Server *server, + String domain, String cert_file, String key_file); + +// When a thread is blocked waiting for server events, +// other threads can call this function to wake it up. +int http_server_wakeup(HTTP_Server *server); + +// List all low-level socket events the server is +// waiting for such that the caller can call poll() +// with it. +int http_server_register_events(HTTP_Server *server, + struct pollfd *polled, int max_polled); + +// The caller has waited for poll() to return and some +// I/O events to be triggered, so now the HTTP server +// can continue its buffering and flushing operations. +int http_server_process_events(HTTP_Server *server, + struct pollfd *polled, int num_polled); + +typedef struct { + HTTP_Server *server; + uint16_t index; + uint16_t gen; } HTTP_ResponseBuilder; -typedef struct HTTP_Server HTTP_Server; +// After some I/O events were processes, some requests +// may be availabe. This function returns one of the +// buffered requests. If a request was available, true +// is returned. If no more are avaiable, false is returned. +// Note that It's possible to get multiple requests to +// respond in batches. +// For each request returned by this function, the user +// must build a response using the response builder API. +bool http_server_next_request(HTTP_Server *server, + HTTP_Request **request, HTTP_ResponseBuilder *builder); -HTTP_Server *http_server_init(HTTP_String addr, uint16_t port); +// This function is called to set the status code of +// a request's response. If this function is called +// after the other response builder functions, it will +// reset the response and set a new status. +void http_response_builder_status(HTTP_ResponseBuilder builder, int status); -HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port, - uint16_t secure_port, HTTP_String cert_key, HTTP_String private_key); +// Append a header to the response. This can only be +// used after having set the status and before appending +// to the body. +void http_response_builder_header(HTTP_ResponseBuilder builder, String str); -void http_server_free (HTTP_Server *server); -int http_server_wait (HTTP_Server *server, HTTP_Request **req, HTTP_ResponseBuilder *handle); -int http_server_add_website (HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file); -void http_response_builder_status (HTTP_ResponseBuilder res, int status); -void http_response_builder_header (HTTP_ResponseBuilder res, HTTP_String str); -void http_response_builder_body (HTTP_ResponseBuilder res, HTTP_String str); -void http_response_builder_bodycap (HTTP_ResponseBuilder res, int mincap); -char* http_response_builder_bodybuf (HTTP_ResponseBuilder res, int *cap); -void http_response_builder_bodyack (HTTP_ResponseBuilder res, int num); -void http_response_builder_undo (HTTP_ResponseBuilder res); -void http_response_builder_done (HTTP_ResponseBuilder res); +// Append some bytes to the response's body +void http_response_builder_body(HTTP_ResponseBuilder builder, String str); -#endif // HTTP_SERVER_INCLUDED \ No newline at end of file +// Mark the response as complete. This will invalidate +// the response builder handle. +void http_response_builder_send(HTTP_ResponseBuilder builder, String str); diff --git a/src/socket.c b/src/socket.c index 7b68ab1..fc29021 100644 --- a/src/socket.c +++ b/src/socket.c @@ -1,264 +1,224 @@ -#include // snprintf -#include -#include -#include +static int create_socket_pair(NATIVE_SOCKET *a, NATIVE_SOCKET *b) +{ #ifdef _WIN32 -#include + SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (sock == INVALID_SOCKET) + return -1; + + // Bind to loopback address with port 0 (dynamic port assignment) + struct sockaddr_in addr; + int addr_len = sizeof(addr); + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // 127.0.0.1 + addr.sin_port = 0; // Let system choose port + + if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { + closesocket(sock); + return -1; + } + + if (getsockname(sock, (struct sockaddr*)&addr, &addr_len) == SOCKET_ERROR) { + closesocket(sock); + return -1; + } + + if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { + closesocket(sock); + return -1; + } + + // Optional: Set socket to non-blocking mode + // This prevents send() from blocking if the receive buffer is full + u_long mode = 1; + ioctlsocket(sock, FIONBIO, &mode); // TODO: does this fail? + + *a = sock; + *b = sock; + return 0; +#else + int fds[2]; + if (pipe(fds) < 0) + return -1; + *a = fds[0]; + *b = fds[1]; + return 0; +#endif +} + +static int set_socket_blocking(NATIVE_SOCKET sock, bool value) +{ +#ifdef _WIN32 + u_long mode = !value; + if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR) + return -1; #endif #ifdef __linux__ -#include -#include -#include -#include -#endif - -#ifdef HTTPS_ENABLED -#include -#include -#endif - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#include "socket.h" -#endif - -typedef struct { - bool is_ipv4; - union { - HTTP_IPv4 ipv4; - HTTP_IPv6 ipv6; - }; -} PendingConnectAddr; - -struct PendingConnect { - uint16_t port; - int cursor; - int num_addrs; - int max_addrs; - PendingConnectAddr *addrs; - char* hostname; // null-terminated - int hostname_len; -}; - -static PendingConnect* -pending_connect_init(HTTP_String hostname, uint16_t port, int max_addrs) -{ - PendingConnect *pending_connect = malloc(sizeof(PendingConnect) + max_addrs * sizeof(PendingConnectAddr) + hostname.len + 1); - if (pending_connect == NULL) - return NULL; - pending_connect->port = port; - pending_connect->cursor = 0; - pending_connect->num_addrs = 0; - pending_connect->max_addrs = max_addrs; - pending_connect->addrs = (PendingConnectAddr*) (pending_connect + 1); - pending_connect->hostname = (char*) (pending_connect->addrs + max_addrs); - memcpy(pending_connect->hostname, hostname.ptr, hostname.len); - pending_connect->hostname[hostname.len] = '\0'; - pending_connect->hostname_len = hostname.len; - return pending_connect; -} - -static void -pending_connect_free(PendingConnect *pending_connect) -{ - free(pending_connect); -} - -static void -pending_connect_add_ipv4(PendingConnect *pending_connect, HTTP_IPv4 ipv4) -{ - if (pending_connect->num_addrs == pending_connect->max_addrs) - return; - pending_connect->addrs[pending_connect->num_addrs++] = (PendingConnectAddr) { .is_ipv4=true, .ipv4=ipv4 }; -} - -static void -pending_connect_add_ipv6(PendingConnect *pending_connect, HTTP_IPv6 ipv6) -{ - if (pending_connect->num_addrs == pending_connect->max_addrs) - return; - pending_connect->addrs[pending_connect->num_addrs++] = (PendingConnectAddr) { .is_ipv4=false, .ipv6=ipv6 }; -} - -static int -next_connect_addr(PendingConnect *pending_connect, PendingConnectAddr *addr) -{ - if (pending_connect->cursor == pending_connect->num_addrs) + int flags = fcntl(sock, F_GETFL, 0); + if (flags < 0) return -1; - *addr = pending_connect->addrs[pending_connect->cursor++]; + if (value) flags &= ~O_NONBLOCK; + else flags |= O_NONBLOCK; + if (fcntl(sock, F_SETFL, flags) < 0) + return -1; +#endif + return 0; } -// Initializes a FREE socket with the information required to -// connect to specified host name. The resulting socket state -// is DIED if an error occurred or PENDING. -void socket_connect(Socket *sock, SecureContext *sec, - HTTP_String hostname, uint16_t port, void *user_data) +static NATIVE_SOCKET create_listen_socket(String addr, + Port port, bool reuse_addr, int backlog) { - PendingConnect *pending_connect; + NATIVE_SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == NATIVE_SOCKET_INVALID) + return NATIVE_SOCKET_INVALID; - int max_addrs = 30; - pending_connect = pending_connect_init(hostname, port, max_addrs); - if (pending_connect == NULL) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return; + if (set_socket_blocking(sock, false) < 0) { + CLOSE_NATIVE_SOCKET(sock); + return NATIVE_SOCKET_INVALID; } - char portstr[16]; - int len = snprintf(portstr, sizeof(portstr), "%u", port); - if (len < 0 || len >= (int) sizeof(portstr)) { - pending_connect_free(pending_connect); - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return; + if (reuse_addr) { + int one = 1; + setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &one, sizeof(one)); } - // DNS query - struct addrinfo hints = {0}; - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; + struct in_addr addr_buf; + if (addr.len == 0) + addr_buf.s_addr = htonl(INADDR_ANY); + else { - struct addrinfo *res = NULL; - int ret = getaddrinfo(pending_connect->hostname, portstr, &hints, &res); - if (ret != 0) { - pending_connect_free(pending_connect); - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return; - } + char copy[100]; + if (addr.len >= (int) sizeof(copy)) { + CLOSE_NATIVE_SOCKET(sock); + return NATIVE_SOCKET_INVALID; + } + memcpy(copy, addr.ptr, addr.len); + copy[addr.len] = '\0'; - for (struct addrinfo *rp = res; rp; rp = rp->ai_next) { - if (rp->ai_family == AF_INET) { - HTTP_IPv4 *ipv4 = (void*) &((struct sockaddr_in*)rp->ai_addr)->sin_addr; - pending_connect_add_ipv4(pending_connect, *ipv4); - } else if (rp->ai_family == AF_INET6) { - HTTP_IPv6 *ipv6 = (void*) &((struct sockaddr_in6*)rp->ai_addr)->sin6_addr; - pending_connect_add_ipv6(pending_connect, *ipv6); + if (inet_pton(AF_INET, copy, &addr_buf) < 0) { + CLOSE_NATIVE_SOCKET(sock); + return NATIVE_SOCKET_INVALID; } } - freeaddrinfo(res); - - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - - sock->raw = BAD_SOCKET; - sock->user_data = user_data; - sock->pending_connect = pending_connect; - sock->sec = sec; - -#ifdef HTTPS_ENABLED - sock->ssl = NULL; -#endif - - socket_update(sock); -} - -// Just like socket_connect, but the raw IPv4 address is specified -void socket_connect_ipv4(Socket *sock, SecureContext *sec, - HTTP_IPv4 addr, uint16_t port, void *user_data) -{ - PendingConnect *pending_connect; - - pending_connect = pending_connect_init(HTTP_STR(""), port, 1); - if (pending_connect == NULL) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return; + struct sockaddr_in bind_buf; + bind_buf.sin_family = AF_INET; + bind_buf.sin_addr = addr_buf; + bind_buf.sin_port = htons(port); + if (bind(sock, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) { // TODO: how does bind fail on windows? + CLOSE_NATIVE_SOCKET(sock); + return NATIVE_SOCKET_INVALID; } - pending_connect_add_ipv4(pending_connect, addr); - - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - - sock->raw = BAD_SOCKET; - sock->user_data = user_data; - sock->pending_connect = pending_connect; - sock->sec = sec; - -#ifdef HTTPS_ENABLED - sock->ssl = NULL; -#endif - - socket_update(sock); -} - -// Just like socket_connect, but the raw IPv6 address is specified -void socket_connect_ipv6(Socket *sock, SecureContext *sec, - HTTP_IPv6 addr, uint16_t port, void *user_data) -{ - PendingConnect *pending_connect; - - pending_connect = pending_connect_init(HTTP_STR(""), port, 1); - if (pending_connect == NULL) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return; + if (listen(sock, backlog) < 0) { // TODO: how does listen fail on windows? + CLOSE_NATIVE_SOCKET(sock); + return NATIVE_SOCKET_INVALID; } - pending_connect_add_ipv6(pending_connect, addr); - - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - - sock->raw = BAD_SOCKET; - sock->user_data = user_data; - sock->pending_connect = pending_connect; - sock->sec = sec; - -#ifdef HTTPS_ENABLED - sock->ssl = NULL; -#endif - - socket_update(sock); + return sock; } -void socket_accept(Socket *sock, SecureContext *sec, RAW_SOCKET raw) +static void close_socket_pair(NATIVE_SOCKET a, NATIVE_SOCKET b) { - sock->state = SOCKET_STATE_ACCEPTED; - sock->raw = raw; - sock->events = 0; - sock->user_data = NULL; - sock->pending_connect = NULL; - sock->sec = sec; - -#ifdef HTTPS_ENABLED - sock->ssl = NULL; +#ifdef _WIN32 + closesocket(a); + (void) b; +#else + close(a); + close(b); #endif +} - if (set_socket_blocking(raw, false) < 0) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return; +int socket_manager_init(SocketManager *sm, Socket *socks, + int num_socks) +{ + if (mutex_init(&sm->mutex) < 0) + return -1; + sm->plain_sock = NATIVE_SOCKET_INVALID; + sm->secure_sock = NATIVE_SOCKET_INVALID; + if (create_socket_pair(&sm->wait_sock, &sm->signal_sock) < 0) + return -1; + sm->at_least_one_secure_connect = false; + + sm->num_used = 0; + sm->max_used = num_socks; + sm->sockets = socks; + return 0; +} + +void socket_manager_free(SocketManager *sm) +{ + close_socket_pair(sm->wait_sock, sm->signal_sock); + + if (sm->secure_sock != NATIVE_SOCKET_INVALID) + server_secure_context_free(&sm->server_secure_context); + + if (sm->at_least_one_secure_connect) + client_secure_context_free(&sm->client_secure_context); + + if (sm->plain_sock != NATIVE_SOCKET_INVALID) + CLOSE_NATIVE_SOCKET(sm->plain_sock); + + if (sm->secure_sock != NATIVE_SOCKET_INVALID) + CLOSE_NATIVE_SOCKET(sm->secure_sock); + + mutex_free(&sm->mutex); +} + +int socket_manager_listen_tcp(SocketManager *sm, + String addr, Port port) +{ + if (sm->plain_sock != NATIVE_SOCKET_INVALID) + return -1; + + bool reuse_addr = false; + int backlog = 32; + sm->plain_sock = create_listen_socket(addr, port, reuse_addr, backlog); + if (sm->plain_sock == NATIVE_SOCKET_INVALID) + return -1; + + return 0; +} + +int socket_manager_listen_tls(SocketManager *sm, + String addr, Port port, String cert_file_name, + String key_file_name) +{ + if (sm->secure_sock != NATIVE_SOCKET_INVALID) + return -1; + + bool reuse_addr = false; + int backlog = 32; + sm->secure_sock = create_listen_socket(addr, port, reuse_addr, backlog); + if (sm->secure_sock == NATIVE_SOCKET_INVALID) + return -1; + + if (server_secure_context_init(&sm->server_secure_context) < 0) { + CLOSE_NATIVE_SOCKET(sm->secure_sock); + sm->secure_sock = NATIVE_SOCKET_INVALID; + return -1; } - socket_update(sock); + return 0; } -void socket_close(Socket *sock) +int socket_manager_add_certificate(SocketManager *sm, + String domain, String cert_file, String key_file) { - // TODO: maybe we don't want to always set to SHUTDOWN. What if the socket is DIED for instance? - sock->state = SOCKET_STATE_SHUTDOWN; - sock->events = 0; - socket_update(sock); -} + if (sm->secure_sock == NATIVE_SOCKET_INVALID) + return -1; -bool socket_ready(Socket *sock) -{ - return sock->state == SOCKET_STATE_ESTABLISHED_READY; -} + int ret = server_secure_context_add_certificate( + &sm->server_secure_context, domain, cert_file, key_file); + if (ret < 0) + return -1; -bool socket_died(Socket *sock) -{ - return sock->state == SOCKET_STATE_DIED; + return 0; } -// TODO: when is the pending_connect data freed? - static bool connect_pending(void) { #ifdef _WIN32 @@ -269,7 +229,18 @@ static bool connect_pending(void) } static bool -connect_failed_because_or_peer_2(int err) +connect_failed_because_of_peer(void) +{ +#ifdef _WIN32 + int err = WSAGetLastError(); +#else + int err = errno; +#endif + return connect_failed_because_of_peer_2(err); +} + +static bool +connect_failed_because_of_peer_2(int err) { #ifdef _WIN32 return err == WSAECONNREFUSED @@ -284,507 +255,669 @@ connect_failed_because_or_peer_2(int err) #endif } -static bool -connect_failed_because_or_peer(void) +// This function moves the socket state machine +// to the next state until an I/O event would +// be required to continue. +static void socket_update(Socket *socket) { -#ifdef _WIN32 - int err = WSAGetLastError(); -#else - int err = errno; -#endif - return connect_failed_because_or_peer_2(err); -} - -// Processes the socket until it's either ready, died, or would block -void socket_update(Socket *sock) -{ - sock->events = 0; - + // Each case of this switch encodes a state transition. + // If the evaluated case requires a given I/O event to + // continue, the loop will exit so that the caller can + // wait for that event. If the case can continue to a + // different case, the again flag is set, which causes + // a different case to be evaluated. bool again; do { - again = false; - - switch (sock->state) { + switch (socket->state) { case SOCKET_STATE_PENDING: - { - // In this state we need to pop an address from the pending connect - // data and try connect to it. This state is reached when a socket - // is initialized using one of the socket_connect functions or by - // failing to connect before the established state is reached. - - // If this isn't the first connection attempt we may have old - // descriptors that need freeing before trying again. { -#ifdef HTTPS_ENABLED - if (sock->ssl) { - SSL_free(sock->ssl); - sock->ssl = NULL; + // This point may be reached because + // 1. The socket was just created by a connect + // operation. + // 2. Connecting to a host failed and now we + // need to try the next one. + // If (2) is true, we have some resources + // to clean up. + + if (socket->sock != NATIVE_SOCKET_INVALID) { + // This is not the first attempt + + CLOSE_NATIVE_SOCKET(socket->sock); + + socket->next_addr++; + if (socket->next_addr == socket->num_addr) { + assert(0); // TODO + } + } + AddressAndPort addr = socket->addrs[socket->next_addr]; + + int family = (addr.is_ipv4 ? AF_INET : AF_INET6); + NATIVE_SOCKET sock = socket(family, SOCK_STREAM, 0); + if (sock == NATIVE_SOCKET_INVALID) { + assert(0); // TODO + } + + if (set_socket_blocking(sock, false) < 0) { + assert(0); // TODO + } + + int ret; + if (addr.is_ipv4) { + struct sockaddr_in buf; + buf.sin_family = AF_INET; + buf.sin_port = htons(addr.port); + memset(&buf.sin_addr, &addr.ipv4, sizeof(IPv4)); + ret = connect(sock, (struct sockaddr*) &connect_buf, + sizeof(connect_buf)); + } else { + struct sockaddr_in6 buf; + buf.sin6_family = AF_INET6; + buf.sin6_port = htons(addr.port); + memset(&buf.sin6_addr, &addr.ipv6, sizeof(IPv6)); + ret = connect(sock, (struct sockaddr*) &connect_buf, + sizeof(connect_buf)); + } + + if (ret == 0) { + // Connect resolved immediately + socket->sock = sock; + socket->state = SOCKET_STATE_CONNECTED; + socket->events = 0; + again = true; + } else if (connect_pending()) { + // Connect is pending, which is expected + socket->sock = sock; + socket->state = SOCKET_STATE_CONNECTING; + socket->events = POLLOUT; + } else if (connect_failed_because_of_peer()) { + // Conenct failed due to the peer host + // We should try a different address. + socket->sock = sock; + socket->state = SOCKET_STATE_PENDING; + socket->events = 0; + again = true; + } else { + // An error occurred that we can't recover from + socket->sock = sock; + socket->state = SOCKET_STATE_DIED; + socket->events = 0; + again = true; } -#endif - if (sock->raw != BAD_SOCKET) - CLOSE_SOCKET(sock->raw); } - - // Pop the next address from the pending connect data - PendingConnectAddr addr; - if (next_connect_addr(sock->pending_connect, &addr) < 0) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - uint16_t port = sock->pending_connect->port; - - // Create a kernel socket object - int family = addr.is_ipv4 ? AF_INET : AF_INET6; - RAW_SOCKET raw = socket(family, SOCK_STREAM, 0); - if (raw == BAD_SOCKET) { - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - again = true; - break; - } - - // Configure it - if (set_socket_blocking(raw, false) < 0) { - CLOSE_SOCKET(raw); - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - - // Now perform the connect - - struct sockaddr_in connect_buf_4; - struct sockaddr_in6 connect_buf_6; - struct sockaddr* connect_buf; - int connect_buf_len; - - if (addr.is_ipv4) { - - connect_buf = (struct sockaddr*) &connect_buf_4; - connect_buf_len = sizeof(connect_buf_4); - - connect_buf_4.sin_family = AF_INET; - connect_buf_4.sin_port = htons(port); - memcpy(&connect_buf_4.sin_addr, &addr.ipv4, sizeof(HTTP_IPv4)); - - } else { - - connect_buf = (struct sockaddr*) &connect_buf_6; - connect_buf_len = sizeof(connect_buf_6); - - connect_buf_6.sin6_family = AF_INET6; - connect_buf_6.sin6_port = htons(port); - memcpy(&connect_buf_6.sin6_addr, &addr.ipv6, sizeof(HTTP_IPv6)); - } - - int ret = connect(raw, connect_buf, connect_buf_len); - - // We divide the connect() results in four categories: - // - // 1) The connect resolved immediately. I'm not sure how this can happen, - // but we may as well handle it. This allows us to skip a step. - // - // 2) The connect operation is pending. This is what we expect most of the time. - // - // 3) The connect operation failed because the target address wasn't good - // for some reason. It make sense to try connecting to a different address - // - // 4) The connect operation failed for unknown reasons. There isn't much we - // can do at this point. - - if (ret == 0) { - // Connected immediately - sock->raw = raw; - sock->state = SOCKET_STATE_CONNECTED; - sock->events = 0; - again = true; - break; - } - - if (connect_pending()) { // TODO: I'm pretty sure all the error numbers need to be changed for windows - // Connection pending - sock->raw = raw; - sock->state = SOCKET_STATE_CONNECTING; - sock->events = POLLOUT; - break; - } - - // Connect failed - - // If remote peer not working, try next address - if (connect_failed_because_or_peer()) { - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - again = true; - } else { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - } - } - break; + break; case SOCKET_STATE_CONNECTING: - { - // We reach this point when a connect() operation on the - // socket started and then the descriptor was marked as - // ready for output. This means the operation is complete. + { + // This point is reached when a connect() + // operation completes. - int err = 0; - socklen_t len = sizeof(err); - - if (getsockopt(sock->raw, SOL_SOCKET, SO_ERROR, (void*) &err, &len) < 0 || err != 0) { - - // If remote peer not working, try next address - if (connect_failed_because_or_peer_2(err)) { - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - again = true; - break; + int err = 0; + socklen_t len = sizeof(err); + if (getsockopt(socket->sock, SOL_SOCKET, SO_ERROR, (void*) &err, &len) < 0) { + assert(0); // TODO } - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; + if (err == 0) { + // Connection succeded + socket->state = SOCKET_STATE_CONNECTED; + socket->events = 0; + again = true; + } else if (connect_failed_because_of_peer_2(err)) { + // Try the next address + socket->state = SOCKET_STATE_PENDING; + socket->events = 0; + again = true; + } else { + socket->state = SOCKET_STATE_DIED; + socket->events = 0; + } } - - // Connect succeeded - sock->state = SOCKET_STATE_CONNECTED; - sock->events = 0; - again = true; - } - break; + break; case SOCKET_STATE_CONNECTED: - { - if (!socket_secure(sock)) { + { + // We managed to connect to the peer. + // We can free the target array if it + // was allocated dynamically. + if (socket->num_addr > 1) + free(socket->addrs); - pending_connect_free(sock->pending_connect); - sock->pending_connect = NULL; - - sock->events = 0; - sock->state = SOCKET_STATE_ESTABLISHED_READY; - - } else { + if (!is_secure(socket)) { + socket->events = 0; + socket->state = SOCKET_STATE_ESTABLISHED_READY; + } else { #ifdef HTTPS_ENABLED - // Start SSL handshake - - if (sock->ssl == NULL) { - sock->ssl = SSL_new(sock->sec->ctx); - if (sock->ssl == NULL) { - ERR_print_errors_fp(stderr); // TODO: remove - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - - if (SSL_set_fd(sock->ssl, sock->raw) != 1) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - - char *hostname = NULL; - if (sock->pending_connect->hostname[0]) - hostname = sock->pending_connect->hostname; - - if (hostname) - SSL_set_tlsext_host_name(sock->ssl, hostname); - } - - int ret = SSL_connect(sock->ssl); - if (ret == 1) { - // Handshake done - - pending_connect_free(sock->pending_connect); - sock->pending_connect = NULL; - - sock->state = SOCKET_STATE_ESTABLISHED_READY; - sock->events = 0; - break; - } - - int err = SSL_get_error(sock->ssl, ret); - if (err == SSL_ERROR_WANT_READ) { - sock->events = POLLIN; - break; - } - - if (err == SSL_ERROR_WANT_WRITE) { - sock->events = POLLOUT; - break; - } - - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - again = true; -#else - assert(0); + assert(0); // TODO #endif + } } - } - break; - - case SOCKET_STATE_ACCEPTED: - { - if (!socket_secure(sock)) { - sock->state = SOCKET_STATE_ESTABLISHED_READY; - sock->events = 0; - } else { -#ifdef HTTPS_ENABLED - // Start server-side SSL handshake - if (!sock->ssl) { - - sock->ssl = SSL_new(sock->sec->ctx); - if (sock->ssl == NULL) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - - if (SSL_set_fd(sock->ssl, sock->raw) != 1) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - } - - int ret = SSL_accept(sock->ssl); - if (ret == 1) { - // Handshake done - sock->state = SOCKET_STATE_ESTABLISHED_READY; - sock->events = 0; - break; - } - - int err = SSL_get_error(sock->ssl, ret); - if (err == SSL_ERROR_WANT_READ) { - sock->events = POLLIN; - break; - } - - if (err == SSL_ERROR_WANT_WRITE) { - sock->events = POLLOUT; - break; - } - - // Server socket error - close the connection - sock->state = SOCKET_STATE_DIED; - sock->events = 0; -#else - assert(0); -#endif - } - } - break; + break; case SOCKET_STATE_ESTABLISHED_WAIT: - { - sock->state = SOCKET_STATE_ESTABLISHED_READY; - sock->events = 0; - } - break; + socket->state = SOCKET_STATE_ESTABLISHED_READY; + socket->events = 0; + break; case SOCKET_STATE_SHUTDOWN: - { - if (!socket_secure(sock)) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - } else { + { + if (!is_secure(socket)) { + socket->state = SOCKET_STATE_DIED; + socket->events = 0; + } else { #ifdef HTTPS_ENABLED - int ret = SSL_shutdown(sock->ssl); - if (ret == 1) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - - int err = SSL_get_error(sock->ssl, ret); - if (err == SSL_ERROR_WANT_READ) { - sock->events = POLLIN; - break; - } - - if (err == SSL_ERROR_WANT_WRITE) { - sock->events = POLLOUT; - break; - } - - sock->state = SOCKET_STATE_DIED; - sock->events = 0; -#else - assert(0); + assert(0); // TODO #endif + } } - } - break; + break; default: // Do nothing break; } - } while (again); } -static bool would_block(void) +int socket_manager_wakeup(SocketManager *sm) { -#ifdef _WIN32 - int err = WSAGetLastError(); - return err == WSAEWOULDBLOCK; -#else - return errno == EAGAIN || errno == EWOULDBLOCK; -#endif + if (mutex_lock(&sm->mutex) < 0) + return -1; + + // TODO + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return 0; } -static bool interrupted(void) +static int socket_manager_register_events_nolock( + SocketManager *sm, struct pollfd *polled, int max_polled) { -#ifdef _WIN32 - return false; -#else - return errno == EINTR; -#endif -} + // The poll array must be able to hold descriptors + // for a socket manager at full capacity. Note that + // other than having a number of connection sockets, + // the manager also needs 2 for the listeners and + // one for the wakeup self-pipe. + if (max_polled < sm->max_used+3) + return -1; + int num_polled = 0; -int socket_read(Socket *sock, char *dst, int max) -{ - // If not ESTABLISHED, set state to DIED and return - if (sock->state != SOCKET_STATE_ESTABLISHED_READY) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return 0; + polled[num_polled].fd = sm->wait_sock; + polled[num_polled].events = 0; + polled[num_polled].revents = 0; + num_polled++; + + // If the manager isn't at full capacity, monitor + // the listener sockets for incoming connections. + if (sm->num_used < sm->max_used) { + + if (sm->plain_sock != NATIVE_SOCKET_INVALID) { + polled[num_polled].fd = sm->plain_sock; + polled[num_polled].events = POLLIN; + polled[num_polled].revents = 0; + num_polled++; + } + + if (sm->secure_sock != NATIVE_SOCKET_INVALID) { + polled[num_polled].fd = sm->secure_sock; + polled[num_polled].events = POLLIN; + polled[num_polled].revents = 0; + num_polled++; + } } - if (!socket_secure(sock)) { - int ret = recv(sock->raw, dst, max, 0); - if (ret == 0) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; + // Iterate over each socket and register those that + // are waiting for I/O. If at least one socket that + // is ready to be processed exists, return an empty + // event registration list so that those entries can + // be processed immediately. + for (int i = 0, j = 0; j < sm->num_used; i++) { + Socket *s = &sm->sockets[i]; + if (s->state = SOCKET_STATE_FREE) + continue; + j++; + + if (s->state == SOCKET_STATE_DIED || s->state == SOCKET_STATE_ESTABLISHED_READY) + return 0; + + if (s->events) { + polled[num_polled].fd = s->sock; + polled[num_polled].events = s->events; + polled[num_polled].revents = 0; + num_polled++; + } + } + + return num_polled; +} + +int socket_manager_register_events(SocketManager *sm, + struct pollfd *polled, int max_polled) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + + int ret = socket_manager_register_events_nolock( + sm, polled, max_polled); + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return ret; +} + +static SocketHandle +socket_to_handle(SocketManager *sm, Socket *s) +{ + assert(0); // TODO +} + +static Socket *handle_to_socket(SocketManager *sm, SocketHandle handle) +{ + assert(0); // TODO +} + +static int socket_manager_translate_events_nolock( + SocketManager *sm, SocketEvent *events, int max_events, + struct pollfd *polled, int num_polled) +{ + int num_events = 0; + for (int i = 0; i < num_polled; i++) { + + if (polled[i].fd == sm->plain_sock || + polled[i].fd == sm->secure_sock) { + + // We only listen for input events from the listener + // if the socket pool isn't fool. This ensures that + // at least one socket struct is available. Note that + // it's still possible that we were at capacity MAX-1 + // and then got events from both the TCP and TCP/TLS + // listeners, causing one to be left witout a struct. + // This means we still need to check for full capacity. + // Fortunately, poll() is level-triggered, which means + // we'll handle this at the next iteration. + if (sm->num_used == sm->max_used) + continue; + + // Determine whether the event came from + // the encrypted listener or not. + bool secure = (polled[i].fd == sm->secure_sock); + + Socket *s = sm->sockets; + while (s->type != SOCKET_FREE) { + s++; + assert(s - sm->sockets < + sm->max_used); + } + + NATIVE_SOCKET sock = accept(polled[i].fd, NULL, NULL); + if (sock == NATIVE_SOCKET_INVALID) + continue; + + if (set_socket_blocking(sock, false) < 0) { + CLOSE_NATIVE_SOCKET(sock); + continue; + } + + s->state = SOCKET_STATE_ACCEPTED; + s->sock = sock; + s->events = 0; + s->user = NULL; + + socket_update(s); + if (s->state == SOCKET_STATE_DIED) { + CLOSE_NATIVE_SOCKET(sock); + s->state = SOCKET_STATE_FREE; + continue; + } + + pool->num_used++; + + } else if (polled[i].fd == sm->wait_sock) { + + // TODO: consume + } else { - if (ret < 0) { - if (would_block()) { - sock->state = SOCKET_STATE_ESTABLISHED_WAIT; - sock->events = POLLIN; - } else { - if (!interrupted()) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; + if (polled[i].revents) + socket_update(s); + } + } + + for (int i = 0, j = 0; j < sm->num_used; i++) { + Socket *s = &sm->sockets[i]; + if (s->state == SOCKET_FREE) + continue; + j++; + + if (num_events == max_events) + break; + + if (s->state == SOCKET_DIED) { + + events[num_events++] = (SocketEvent) { + SOCKET_EVENT_DISCONNECT, + SOCKET_HANDE_INVALID, + s->user + }; + + // Free resources associated to socket + s->state = SOCKET_FREE; + if (s->sock != NATIVE_SOCKET_INVALID) + CLOSE_NATIVE_SOCKET(s->sock); + if (s->sock == SOCKET_STATE_PENDING || + s->sock == SOCKET_STATE_CONNECTING) { + if (s->num_addr > 1) + free(s->addrs); + } + s->num_used--; + + } else if (s->state == SOCKET_STATE_ESTABLISHED_READY) { + events[num_events++] = (SocketEvent) { + SOCKET_EVENT_READY, + socket_to_handle(sm, s), + s->user + }; + } + } + + return num_events; +} + +int socket_manager_translate_events(SocketManager *sm, + SocketEvent *events, int max_events, struct pollfd *polled, + int num_polled) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + + socket_manager_translate_events_nolock( + sm, events, max_events, polled, num_polled); + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return 0; +} + +static int resolve_connect_targets(ConnectTarget *targets, + int num_targets, AddressAndPort *resolved, int max_resolved) +{ + int num_resolved = 0; + for (int i = 0; i < num_targets; i++) { + switch (targets[i].type) { + case CONNECT_TARGET_NAME: + { + char portstr[16]; + int len = snprintf(portstr, sizeof(portstr), "%u", port); + if (len < 0 || len >= (int) sizeof(portstr)) + return -1; + + struct addrinfo hints = {0}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + struct addrinfo *res = NULL; + int ret = getaddrinfo(pending_connect->hostname, portstr, &hints, &res); + if (ret != 0) + return -1; + + for (struct addrinfo *rp = res; rp; rp = rp->ai_next) { + if (rp->ai_family == AF_INET) { + IPv4 ipv4 = *(IPv4*) &((struct sockaddr_in*)rp->ai_addr)->sin_addr; + if (num_resolved < max_resolved) { + resolved[num_resolved].is_ipv4 = true; + resolved[num_resolved].ipv4 = ipv4; + resolved[num_resolved].port = targets[i].port; + num_resolved++; + } + } else if (rp->ai_family == AF_INET6) { + IPv6 ipv6 = *(IPv6*) &((struct sockaddr_in6*)rp->ai_addr)->sin6_addr; + if (num_resolved < max_resolved) { + resolved[num_resolved].is_ipv4 = false; + resolved[num_resolved].ipv4 = ipv6; + resolved[num_resolved].port = targets[i].port; + num_resolved++; + } } } - ret = 0; + + freeaddrinfo(res); } - } - return ret; - } else { -#ifdef HTTPS_ENABLED - int ret = SSL_read(sock->ssl, dst, max); - if (ret <= 0) { - int err = SSL_get_error(sock->ssl, ret); - if (err == SSL_ERROR_WANT_READ) { - sock->state = SOCKET_STATE_ESTABLISHED_WAIT; - sock->events = POLLIN; - } else if (err == SSL_ERROR_WANT_WRITE) { - sock->state = SOCKET_STATE_ESTABLISHED_WAIT; - sock->events = POLLOUT; - } else { - fprintf(stderr, "OpenSSL error in socket_read: "); - ERR_print_errors_fp(stderr); - sock->state = SOCKET_STATE_DIED; - sock->events = 0; + break; + case CONNECT_TARGET_IPV4: + if (num_resolved < max_resolved) { + resolved[num_resolved].is_ipv4 = true; + resolved[num_resolved].ipv4 = targets[i].ipv4; + resolved[num_resolved].port = targets[i].port; + num_resolved++; } - ret = 0; + break; + case CONNECT_TARGET_IPV6: + if (num_resolved < max_resolved) { + resolved[num_resolved].is_ipv4 = false; + resolved[num_resolved].ipv6 = targets[i].ipv6; + resolved[num_resolved].port = targets[i].port; + num_resolved++; + } + break; } - return ret; -#else - assert(0); - return -1; -#endif } + return num_resolved; } -int socket_write(Socket *sock, char *src, int len) +int socket_connect(SocketManager *sm, int num_targets, + ConnectTarget *targets, bool secure, void *user) { - // If not ESTABLISHED, set state to DIED and return - if (sock->state != SOCKET_STATE_ESTABLISHED_READY) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; + if (sm->num_used == sm->max_used) + return -1; + + AddressAndPort resolved[MAX_CONNECT_TARGETS]; + int num_resolved = resolve_connect_targets( + targets, num_targets, resolved, MAX_CONNECT_TARGETS); + + if (num_resolved <= 0) + return -1; + + Socket *s = sm->sockets; + while (s->type != SOCKET_FREE) { + s++; + assert(s - sm->sockets < + sm->max_used); + } + + if (num_resolved == 1) { + s->num_addr = 1; + s->next_addr = 0; + s->addr = resolved[0]; + } else { + s->num_addr = num_resolved; + s->next_addr = 0; + s->addrs = malloc(num_resolved * sizeof(AddressAndPort)); + if (s->addrs == NULL) + return -1; + for (int i = 0; i < num_resolved; i++) + s->addrs[i] = resolved[i]; + } + + s->type = SOCKET_STATE_PENDING; + s->sock = NATIVE_SOCKET_INVALID; + s->user = user; + s->num_used++; + return 0; +} + +static int socket_recv_nolock(SocketManager *sm, SocketHandle handle, + char *dst, int max) +{ + Socket *s = handle_to_socket(sm, handle); + if (s == NULL) + return 0; + + if (s->state != SOCKET_STATE_ESTABLISHED_READY) { + s->state = SOCKET_STATE_DIED; + s->events = 0; return 0; } - if (!socket_secure(sock)) { - int ret = send(sock->raw, src, len, 0); + if (!is_secure(s)) { + int ret = recv(s->sock, dst, max, 0); + if (ret == 0) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + } else if (ret < 0) { + if (would_block()) { + s->state = SOCKET_STATE_ESTABLISHED_WAIT; + s->events = POLLIN; + } else if (!interrupted()) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + } + ret = 0; + } + return 0; + } else { +#ifdef HTTPS_ENABLED + int ret = SSL_read(s->ssl, dst, max); + if (ret <= 0) { + int err = SSL_get_error(s->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + s->state = SOCKET_STATE_ESTABLISHED_WAIT; + s->events = POLLIN; + } else if (err == SSL_ERROR_WANT_WRITE) { + s->state = SOCKET_STATE_ESTABLISHED_WAIT; + s->events = POLLOUT; + } else { + s->state = SOCKET_STATE_DIED; + s->events = 0; + } + ret = 0; + } + return ret; +#endif + } +} + +int socket_recv(SocketManager *sm, SocketHandle handle, + char *dst, int max) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + + int ret = socket_recv_nolock(sm, handle, dst, max); + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return ret; +} + +static int socket_send_nolock(SocketManager *sm, SocketHandle handle, + char *src, int len) +{ + Socket *s = handle_to_socket(sm, handle); + if (s == NULL) + return 0; + + if (s->state != SOCKET_STATE_ESTABLISHED_READY) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + return 0; + } + + if (!socket_secure(s)) { + int ret = send(s->sock, src, len, 0); if (ret < 0) { if (would_block()) { - sock->state = SOCKET_STATE_ESTABLISHED_WAIT; - sock->events = POLLOUT; - } else { - if (!interrupted()) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - } + s->state = SOCKET_STATE_ESTABLISHED_WAIT; + s->events = POLLOUT; + } else if (!interrupted()) { + s->state = SOCKET_DIED; + s->events = 0; } ret = 0; } return ret; } else { #ifdef HTTPS_ENABLED - int ret = SSL_write(sock->ssl, src, len); + int ret = SSL_write(s->ssl, src, len); if (ret <= 0) { - int err = SSL_get_error(sock->ssl, ret); + int err = SSL_get_error(s->ssl, ret); if (err == SSL_ERROR_WANT_READ) { - sock->state = SOCKET_STATE_ESTABLISHED_WAIT; - sock->events = POLLIN; + s->state = SOCKET_STATE_ESTABLISHED_WAIT; + s->events = POLLIN; } else if (err == SSL_ERROR_WANT_WRITE) { - sock->state = SOCKET_STATE_ESTABLISHED_WAIT; - sock->events = POLLOUT; + s->state = SOCKET_STATE_ESTABLISHED_WAIT; + s->events = POLLOUT; } else { - fprintf(stderr, "OpenSSL error in socket_write: "); - ERR_print_errors_fp(stderr); - sock->state = SOCKET_STATE_DIED; - sock->events = 0; + s->state = SOCKET_STATE_DIED; + s->events = 0; } ret = 0; } return ret; -#else - assert(0); #endif } } -bool socket_secure(Socket *sock) +int socket_send(SocketManager *sm, SocketHandle handle, + char *src, int len) { + if (mutex_lock(&sm->mutex) < 0) + return -1; + + int ret = socket_send_nolock(sm, handle, src, len); + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return ret; +} + +void socket_close(SocketManager *sm, SocketHandle handle) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + + // TODO: maybe we don't want to always set to SHUTDOWN. What if the socket is DIED for instance? + s->state = SOCKET_STATE_SHUTDOWN; + s->events = 0; + socket_update(s); + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return 0; +} + +int socket_is_secure(SocketManager *sm, SocketHandle handle) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + #ifdef HTTPS_ENABLED - return sock->sec != NULL; + Socket *s = handle_to_socket(sm, handle); + + int ret; + if (s == NULL) + ret = -1; + else { + ret = (s->ssl != NULL); + } #else - (void) sock; - return false; -#endif -} - -void socket_free(Socket *sock) -{ - if (sock->pending_connect != NULL) - pending_connect_free(sock->pending_connect); - - if (sock->raw != BAD_SOCKET) - CLOSE_SOCKET(sock->raw); - -#ifdef HTTPS_ENABLED - if (sock->ssl) - SSL_free(sock->ssl); + int ret = 0; #endif - sock->state = SOCKET_STATE_FREE; + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return ret; } -void socket_set_user_data(Socket *sock, void *user_data) +int socket_set_user(SocketManager *sm, SocketHandle handle) { - sock->user_data = user_data; -} + if (mutex_lock(&sm->mutex) < 0) + return -1; -void *socket_get_user_data(Socket *sock) -{ - return sock->user_data; -} \ No newline at end of file + Socket *s = handle_to_socket(sm, handle); + if (s == NULL) + ret = -1; + else + s->user = user; + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return ret; +} diff --git a/src/socket.h b/src/socket.h index 96a4c99..f068ce3 100644 --- a/src/socket.h +++ b/src/socket.h @@ -1,75 +1,302 @@ -#ifndef SOCKET_INCLUDED -#define SOCKET_INCLUDED +// This file (and its relative .c file) implements an asynchronous TCP/TLS +// server and client abstraction. +// +// It introduces the concept of a "socket manager", which is a pool of +// connection sockets and a listener socket. The listener is managed +// internally, which means the manager automatically accepts sockets +// from it and adds them to the pool. +// +// If the listener is configured using the function: +// +// socket_manager_listen_tcp +// +// the resulting connections will not use TLS. If instead the listener +// is configured using: +// +// socket_manager_listen_tls +// +// the listener will use TLS. Note that both functions can be used on +// the same manager to allow both plaintext and encrypted connections. +// Users may enable zero listeners, in which case only outgoing +// connections are allowed (more on this later). +// +// Once the manager is set up, one can wait for events by following +// this pattern: +// +// struct pollfd polled[...]; +// int num_polled = socket_manager_register_events(sm, polled, max_polled); +// poll(polled, num_polled, -1); +// +// #define MAX_EVENTS ... +// SocketEvent events[MAX_EVENTS]; +// int num_events = socket_manager_translate_events(sm, events, MAX_EVENTS, polled, num_polled); +// for (int i = 0; i < num_events; i++) { +// ... Here call socket_recv, socket_send, socket_close, ... +// } +// +// Note that from the user's perspective, there is no difference +// between connections that use plain TCP and those that use TCP/TLS. +// +// Users can also establish outgoing connections by calling the +// function: +// +// socket_connect +// +// Which allows the creation of a connection towards an host given +// its domain, IPv4, IPv6, or an array of them. This can be done both +// for TCP and TCP/TLS connection. Note that users that only intend +// to establish outgoing connection may omit the configuration of +// listeners entirely. -// This module implements the socket state machine to encapsulate -// the complexity of non-blocking TCP and TLS sockets. -// -// A socket is represented by the "Socket" structure, which may -// be in a number of states. As far as an user of the interface -// is concerned, the socket may be DIED, READY, or in an internal -// state that requires waiting for an event. Therefore, if the -// socket is not DIED or READY, the user needs to wait for the -// events specified in the [socket->events] field, then call the -// socket_update function. At some point the socket will become -// either READY or DIED. -// -// When the socket reaches the DIED state, the user must call -// socket_free. -// -// If the socket is ESTABLISHED_READY, the user may call socket_read, -// socket_write, or socket_close on it. - -#ifndef HTTP_AMALGAMATION -#include "sec.h" -#include "parse.h" -#include "socket_raw.h" +#ifdef _WIN32 +#define NATIVE_SOCKET SOCKET +#define NATIVE_SOCKET_INVALID SOCKET_ERROR +#define CLOSE_NATIVE_SOCKET closesocket +#else +#define NATIVE_SOCKET int +#define NATIVE_SOCKET_INVALID -1 +#define CLOSE_NATIVE_SOCKET close #endif -typedef struct PendingConnect PendingConnect; +typedef uint32_t SocketHandle; +#define SOCKET_HANDLE_INVALID ((SocketHandle) 0) + +typedef struct { + char *ptr; + int len; +} String; + +typedef uint16_t Port; + +typedef struct { + uint32_t data; +} IPv4; + +typedef struct { + uint16_t data[8]; +} IPv6; -// These should only be relevant to socket.c typedef enum { + SOCKET_EVENT_READY, + SOCKET_EVENT_DISCONNECT, +} SocketEventType; + +typedef struct { + SocketEventType type; + SocketHandle handle; + void* user; +} SocketEvent; + +// Internal use only +typedef enum { + + // The Socket struct is unused SOCKET_STATE_FREE, - SOCKET_STATE_DIED, - SOCKET_STATE_ESTABLISHED_WAIT, - SOCKET_STATE_ESTABLISHED_READY, + + // The state associated to a socket created + // by a connect operation that hasn't been + // processed yet. SOCKET_STATE_PENDING, - SOCKET_STATE_ACCEPTED, - SOCKET_STATE_CONNECTED, + + // A connect() operation was started but is + // still pending. SOCKET_STATE_CONNECTING, + + // The connection was estabished, but a TLS + // handshake may need to be performed. + SOCKET_STATE_CONNECTED, + + // The connection was esablished, but the user + // wants to perform a read or write operation that + // would block. + SOCKET_STATE_ESTABLISHED_WAIT, + + // The connection was established and it's possible + // to perform read or write operations on it without + // blocking. + SOCKET_STATE_ESTABLISHED_READY, + + // The socket was marked to be closed. SOCKET_STATE_SHUTDOWN, + + // The current socket is was closed. The only + // valid thing to do here is free its resources. + SOCKET_STATE_DIED, } SocketState; +// Internal use only +typedef struct { + union { + IPv4 ipv4; + IPv6 ipv6; + }; + bool is_ipv4; + Port port; +} AddressAndPort; + +// Internal use only typedef struct { SocketState state; - RAW_SOCKET raw; + // OS-specific socket type + NATIVE_SOCKET sock; + + // Native socket events that need to be monitored int events; - void *user_data; - PendingConnect *pending_connect; - -#ifdef HTTPS_ENABLED - SSL *ssl; -#endif - - SecureContext *sec; + // User-provided context pointer + void *user; + // A single connect operation may involve + // trying to establish a connection towards + // one of a set of addresses. + int num_addr; + int next_addr; + union { + AddressAndPort addr; // When num_addr=1 + AddressAndPort *addrs; // Dynamically allocated when num_addr>1 + }; } Socket; -void socket_connect(Socket *sock, SecureContext *sec, HTTP_String hostname, uint16_t port, void *user_data); -void socket_connect_ipv4(Socket *sock, SecureContext *sec, HTTP_IPv4 addr, uint16_t port, void *user_data); -void socket_connect_ipv6(Socket *sock, SecureContext *sec, HTTP_IPv6 addr, uint16_t port, void *user_data); -void socket_accept(Socket *sock, SecureContext *sec, RAW_SOCKET raw); -void socket_update(Socket *sock); -void socket_close(Socket *sock); -bool socket_ready(Socket *sock); -bool socket_died(Socket *sock); -int socket_read(Socket *sock, char *dst, int max); -int socket_write(Socket *sock, char *src, int len); -void socket_free(Socket *sock); -bool socket_secure(Socket *sock); -void socket_set_user_data(Socket *sock, void *user_data); -void* socket_get_user_data(Socket *sock); +// Glorified array of sockets. This structure +// is private to the .c file associated to this +// header. +typedef struct { -#endif // SOCKET_INCLUDED \ No newline at end of file + // This guards access to the main thread using + // the manager from other threads calling the + // wakeup function. + Mutex mutex; + + // TCP listener sockets. The first is intended + // for plaintext, while the second is for TLS. + // The socket manager will accept and add new + // sockets to the pool automatically. Note that + // either may be unset. If both are unset, users + // can only create outgoing connections. + NATIVE_SOCKET plain_sock; + NATIVE_SOCKET secure_sock; + + // Handles for the self-pipe trick necessary for + // other threads to wake up sockets blocked on + // poll(). + NATIVE_SOCKET wait_sock; + NATIVE_SOCKET signal_sock; + + // TLS contexts. One is used for outgoing connections + // (the client context) and one for incoming + // connections (server). If the secure_sock is + // set, the server context is initialized. If at + // least one connect was performed using TLS + // (and the flag is set), the client context is + // initialized. + bool at_least_one_secure_connect; + ClientSecureContext client_secure_context; + ServerSecureContext server_secure_context; + + // Array of sockets. Structs with state FREE + // are unused. + int num_used; + int max_used; + Socket *sockets; + +} SocketManager; + +// Instanciate a socket manager. Returns 0 on +// success and -1 on error. +int socket_manager_init(SocketManager *sm, Socket *socks, + int num_socks); + +// Deinitialize a socket manager +void socket_manager_free(SocketManager *sm); + +// Configure the socket manager to listen on +// the specified interface for TCP connections. +// Incoming connections will be automatically +// added to the internal pool. This function +// can only be used once per manager. +// Returns 0 on success, -1 on error. +int socket_manager_listen_tcp(SocketManager *sm, + String addr, Port port); + +// Same as the previous function, but incoming +// connections will be interpreted as TLS. You +// can only call this function once per manager, +// but you can call this and the plaintext variant +// on the same manager to accept both plaintext +// and secure connections. +// Returns 0 on success, -1 on error. +int socket_manager_listen_tls(SocketManager *sm, + String addr, Port port, String cert_file_name, + String key_file_name); + +// If the socket manager was configures to accept +// TLS connections, this adds additional certificates +// the client can use to verify the server's +// authenticity. +// Returns 0 on success, -1 on error. +int socket_manager_add_certificate(SocketManager *sm, + String domain, String cert_file, String key_file); + +// When a thread is blocked on a poll() call for +// descriptors associated to this socket manager, +// other threads can call this function to wake +// up that blocked thread. +// Returns 0 on success, -1 on error. +int socket_manager_wakeup(SocketManager *sm); + +// Writes to the polled array the list of sockets +// that the socket manager is monitoring and which +// events associated to that socket. +// Returns the number of items written to the polled +// array. +int socket_manager_register_events(SocketManager *sm, + struct pollfd *polled, int max_polled); + +// After poll() is called on the previously registered +// pollfd array and the revents fields are set, this +// function processes those events to produce higher-level +// socket events. Returns the number of socket events +// written to the output array, or -1 on error. +int socket_manager_translate_events(SocketManager *sm, + SocketEvent *events, int max_events, struct pollfd *polled, + int num_polled); + +typedef enum { + CONNECT_TARGET_NAME, + CONNECT_TARGET_IPV4, + CONNECT_TARGET_IPV6, +} ConnectTargetType; + +typedef struct { + ConnectTargetType type; + Port port; + union { + IPv4 ipv4; + IPv6 ipv6; + String name; + }; +} ConnectTarget; + +// Connect to one of the given targets. The socket +// manager will try to connecting to addresses until +// one succedes. If secure=true, the socket uses TLS. +// Returns 0 on success, -1 on error. +int socket_connect(SocketManager *sm, int num_targets, + ConnectTarget *targets, bool secure, void *user); + +int socket_recv(SocketManager *sm, SocketHandle handle, + char *dst, int max); + +int socket_send(SocketManager *sm, SocketHandle handle, + char *src, int len); + +void socket_close(SocketManager *sm, SocketHandle handle); + +// Returns -1 on error, 0 if the socket was accepted +// from the plaintext listener, or 1 if it was accepted +// by the secure listener. +int socket_is_secure(SocketManager *sm, SocketHandle handle); + +// Set the user pointer of a socket +void socket_set_user(SocketManager *sm, SocketHandle handle); diff --git a/src/socket_pool.c b/src/socket_pool.c deleted file mode 100644 index d61ded8..0000000 --- a/src/socket_pool.c +++ /dev/null @@ -1,357 +0,0 @@ -#include -#include - -#ifdef __linux__ -#include -#include -#endif - -#ifndef HTTP_AMALGAMATION -#include "socket_pool.h" -#endif - -#define SOCKET_HARD_LIMIT (1<<10) -#define MAX_CERTS 10 - -struct SocketPool { - - SecureContext sec; - - RAW_SOCKET listen_sock; - RAW_SOCKET secure_sock; - - int num_socks; - int max_socks; - Socket socks[]; -}; - -int socket_pool_global_init(void) -{ - int ret = socket_raw_global_init(); - if (ret < 0) - return -1; - - secure_context_global_init(); - return 0; -} - -void socket_pool_global_free(void) -{ - secure_context_global_free(); - socket_raw_global_free(); -} - -SocketPool *socket_pool_init(HTTP_String addr, - uint16_t port, uint16_t secure_port, int max_socks, - bool reuse_addr, int backlog, HTTP_String cert_file, - HTTP_String key_file) -{ - if (max_socks > SOCKET_HARD_LIMIT) - return NULL; - - SocketPool *pool = malloc(sizeof(SocketPool) + max_socks * sizeof(Socket)); - if (pool == NULL) - return NULL; - - pool->num_socks = 0; - pool->max_socks = max_socks; - - for (int i = 0; i < pool->max_socks; i++) - pool->socks[i].state = SOCKET_STATE_FREE; - - if (port == 0) - pool->listen_sock = BAD_SOCKET; - else { - pool->listen_sock = listen_socket(addr, port, reuse_addr, backlog); - if (pool->listen_sock == BAD_SOCKET) { - free(pool); - return NULL; - } - } - - if (secure_port == 0) - pool->secure_sock = BAD_SOCKET; - else { -#ifndef HTTPS_ENABLED - (void) cert_file; - (void) key_file; - if (pool->listen_sock != BAD_SOCKET) - CLOSE_SOCKET(pool->listen_sock); - free(pool); - return NULL; -#else - if (secure_context_init_as_server(&pool->sec, cert_file, key_file) < 0) { - if (pool->listen_sock != BAD_SOCKET) - CLOSE_SOCKET(pool->listen_sock); - free(pool); - return NULL; - } - - pool->secure_sock = listen_socket(addr, secure_port, reuse_addr, backlog); - if (pool->secure_sock == BAD_SOCKET) { - if (pool->listen_sock != BAD_SOCKET) CLOSE_SOCKET(pool->listen_sock); - free(pool); - return NULL; - } -#endif - } - -#ifdef HTTPS_ENABLED - if (port == 0 && secure_port == 0) { - if (secure_context_init_as_client(&pool->sec) < 0) { - if (pool->listen_sock != BAD_SOCKET) CLOSE_SOCKET(pool->listen_sock); - if (pool->secure_sock != BAD_SOCKET) CLOSE_SOCKET(pool->secure_sock); - free(pool); - return NULL; - } - } -#endif - - for (int i = 0; i < max_socks; i++) - pool->socks[i].state = SOCKET_STATE_FREE; - - return pool; -} - -void socket_pool_free(SocketPool *pool) -{ - for (int i = 0, j = 0; j < pool->num_socks; i++) { - - Socket *sock = &pool->socks[i]; - - if (sock->state == SOCKET_STATE_FREE) - continue; - j++; - - socket_free(sock); - } - - secure_context_free(&pool->sec); - - if (pool->secure_sock != BAD_SOCKET) CLOSE_SOCKET(pool->secure_sock); - if (pool->listen_sock != BAD_SOCKET) CLOSE_SOCKET(pool->listen_sock); -} - -int socket_pool_add_cert(SocketPool *pool, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) -{ - return secure_context_add_cert(&pool->sec, domain, cert_file, key_file); -} - -void socket_pool_set_user_data(SocketPool *pool, SocketHandle handle, void *user_data) -{ - Socket *sock = &pool->socks[handle]; - socket_set_user_data(sock, user_data); -} - -void socket_pool_close(SocketPool *pool, SocketHandle handle) -{ - Socket *sock = &pool->socks[handle]; - socket_close(sock); -} - -static Socket *find_free_socket(SocketPool *pool) -{ - if (pool->num_socks == pool->max_socks) - return NULL; - - int i = 0; - while (pool->socks[i].state != SOCKET_STATE_FREE) - i++; - - return &pool->socks[i]; -} - -int socket_pool_connect(SocketPool *pool, bool secure, - HTTP_String addr, uint16_t port, void *user_data) -{ - Socket *sock = find_free_socket(pool); - if (sock == NULL) - return -1; - - socket_connect(sock, secure ? &pool->sec : NULL, addr, port, user_data); - - if (socket_died(sock)) { - socket_free(sock); - return -1; - } - - pool->num_socks++; - return 0; -} - -int socket_pool_connect_ipv4(SocketPool *pool, bool secure, - HTTP_IPv4 addr, uint16_t port, void *user_data) -{ - Socket *sock = find_free_socket(pool); - if (sock == NULL) - return -1; - - socket_connect_ipv4(sock, secure ? &pool->sec : NULL, addr, port, user_data); - - if (socket_died(sock)) { - socket_free(sock); - return -1; - } - - pool->num_socks++; - return 0; -} - -int socket_pool_connect_ipv6(SocketPool *pool, bool secure, - HTTP_IPv6 addr, uint16_t port, void *user_data) -{ - Socket *sock = find_free_socket(pool); - if (sock == NULL) - return -1; - - socket_connect_ipv6(sock, secure ? &pool->sec : NULL, addr, port, user_data); - - if (socket_died(sock)) { - socket_free(sock); - return -1; - } - - pool->num_socks++; - return 0; -} - -#include // TODO: remove - -SocketEvent socket_pool_wait(SocketPool *pool) -{ - for (;;) { - - // First, iterate over all sockets to find one that - // died or is ready. - - for (int i = 0, j = 0; j < pool->num_socks; i++) { - - Socket *sock = &pool->socks[i]; - - if (sock->state == SOCKET_STATE_FREE) - continue; - j++; - - if (socket_died(sock)) { - void *user_data = socket_get_user_data(sock); - socket_free(sock); - pool->num_socks--; - return (SocketEvent) { SOCKET_EVENT_DIED, -1, user_data }; - } - - if (socket_ready(sock)) - return (SocketEvent) { SOCKET_EVENT_READY, i, socket_get_user_data(sock) }; - - assert(sock->events); - } - - // If we reached this point, we either have no sockets - // or all sockets need to wait for some event. Waiting - // when no sockets are available is only allowed when - // the pool is in server mode. - - int indices[SOCKET_HARD_LIMIT+2]; - struct pollfd polled[SOCKET_HARD_LIMIT+2]; - int num_polled = 0; - - if (pool->num_socks < pool->max_socks) { - - if (pool->listen_sock != BAD_SOCKET) { - indices[num_polled] = -1; - polled[num_polled].fd = pool->listen_sock; - polled[num_polled].events = POLLIN; - polled[num_polled].revents = 0; - num_polled++; - } - - if (pool->secure_sock != BAD_SOCKET) { - indices[num_polled] = -1; - polled[num_polled].fd = pool->secure_sock; - polled[num_polled].events = POLLIN; - polled[num_polled].revents = 0; - num_polled++; - } - } - - for (int i = 0, j = 0; j < pool->num_socks; i++) { - - Socket *sock = &pool->socks[i]; - - if (sock->state == SOCKET_STATE_FREE) - continue; - j++; - - indices[num_polled] = i; - polled[num_polled].fd = sock->raw; - polled[num_polled].events = sock->events; - polled[num_polled].revents = 0; - num_polled++; - } - - if (num_polled == 0) - return (SocketEvent) { SOCKET_EVENT_ERROR, -1, NULL }; - - int ret = POLL(polled, num_polled, -1); - if (ret < 0) { - - if (errno == EINTR) - return (SocketEvent) { SOCKET_EVENT_SIGNAL, -1, NULL }; - - return (SocketEvent) { SOCKET_EVENT_ERROR, -1, NULL }; - } - - for (int i = 0; i < num_polled; i++) { - - Socket *sock; - - if (polled[i].fd == pool->listen_sock || polled[i].fd == pool->secure_sock) { - - bool secure = false; - if (polled[i].fd == pool->secure_sock) - secure = true; - - Socket *sock = find_free_socket(pool); - if (sock == NULL) - continue; - - RAW_SOCKET raw = accept(polled[i].fd, NULL, NULL); - if (raw == BAD_SOCKET) - continue; - - socket_accept(sock, secure ? &pool->sec : NULL, raw); - - if (socket_died(sock)) { - socket_free(sock); - continue; - } - - pool->num_socks++; - - } else { - int j = indices[i]; - sock = &pool->socks[j]; - - if (polled[i].revents) - socket_update(sock); - } - } - } - - // This branch is unreachable - return (SocketEvent) { SOCKET_EVENT_ERROR, -1, NULL }; -} - -int socket_pool_read(SocketPool *pool, SocketHandle handle, char *dst, int len) -{ - return socket_read(&pool->socks[handle], dst, len); -} - -int socket_pool_write(SocketPool *pool, SocketHandle handle, char *src, int len) -{ - return socket_write(&pool->socks[handle], src, len); -} - -bool socket_pool_secure(SocketPool *pool, SocketHandle handle) -{ - return socket_secure(&pool->socks[handle]); -} \ No newline at end of file diff --git a/src/socket_pool.h b/src/socket_pool.h deleted file mode 100644 index a365aa1..0000000 --- a/src/socket_pool.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef SOCKET_POOL_INCLUDED -#define SOCKET_POOL_INCLUDED - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#include "socket.h" -#include "socket_raw.h" -#endif - -typedef struct SocketPool SocketPool; - -typedef int SocketHandle; - -typedef enum { - SOCKET_EVENT_DIED, - SOCKET_EVENT_READY, - SOCKET_EVENT_ERROR, - SOCKET_EVENT_SIGNAL, -} SocketEventType; - -typedef struct { - SocketEventType type; - SocketHandle handle; - void *user_data; -} SocketEvent; - -int socket_pool_global_init(void); -void socket_pool_global_free(void); - -SocketPool *socket_pool_init(HTTP_String addr, - uint16_t port, uint16_t secure_port, int max_socks, - bool reuse_addr, int backlog, HTTP_String cert_file, - HTTP_String key_file); - -void socket_pool_free(SocketPool *pool); - -int socket_pool_add_cert(SocketPool *pool, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file); - -SocketEvent socket_pool_wait(SocketPool *pool); - -void socket_pool_set_user_data(SocketPool *pool, SocketHandle handle, void *user_data); - -void socket_pool_close(SocketPool *pool, SocketHandle handle); - -int socket_pool_connect(SocketPool *pool, bool secure, - HTTP_String addr, uint16_t port, void *user_data); - -int socket_pool_connect_ipv4(SocketPool *pool, bool secure, - HTTP_IPv4 addr, uint16_t port, void *user_data); - -int socket_pool_connect_ipv6(SocketPool *pool, bool secure, - HTTP_IPv6 addr, uint16_t port, void *user_data); - -int socket_pool_read(SocketPool *pool, SocketHandle handle, char *dst, int len); - -int socket_pool_write(SocketPool *pool, SocketHandle handle, char *src, int len); - -bool socket_pool_secure(SocketPool *pool, SocketHandle handle); - -#endif // SOCKET_POOL_INCLUDED \ No newline at end of file diff --git a/src/socket_raw.c b/src/socket_raw.c deleted file mode 100644 index cbb57eb..0000000 --- a/src/socket_raw.c +++ /dev/null @@ -1,106 +0,0 @@ -#include - -#ifdef _WIN32 -#include -#endif - -#ifdef __linux__ -#include -#include -#include -#endif - -#ifndef HTTP_AMALGAMATION -#include "socket_raw.h" -#endif - -int socket_raw_global_init(void) -{ -#ifdef _WIN32 - WSADATA wsaData; - int result = WSAStartup(MAKEWORD(2, 2), &wsaData); - if (result != 0) - return 1; -#endif - return 0; -} - -void socket_raw_global_free(void) -{ -#ifdef _WIN32 - WSACleanup(); -#endif -} - -int set_socket_blocking(RAW_SOCKET sock, bool value) -{ -#ifdef _WIN32 - u_long mode = !value; - if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR) - return -1; -#endif - -#ifdef __linux__ - int flags = fcntl(sock, F_GETFL, 0); - if (flags < 0) - return -1; - if (value) flags &= ~O_NONBLOCK; - else flags |= O_NONBLOCK; - if (fcntl(sock, F_SETFL, flags) < 0) - return -1; -#endif - - return 0; -} - -RAW_SOCKET listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int backlog) -{ - RAW_SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock == BAD_SOCKET) - return BAD_SOCKET; - - if (set_socket_blocking(sock, false) < 0) { - CLOSE_SOCKET(sock); - return BAD_SOCKET; - } - - if (reuse_addr) { - int one = 1; - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &one, sizeof(one)); - } - - struct in_addr addr_buf; - if (addr.len == 0) - addr_buf.s_addr = htonl(INADDR_ANY); - else { - - char copy[100]; - if (addr.len >= (int) sizeof(copy)) { - CLOSE_SOCKET(sock); - return BAD_SOCKET; - } - memcpy(copy, addr.ptr, addr.len); - copy[addr.len] = '\0'; - - if (inet_pton(AF_INET, copy, &addr_buf) < 0) { - CLOSE_SOCKET(sock); - return BAD_SOCKET; - } - } - - struct sockaddr_in bind_buf; - bind_buf.sin_family = AF_INET; - bind_buf.sin_addr = addr_buf; - bind_buf.sin_port = htons(port); - if (bind(sock, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) { // TODO: how does bind fail on windows? - CLOSE_SOCKET(sock); - return BAD_SOCKET; - } - - if (listen(sock, backlog) < 0) { // TODO: how does listen fail on windows? - CLOSE_SOCKET(sock); - return BAD_SOCKET; - } - - return sock; -} diff --git a/src/socket_raw.h b/src/socket_raw.h deleted file mode 100644 index 56dd758..0000000 --- a/src/socket_raw.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef SOCKET_RAW_INCLUDED -#define SOCKET_RAW_INCLUDED - -#include -#include - -#ifdef _WIN32 -#include -#define RAW_SOCKET SOCKET -#define BAD_SOCKET INVALID_SOCKET -#define POLL WSAPoll -#define CLOSE_SOCKET closesocket -#endif - -#ifdef __linux__ -#include -#include -#define RAW_SOCKET int -#define BAD_SOCKET -1 -#define POLL poll -#define CLOSE_SOCKET close -#endif - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#endif - -int socket_raw_global_init(void); -void socket_raw_global_free(void); - -int set_socket_blocking(RAW_SOCKET sock, bool value); - -RAW_SOCKET listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int backlog); - -#endif // SOCKET_RAW_INCLUDED \ No newline at end of file diff --git a/src/thread.c b/src/thread.c new file mode 100644 index 0000000..d841cfd --- /dev/null +++ b/src/thread.c @@ -0,0 +1,48 @@ + +int mutex_init(Mutex *mutex) +{ +#ifdef _WIN32 + InitializeCriticalSection(mutex); // TODO: mock? + return 0; +#else + if (pthread_mutex_init(mutex, NULL)) // TODO: mock + return -1; + return 0; +#endif +} + +int mutex_free(Mutex *mutex) +{ +#ifdef _WIN32 + DeleteCriticalSection(mutex); // TODO: mock? + return 0; +#else + if (pthread_mutex_destroy(mutex)) // TODO: mock + return -1; + return 0; +#endif +} + +int mutex_lock(Mutex *mutex) +{ +#ifdef _WIN32 + EnterCriticalSection(mutex); // TODO: mock? + return 0; +#else + if (pthread_mutex_lock(mutex)) // TODO: mock + return -1; + return 0; +#endif +} + +int mutex_unlock(Mutex *mutex) +{ +#ifdef _WIN32 + LeaveCriticalSection(mutex); // TODO: mock? + return 0; +#else + if (pthread_mutex_unlock(mutex)) // TODO: mock + return -1; + return 0; +#endif +} diff --git a/src/thread.h b/src/thread.h new file mode 100644 index 0000000..7141287 --- /dev/null +++ b/src/thread.h @@ -0,0 +1,9 @@ + +typedef struct { + // TODO +} Mutex; + +int mutex_init(Mutex *mutex); +int mutex_free(Mutex *mutex); +int mutex_lock(Mutex *mutex); +int mutex_unlock(Mutex *mutex); diff --git a/tests/test.c b/tests/test.c deleted file mode 100644 index 76f04df..0000000 --- a/tests/test.c +++ /dev/null @@ -1,81 +0,0 @@ -#include -#include -#include "test.h" - -static const char *statestr(HTTP_EngineState state) -{ - switch (state) { - case HTTP_ENGINE_STATE_NONE : return "NONE"; - case HTTP_ENGINE_STATE_CLIENT_PREP_URL : return "CLIENT_PREP_URL"; - case HTTP_ENGINE_STATE_CLIENT_PREP_HEADER : return "CLIENT_PREP_HEADER"; - case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF: return "CLIENT_PREP_BODY_BUF"; - case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK: return "CLIENT_PREP_BODY_ACK"; - case HTTP_ENGINE_STATE_CLIENT_PREP_ERROR : return "CLIENT_PREP_ERROR"; - case HTTP_ENGINE_STATE_CLIENT_SEND_BUF : return "CLIENT_SEND_BUF"; - case HTTP_ENGINE_STATE_CLIENT_SEND_ACK : return "CLIENT_SEND_ACK"; - case HTTP_ENGINE_STATE_CLIENT_RECV_BUF : return "CLIENT_RECV_BUF"; - case HTTP_ENGINE_STATE_CLIENT_RECV_ACK : return "CLIENT_RECV_ACK"; - case HTTP_ENGINE_STATE_CLIENT_READY : return "CLIENT_READY"; - case HTTP_ENGINE_STATE_CLIENT_CLOSED : return "CLIENT_CLOSED"; - case HTTP_ENGINE_STATE_SERVER_RECV_BUF : return "SERVER_RECV_BUF"; - case HTTP_ENGINE_STATE_SERVER_RECV_ACK : return "SERVER_RECV_ACK"; - case HTTP_ENGINE_STATE_SERVER_PREP_STATUS : return "SERVER_PREP_STATUS"; - case HTTP_ENGINE_STATE_SERVER_PREP_HEADER : return "SERVER_PREP_HEADER"; - case HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF: return "SERVER_PREP_BODY_BUF"; - case HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK: return "SERVER_PREP_BODY_ACK"; - case HTTP_ENGINE_STATE_SERVER_PREP_ERROR : return "SERVER_PREP_ERROR"; - case HTTP_ENGINE_STATE_SERVER_SEND_BUF : return "SERVER_SEND_BUF"; - case HTTP_ENGINE_STATE_SERVER_SEND_ACK : return "SERVER_SEND_ACK"; - case HTTP_ENGINE_STATE_SERVER_CLOSED : return "SERVER_CLOSED"; - } - return "???"; -} - -void testeq_engstate(HTTP_EngineState l, HTTP_EngineState r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line) -{ - if (l != r) { - printf("Test failed at %s:%d\n", file, line); - printf(" TEST_EQ(%.*s, %.*s) -> TEST_EQ(%s, %s)\n", HTTP_UNPACK(uneval_l), HTTP_UNPACK(uneval_r), statestr(l), statestr(r)); - abort(); - } -} - -void testeq_int(int l, int r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line) -{ - if (l != r) { - printf("Test failed at %s:%d\n", file, line); - printf(" TEST_EQ(%.*s, %.*s) -> TEST_EQ(%d, %d)\n", HTTP_UNPACK(uneval_l), HTTP_UNPACK(uneval_r), l, r); - abort(); - } -} - -void testeq_str(HTTP_String l, HTTP_String r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line) -{ - if (!http_streq(l, r)) { - printf("Test failed at %s:%d\n", file, line); - printf(" TEST_EQ(\"%.*s\", \"%.*s\") -> TEST_EQ(%.*s, %.*s)\n", HTTP_UNPACK(uneval_l), HTTP_UNPACK(uneval_r), HTTP_UNPACK(l), HTTP_UNPACK(r)); - abort(); - } -} - -void test_branch_coverage_parse(void); -void test_branch_coverage_engine(void); -void test_fuzz_engine(void); - -int main(void) -{ - char *tests[] = { - }; - - for (int i = 0; i < sizeof(tests)/sizeof(tests[0]); i++) { - HTTP_Request req; - int ret = http_parse_request(tests[i], strlen(tests[i]), &req); - TEST(ret == 0); - } - - //test_branch_coverage_parse(); - test_branch_coverage_engine(); - //test_fuzz_engine(); - - return 0; -} diff --git a/tests/test.h b/tests/test.h deleted file mode 100644 index c21432a..0000000 --- a/tests/test.h +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include "../tinyhttp.h" - -#define S HTTP_STR - -#define COUNT(X) (int) (sizeof(X)/sizeof((X)[0])) -#define TEST(X) {if (!(X)) { fprintf(stderr, "Failed test at %s:%d\n", __FILE__, __LINE__); __builtin_trap(); }} - -void testeq_engstate(HTTP_EngineState l, HTTP_EngineState r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line); -void testeq_int(int l, int r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line); -void testeq_str(HTTP_String l, HTTP_String r, HTTP_String uneval_l, HTTP_String uneval_r, const char *file, int line); -#define TEST_EQ(X, Y) _Generic((X), HTTP_String: testeq_str, int: testeq_int, HTTP_EngineState: testeq_engstate)((X), (Y), S(#X), S(#Y), __FILE__, __LINE__) diff --git a/tests/test_branch_coverage_engine.c b/tests/test_branch_coverage_engine.c deleted file mode 100644 index 203ee42..0000000 --- a/tests/test_branch_coverage_engine.c +++ /dev/null @@ -1,138 +0,0 @@ -#include -#include -#include "test.h" - -static void *memfunc(HTTP_MemoryFuncTag tag, - void *ptr, int len, void *data) -{ - (void) data; - switch (tag) { - - case HTTP_MEMFUNC_MALLOC: - return malloc(len); - - case HTTP_MEMFUNC_FREE: - free(ptr); - return NULL; - } - return NULL; -} - -static void test_engine_server(void) -{ - HTTP_Engine eng; - - { - int client = 0; - memset(&eng, 0, sizeof(eng)); - http_engine_init(&eng, client, memfunc, NULL); - TEST_EQ((int) http_engine_state(&eng), HTTP_ENGINE_STATE_SERVER_RECV_BUF); - http_engine_free(&eng); - } - - { - int client = 1; - memset(&eng, 0, sizeof(eng)); - http_engine_init(&eng, client, memfunc, NULL); - TEST_EQ((int) http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_URL); - http_engine_free(&eng); - } -} - -static int read_from_client_engine(HTTP_Engine *eng, char *dst, int cap) -{ - TEST_EQ(http_engine_state(eng), HTTP_ENGINE_STATE_CLIENT_SEND_BUF); - - int num = 0; - do { - int len; - char *src = http_engine_sendbuf(eng, &len); - TEST_EQ(http_engine_state(eng), HTTP_ENGINE_STATE_CLIENT_SEND_ACK); - - TEST(src != NULL); - TEST(cap - num >= len); - memcpy(dst + num, src, len); - - num += len; - http_engine_sendack(eng, len); - } while (http_engine_state(eng) == HTTP_ENGINE_STATEBIT_SEND_BUF); - - return num; -} - -static void send_into_client_engine(HTTP_Engine *eng, char *src, int len) -{ - int num = 0; - while (num < len) { - TEST_EQ(http_engine_state(eng), HTTP_ENGINE_STATE_CLIENT_RECV_BUF); - - int cap; - char *dst = http_engine_recvbuf(eng, &cap); - TEST_EQ(http_engine_state(eng), HTTP_ENGINE_STATE_CLIENT_RECV_ACK); - TEST(dst != NULL); - TEST(cap > 0); - - int cpy = len - num; - if (cpy > cap) cpy = cap; - - memcpy(dst, src + num, cpy); - - num += cpy; - http_engine_recvack(eng, cpy); - } -} - -static void test_engine_client_basic_flow(void) -{ - int client = 1; - HTTP_Engine eng; - http_engine_init(&eng, client, memfunc, NULL); - TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_URL); - - http_engine_url(&eng, HTTP_METHOD_GET, "http://some.url.com/some/endpoint", 1); - TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_HEADER); - - http_engine_header(&eng, "headerA: valueA", -1); - TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_HEADER); - - http_engine_body(&eng, "Hello, world!", -1); - TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF); - - http_engine_done(&eng); - TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_SEND_BUF); - - { - char sendbuf[1<<10]; - int sendnum; - sendnum = read_from_client_engine(&eng, sendbuf, (int) sizeof(sendbuf)); - - char expect[] = - "GET /some/endpoint HTTP/1.1\r\n" - "Host: some.url.com\r\n" - "headerA: valueA\r\n" - "Connection: Keep-Alive\r\n" - "Content-Length: 13 \r\n" - "\r\n" - "Hello, world!"; - - TEST_EQ(sendnum, strlen(expect)); - TEST_EQ(memcmp(expect, sendbuf, strlen(expect)), 0); - } - - { - char response[] = - "HTTP/1.1 200 OK\r\n" - "\r\n"; - send_into_client_engine(&eng, response, strlen(response)); - } - - TEST_EQ(http_engine_state(&eng), HTTP_ENGINE_STATE_CLIENT_READY); - - http_engine_free(&eng); -} - -void test_branch_coverage_engine(void) -{ - test_engine_server(); - test_engine_client_basic_flow(); -} diff --git a/tests/test_branch_coverage_parse.c b/tests/test_branch_coverage_parse.c deleted file mode 100644 index 7017889..0000000 --- a/tests/test_branch_coverage_parse.c +++ /dev/null @@ -1,485 +0,0 @@ -#include -#include -#include "test.h" - -static void test_branch_coverage_parse_request(void) -{ - struct { - int line; - int ret; - char *str; - } error_reqs[] = { - { __LINE__, -1, "G * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "G@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "GE * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "GE@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, 18, "GET * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "GET@ * HTTP/1.1\r\n\r\n" }, - - { __LINE__, -1, "P * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "P@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "PO * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "PO@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "POS * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "POS@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, 19, "POST * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "POST@ * HTTP/1.1\r\n\r\n" }, - - { __LINE__, -1, "PU * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "PU@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, 18, "PUT * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "PUT@ * HTTP/1.1\r\n\r\n" }, - - { __LINE__, -1, "H * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "H@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "HE * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "HE@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "HEA * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "HEA@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, 19, "HEAD * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "HEAD@ * HTTP/1.1\r\n\r\n" }, - - { __LINE__, -1, "D * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "D@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "DE * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "DE@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "DEL * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "DEL@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "DELE * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "DELE@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "DELET * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "DELET@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, 21, "DELETE * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "DELETE@ * HTTP/1.1\r\n\r\n" }, - - { __LINE__, -1, "C * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "C@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "CO * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "CO@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "CON * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "CON@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "CONN * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "CONN@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "CONNE * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "CONNE@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "CONNEC * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "CONNEC@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, 22, "CONNECT * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "CONNECT@ * HTTP/1.1\r\n\r\n" }, - - { __LINE__, -1, "O * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "O@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "PO * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "PO@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "OPT * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "OPT@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "OPTI * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "OPTI@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "OPTIO * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "OPTIO@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "OPTION * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "OPTION@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, 22, "OPTIONS * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "OPTIONS@ * HTTP/1.1\r\n\r\n" }, - - { __LINE__, -1, "T * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "T@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "TR * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "TR@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "TRA * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "TRA@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "TRAC * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "TRAC@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, 20, "TRACE * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "TRACE@ * HTTP/1.1\r\n\r\n" }, - - { __LINE__, -1, "P * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "P@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "PA * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "PA@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "PAT * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "PAT@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "PATC * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "PATC@ * HTTP/1.1\r\n\r\n" }, - { __LINE__, 20, "PATCH * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "PATCH@ * HTTP/1.1\r\n\r\n" }, - - { __LINE__, -1, "GET *@\r\n\r\n" }, - { __LINE__, -1, "GET * @\r\n\r\n" }, - { __LINE__, -1, "GET * H\r\n\r\n" }, - { __LINE__, -1, "GET * H@\r\n\r\n" }, - { __LINE__, -1, "GET * HT\r\n\r\n" }, - { __LINE__, -1, "GET * HT@\r\n\r\n" }, - { __LINE__, -1, "GET * HTT\r\n\r\n" }, - { __LINE__, -1, "GET * HTT@\r\n\r\n" }, - { __LINE__, -1, "GET * HTTP\r\n\r\n" }, - { __LINE__, -1, "GET * HTTP@\r\n\r\n" }, - { __LINE__, -1, "GET * HTTP/\r\n\r\n" }, - { __LINE__, -1, "GET * HTTP/@\r\n\r\n" }, - { __LINE__, 16, "GET * HTTP/1\r\n\r\n" }, - { __LINE__, -1, "GET * HTTP/1@\r\n\r\n" }, - { __LINE__, -1, "GET * HTTP/1.\r\n\r\n" }, - { __LINE__, -1, "GET * HTTP/1.@\r\n\r\n" }, - { __LINE__, 18, "GET * HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "GET * HTTP/1.1@\r\n\r\n" }, - { __LINE__, -1, "GET * HTTP/1.1@\nname:\r\n\r\n" }, - { __LINE__, -1, "GET * HTTP/1.1\r@name:\r\n\r\n" }, - { __LINE__, -1, "GET * HTTP/1.1\r\n@\r\n\r\n" }, - { __LINE__, -1, "GET * HTTP/1.1\r\nn@\r\n\r\n" }, - { __LINE__, -1, "GET * HTTP/1.1\r\nname\r\n\r\n" }, - { __LINE__, 25, "GET * HTTP/1.1\r\nname:\r\n\r\n" }, - { __LINE__, -1, "GET * HTTP/1.1\r\nname:\x1B\r\n\r\n" }, - { __LINE__, 30, "GET * HTTP/1.1\r\nname:value\r\n\r\n" }, - { __LINE__, -1, "GET * HTTP/1.1\r\nname :value\r\n\r\n" }, - { __LINE__, -1, "GET * HTTP/1.1\r\nname:val\rue\r\n\r\n" }, - - { __LINE__, 0, NULL }, - }; - - for (int i = 0; error_reqs[i].str; i++) { - HTTP_Request req; - int ret = http_parse_request(error_reqs[i].str, strlen(error_reqs[i].str), &req); - if (ret != error_reqs[i].ret) { - fprintf(stderr, "Failed test at %s:%d (ret=%d, expected=%d)\n", __FILE__, error_reqs[i].line, ret, error_reqs[i].ret); - } - } - - { - char str[] = "GET * HTTP/1.1\r\n\r\n"; - HTTP_Request req; - int ret = http_parse_request(str, sizeof(str)-1, &req); - TEST(ret == 18); - TEST(req.method == HTTP_METHOD_GET); - } - - { - char str[] = "POST * HTTP/1.1\r\n\r\n"; - HTTP_Request req; - int ret = http_parse_request(str, sizeof(str)-1, &req); - TEST(ret == 19); - TEST(req.method == HTTP_METHOD_POST); - } - - { - char str[] = "PUT * HTTP/1.1\r\n\r\n"; - HTTP_Request req; - int ret = http_parse_request(str, sizeof(str)-1, &req); - TEST(ret == 18); - TEST(req.method == HTTP_METHOD_PUT); - } - - { - char str[] = "HEAD * HTTP/1.1\r\n\r\n"; - HTTP_Request req; - int ret = http_parse_request(str, sizeof(str)-1, &req); - TEST(ret == 19); - TEST(req.method == HTTP_METHOD_HEAD); - } - - { - char str[] = "DELETE * HTTP/1.1\r\n\r\n"; - HTTP_Request req; - int ret = http_parse_request(str, sizeof(str)-1, &req); - TEST(ret == 21); - TEST(req.method == HTTP_METHOD_DELETE); - } - - { - char str[] = "CONNECT * HTTP/1.1\r\n\r\n"; - HTTP_Request req; - int ret = http_parse_request(str, sizeof(str)-1, &req); - TEST(ret == 22); - TEST(req.method == HTTP_METHOD_CONNECT); - } - - { - char str[] = "OPTIONS * HTTP/1.1\r\n\r\n"; - HTTP_Request req; - int ret = http_parse_request(str, sizeof(str)-1, &req); - TEST(ret == 22); - TEST(req.method == HTTP_METHOD_OPTIONS); - } - - { - char str[] = "TRACE * HTTP/1.1\r\n\r\n"; - HTTP_Request req; - int ret = http_parse_request(str, sizeof(str)-1, &req); - TEST(ret == 20); - TEST(req.method == HTTP_METHOD_TRACE); - } - - { - char str[] = "PATCH * HTTP/1.1\r\n\r\n"; - HTTP_Request req; - int ret = http_parse_request(str, sizeof(str)-1, &req); - TEST(ret == 20); - TEST(req.method == HTTP_METHOD_PATCH); - } -} - -static void test_branch_coverage_parse_response(void) -{ - struct { - int line; - int ret; - char *str; - } error_ress[] = { - { __LINE__, -1, "@\r\n\r\n" }, - { __LINE__, -1, "H\r\n\r\n" }, - { __LINE__, -1, "H@\r\n\r\n" }, - { __LINE__, -1, "HT\r\n\r\n" }, - { __LINE__, -1, "HT@\r\n\r\n" }, - { __LINE__, -1, "HTT\r\n\r\n" }, - { __LINE__, -1, "HTT@\r\n\r\n" }, - { __LINE__, -1, "HTTP\r\n\r\n" }, - { __LINE__, -1, "HTTP@\r\n\r\n" }, - { __LINE__, -1, "HTTP/\r\n\r\n" }, - { __LINE__, -1, "HTTP/@\r\n\r\n" }, - { __LINE__, -1, "HTTP/1\r\n\r\n" }, - { __LINE__, -1, "HTTP/1@\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.@\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1@\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1 \r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1 @\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1 4\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1 4@\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1 40\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1 40@\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1 404\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1 404@\r\n\r\n" }, - { __LINE__, 17, "HTTP/1.1 404 \r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1 404 \x1B\r\n\r\n" }, - { __LINE__, 26, "HTTP/1.1 404 Not Found\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1 404 Not Found\x1B\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1 404 Not Found\x1B\nname:\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1 404 Not Found\r\x1Bname:\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1 404 Not Found\r\n@\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1 404 Not Found\r\nn@\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1 404 Not Found\r\nname\r\n\r\n" }, - { __LINE__, 33, "HTTP/1.1 404 Not Found\r\nname:\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1 404 Not Found\r\nname:\x1B\r\n\r\n" }, - { __LINE__, 38, "HTTP/1.1 404 Not Found\r\nname:value\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1 404 Not Found\r\nname :value\r\n\r\n" }, - { __LINE__, -1, "HTTP/1.1 404 Not Found\r\nname:val\rue\r\n\r\n" }, - - { __LINE__, 0, NULL }, - }; - - for (int i = 0; error_ress[i].str; i++) { - HTTP_Response res; - int ret = http_parse_response(error_ress[i].str, strlen(error_ress[i].str), &res); - if (ret != error_ress[i].ret) { - fprintf(stderr, "Failed test at %s:%d (ret=%d, expected=%d)\n", __FILE__, error_ress[i].line, ret, error_ress[i].ret); - } - } -} - -#ifdef _WIN32 -#include -#include -#else -#include -#endif - -typedef struct { - char *buf; - int cap; - int num; -} Buffer; - -static void appendc(Buffer *b, char c) -{ - if (b->num < b->cap) - b->buf[b->num] = c; - b->num++; -} - -static void appends(Buffer *b, HTTP_String s) -{ - if (b->num < b->cap) { - int cpy = s.len; - if (cpy > b->cap - b->num) - cpy = b->cap - b->num; - for (int i = 0; i < cpy; i++) - b->buf[b->num + i] = s.ptr[i]; - } - b->num += s.len; -} - -static void appendi(Buffer *buf, unsigned int num) -{ - char tmp[10]; - - tmp[0] = num / 1000000000; num %= 1000000000; - tmp[1] = num / 100000000; num %= 100000000; - tmp[2] = num / 10000000; num %= 10000000; - tmp[3] = num / 1000000; num %= 1000000; - tmp[4] = num / 100000; num %= 100000; - tmp[5] = num / 10000; num %= 10000; - tmp[6] = num / 1000; num %= 1000; - tmp[7] = num / 100; num %= 100; - tmp[8] = num / 10; num %= 10; - tmp[9] = num; - - int leading_zeros = 0; - while (leading_zeros < 9 && tmp[leading_zeros] == 0) - leading_zeros++; - - for (int i = leading_zeros; i < 10; i++) - tmp[i] += '0'; - - appends(buf, (HTTP_String) { - tmp + leading_zeros, - 10 - leading_zeros - }); -} - -static int build_url( - HTTP_String scheme, - HTTP_String userinfo, - HTTP_String host, - HTTP_HostMode mode, - int port, - HTTP_String path, - HTTP_String query, - HTTP_String fragment, - char* dst, - int cap) -{ - Buffer buf = {dst, cap, 0}; - appends(&buf, scheme); - appendc(&buf, ':'); - if (mode != HTTP_HOST_MODE_VOID) { - appendc(&buf, '/'); - appendc(&buf, '/'); - if (userinfo.len) { - appends(&buf, userinfo); - appendc(&buf, '@'); - } - if (mode == HTTP_HOST_MODE_IPV6) - appendc(&buf, '['); - appends(&buf, host); - if (mode == HTTP_HOST_MODE_IPV6) - appendc(&buf, ']'); - if (port == -2) - appendc(&buf, ':'); - else if (port != -1) { - appendc(&buf, ':'); - appendi(&buf, port); - } - } - appends(&buf, path); - appends(&buf, query); - appends(&buf, fragment); - return buf.num; -} - -static void test_url(HTTP_String scheme, HTTP_String userinfo, - HTTP_String host, HTTP_HostMode mode, int port, - HTTP_String path, HTTP_String query, HTTP_String fragment) -{ - char mem[1<<12]; - int num = build_url(scheme, userinfo, host, mode, port, path, query, fragment, mem, sizeof(mem)); - TEST(num < sizeof(mem)); - - printf("Testing %.*s\n", num, mem); - - HTTP_URL url; - int ret = http_parse_url(mem, num, &url); - - TEST_EQ(ret, num); - TEST_EQ(url.scheme, scheme); - TEST_EQ(url.authority.userinfo, userinfo); - if (port < 0) - TEST_EQ(url.authority.port, 0); - else - TEST_EQ(url.authority.port, port); - TEST_EQ((int) url.authority.host.mode, (int) mode); - if (mode == HTTP_HOST_MODE_IPV4) { - char tmp[1<<12]; - TEST(sizeof(tmp) > host.len); - memcpy(tmp, host.ptr, host.len); - tmp[host.len] = '\0'; - HTTP_IPv4 buf; - TEST_EQ(inet_pton(AF_INET, tmp, &buf), 1); - TEST_EQ((int) buf.data, (int) url.authority.host.ipv4.data); - } else if (mode == HTTP_HOST_MODE_IPV6) { - char tmp[1<<12]; - TEST(sizeof(tmp) > host.len); - memcpy(tmp, host.ptr, host.len); - tmp[host.len] = '\0'; - HTTP_IPv6 buf; - TEST_EQ(inet_pton(AF_INET6, tmp, &buf), 1); - TEST(!memcmp(&buf, &url.authority.host.ipv6, sizeof(HTTP_IPv6))); - } else if (mode == HTTP_HOST_MODE_NAME) { - TEST_EQ(host, url.authority.host.name); - } - TEST_EQ(url.path, path); - TEST_EQ(url.query, query); - TEST_EQ(url.fragment, fragment); -} - -static void test_branch_coverage_parse_url(void) -{ - HTTP_String scheme_values[] = { S("http") }; - - HTTP_String userinfo_values[] = { S(""), S("xxx:yyy") }; - - struct host_values { - HTTP_HostMode mode; - HTTP_String text; - } host_values[] = { - { HTTP_HOST_MODE_VOID, S("") }, - { HTTP_HOST_MODE_IPV4, S("1.2.3.4") }, - { HTTP_HOST_MODE_IPV6, S("::") }, - { HTTP_HOST_MODE_IPV6, S("::1") }, - { HTTP_HOST_MODE_IPV6, S("1:2:3:4:A:B:C:D") }, - { HTTP_HOST_MODE_NAME, S("example.com") }, - { HTTP_HOST_MODE_NAME, S("1.2.3.256") }, - }; - - int port_values[] = { -1, 1, 8080 }; - - HTTP_String path_values[] = { S(""), S("/"), S("/some/path.html") }; - - HTTP_String query_values[] = { S(""), S("?"), S("?param1=hello¶m2=sup") }; - - HTTP_String fragment_values[] = { S(""), S("#"), S("#section0") }; - - for (int i = 0; i < COUNT(scheme_values); i++) - for (int j = 0; j < COUNT(userinfo_values); j++) - for (int k = 0; k < COUNT(host_values); k++) - for (int g = 0; g < COUNT(port_values); g++) - for (int t = 0; t < COUNT(path_values); t++) - for (int p = 0; p < COUNT(query_values); p++) - for (int q = 0; q < COUNT(fragment_values); q++) { - - // Don't test URLs where the host isn't specified but the port or userinfo is - if (host_values[k].mode == HTTP_HOST_MODE_VOID && (port_values[g] > -1 || userinfo_values[j].len)) - continue; - - // If the authority is missing, the path must not be empty - if (host_values[k].mode == HTTP_HOST_MODE_VOID && path_values[t].len == 0) - continue; - - test_url( - scheme_values[i], - userinfo_values[j], - host_values[k].text, - host_values[k].mode, - port_values[g], - path_values[t], - query_values[p], - fragment_values[q] - ); - } -} - -void test_branch_coverage_parse(void) -{ - test_branch_coverage_parse_request(); - test_branch_coverage_parse_response(); - test_branch_coverage_parse_url(); -} diff --git a/tests/test_fuzz_engine.c b/tests/test_fuzz_engine.c deleted file mode 100644 index 36a2cdd..0000000 --- a/tests/test_fuzz_engine.c +++ /dev/null @@ -1,230 +0,0 @@ -#include -#include -#include -#include "test.h" - -static void *memfunc(HTTP_MemoryFuncTag tag, - void *ptr, int len, void *data) -{ - (void) data; - switch (tag) { - - case HTTP_MEMFUNC_MALLOC: - { - void *ptr = NULL; - - int x = rand() & 31; - if (x == 0) - ptr = malloc(len); - return ptr; - } - - case HTTP_MEMFUNC_FREE: - free(ptr); - return NULL; - } - return NULL; -} - -const char *http_statestr(int state) -{ - switch (state) { - case HTTP_ENGINE_STATE_NONE : return "NONE"; - case HTTP_ENGINE_STATE_CLIENT_PREP_URL : return "CLIENT_PREP_URL"; - case HTTP_ENGINE_STATE_CLIENT_PREP_HEADER : return "CLIENT_PREP_HEADER"; - case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF: return "CLIENT_PREP_BODY_BUF"; - case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK: return "CLIENT_PREP_BODY_ACK"; - case HTTP_ENGINE_STATE_CLIENT_PREP_ERROR : return "CLIENT_PREP_ERROR"; - case HTTP_ENGINE_STATE_CLIENT_SEND_BUF : return "CLIENT_SEND_BUF"; - case HTTP_ENGINE_STATE_CLIENT_SEND_ACK : return "CLIENT_SEND_ACK"; - case HTTP_ENGINE_STATE_CLIENT_RECV_BUF : return "CLIENT_RECV_BUF"; - case HTTP_ENGINE_STATE_CLIENT_RECV_ACK : return "CLIENT_RECV_ACK"; - case HTTP_ENGINE_STATE_CLIENT_READY : return "CLIENT_READY"; - case HTTP_ENGINE_STATE_CLIENT_CLOSED : return "CLIENT_CLOSED"; - case HTTP_ENGINE_STATE_SERVER_RECV_BUF : return "SERVER_RECV_BUF"; - case HTTP_ENGINE_STATE_SERVER_RECV_ACK : return "SERVER_RECV_ACK"; - case HTTP_ENGINE_STATE_SERVER_PREP_STATUS : return "SERVER_PREP_STATUS"; - case HTTP_ENGINE_STATE_SERVER_PREP_HEADER : return "SERVER_PREP_HEADER"; - case HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF: return "SERVER_PREP_BODY_BUF"; - case HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK: return "SERVER_PREP_BODY_ACK"; - case HTTP_ENGINE_STATE_SERVER_PREP_ERROR : return "SERVER_PREP_ERROR"; - case HTTP_ENGINE_STATE_SERVER_SEND_BUF : return "SERVER_SEND_BUF"; - case HTTP_ENGINE_STATE_SERVER_SEND_ACK : return "SERVER_SEND_ACK"; - case HTTP_ENGINE_STATE_SERVER_CLOSED : return "SERVER_CLOSED"; - } - return "???"; -} - -static sig_atomic_t stop = 0; - -static void handle_signal(int dummy) -{ - stop = 1; -} - -void test_fuzz_engine(void) -{ - signal(SIGINT, handle_signal); - - char txt1[] = ""; - char txt2[] = "GET / HTTP/1.1\r\n\r\n"; - char txt3[] = "GET / HTTP/1.1\n"; - char txt4[] = "HTTP/1.1 200 OK\r\n"; - char txt5[] = "HTTP/1.1 200 OK\r\n\r\n"; - - int next_recvack = 0; - int next_sendack = 0; - int next_bodyack = 0; - int curr_bodycap = 0; - int client = 0; - HTTP_Engine eng; - http_engine_init(&eng, client, memfunc, NULL); - while (!stop) { - //printf("%s\n", http_statestr(http_engine_state(&eng))); - switch (rand() % 21) { - - int max; - char *buf; - - case 0: - //printf("http_engine_free/http_engine_init\n"); - client = !client; - http_engine_free(&eng); - http_engine_init(&eng, client, memfunc, NULL); - next_recvack = 0; - next_sendack = 0; - next_bodyack = 0; - curr_bodycap = 0; - break; - - case 1: - //printf("http_engine_recvbuf (1)\n"); - buf = http_engine_recvbuf(&eng, &max); - memcpy(buf, txt1, strlen(txt1)); - next_recvack = strlen(txt1); - break; - - case 2: - //printf("http_engine_recvbuf (2)\n"); - buf = http_engine_recvbuf(&eng, &max); - if (buf) { - memcpy(buf, txt2, strlen(txt2)); - next_recvack = strlen(txt2); - } - break; - - case 3: - //printf("http_engine_recvbuf (3)\n"); - buf = http_engine_recvbuf(&eng, &max); - if (buf) { - memcpy(buf, txt3, strlen(txt3)); - next_recvack = strlen(txt3); - } - break; - - case 4: - //printf("http_engine_recvbuf (4)\n"); - buf = http_engine_recvbuf(&eng, &max); - if (buf) { - memcpy(buf, txt4, strlen(txt4)); - next_recvack = strlen(txt4); - } - break; - - case 5: - //printf("http_engine_recvbuf (5)\n"); - buf = http_engine_recvbuf(&eng, &max); - if (buf) { - memcpy(buf, txt5, strlen(txt5)); - next_recvack = strlen(txt5); - } - break; - - case 6: - //printf("http_engine_recvack\n"); - http_engine_recvack(&eng, next_recvack); - next_recvack = 0; - break; - - case 7: - //printf("http_engine_sendbuf\n"); - buf = http_engine_sendbuf(&eng, &max); - if (max) - next_sendack = rand() % max; - break; - - case 8: - //printf("http_engine_sendack\n"); - http_engine_sendack(&eng, next_sendack); - next_sendack = 0; - break; - - case 9: - //printf("http_engine_getreq\n"); - http_engine_getreq(&eng); - break; - - case 10: - //printf("http_engine_getres\n"); - http_engine_getres(&eng); - break; - - case 11: - //printf("http_engine_url\n"); - http_engine_url(&eng, HTTP_METHOD_GET, "", 0); - break; - - case 12: - //printf("http_engine_url\n"); - http_engine_url(&eng, HTTP_METHOD_GET, "http://127.0.0.1/hello", 0); - break; - - case 13: - //printf("http_engine_status\n"); - http_engine_status(&eng, 200); - break; - - case 14: - //printf("http_engine_header\n"); - http_engine_header(&eng, "x:y", -1); - break; - - case 15: - //printf("http_engine_bodycap\n"); - http_engine_bodycap(&eng, rand() % 1000); - break; - - case 16: - //printf("http_engine_bodybuf\n"); - buf = http_engine_bodybuf(&eng, &max); - if (buf && max) { - max = rand() % max; - memset(buf, 0xFF, max); - next_bodyack = max; - } - break; - - case 17: - //printf("http_engine_bodyack\n"); - http_engine_bodyack(&eng, next_bodyack); - next_bodyack = 0; - break; - - case 18: - //printf("http_engine_done\n"); - http_engine_done(&eng); - break; - - case 19: - //printf("http_engine_undo\n"); - http_engine_undo(&eng); - break; - - case 20: - //printf("http_engine_close\n"); - http_engine_close(&eng); - break; - } - } - http_engine_free(&eng); -} \ No newline at end of file