diff --git a/Makefile b/Makefile index 318a634..401858f 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ OFILES = $(CFILES:.c=.o) .PHONY: all clean coverage coverage-report coverage-html -all: toastyfs$(EXT) toastyfs_random_test$(EXT) example_async_api$(EXT) example_blocking_api$(EXT) libtoastyfs.a +all: toastyfs$(EXT) toastyfs_web$(EXT) toastyfs_random_test$(EXT) example_async_api$(EXT) example_blocking_api$(EXT) libtoastyfs.a coverage: toastyfs_random_test_coverage$(EXT) @@ -42,6 +42,9 @@ example_async_api$(EXT): libtoastyfs.a examples/async_api.c example_blocking_api$(EXT): libtoastyfs.a examples/blocking_api.c gcc -o $@ examples/blocking_api.c $(CFLAGS) -ltoastyfs $(LFLAGS) -Iinc -L. +toastyfs_web$(EXT): libtoastyfs.a web/main.c web/chttp.c web/chttp.h + gcc -o $@ web/main.c web/chttp.c $(CFLAGS) -ltoastyfs $(LFLAGS) -Iinc -L. + %.o: %.c $(HFILES) gcc -c -o $@ $< $(CFLAGS) -Iinc diff --git a/web/chttp.c b/web/chttp.c index 13a0424..f9ef146 100644 --- a/web/chttp.c +++ b/web/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) @@ -318,7 +66,6 @@ static bool is_printable(char c) return c >= ' ' && c <= '~'; } -#include void print_bytes(HTTP_String prefix, HTTP_String src) { if (src.len == 0) @@ -363,21 +110,6 @@ void print_bytes(HTTP_String prefix, HTTP_String src) //////////////////////////////////////////////////////////////////////////////////////// // src/parse.c //////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/parse.c" -#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 @@ -812,9 +544,10 @@ static int is_scheme_body(char c) } // userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) +// Note: percent-encoded characters (%XX) are not currently validated static int is_userinfo(char c) { - return is_unreserved(c) || is_sub_delim(c) || c == ':'; // TODO: PCT encoded + return is_unreserved(c) || is_sub_delim(c) || c == ':'; } // authority = [ userinfo "@" ] host [ ":" port ] @@ -881,7 +614,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 +677,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 +758,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 +792,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 +840,7 @@ parse_transfer_encoding(HTTP_String src, TransferEncodingOption *dst, int max) int num = 0; for (;;) { - + CONSUME_OPTIONAL_SEQUENCE(&s, is_space); TransferEncodingOption opt; @@ -1413,8 +1146,13 @@ static int parse_request(Scanner *s, HTTP_Request *req) return num_headers; req->num_headers = num_headers; + // Request methods that typically don't have a body bool body_expected = true; - if (req->method == HTTP_METHOD_GET || req->method == HTTP_METHOD_DELETE) // TODO: maybe other methods? + if (req->method == HTTP_METHOD_GET || + req->method == HTTP_METHOD_HEAD || + req->method == HTTP_METHOD_DELETE || + req->method == HTTP_METHOD_OPTIONS || + req->method == HTTP_METHOD_TRACE) body_expected = false; return parse_body(s, req->headers, req->num_headers, &req->body, body_expected); @@ -1455,10 +1193,12 @@ static int parse_response(Scanner *s, HTTP_Response *res) (s->src[s->cur-3] - '0') * 10 + (s->src[s->cur-4] - '0') * 100; + // Parse reason phrase: HTAB / SP / VCHAR / obs-text + // Note: obs-text (obsolete text, octets 0x80-0xFF) is not validated while (s->cur < s->len && ( s->src[s->cur] == '\t' || s->src[s->cur] == ' ' || - is_vchar(s->src[s->cur]))) // TODO: obs-text + is_vchar(s->src[s->cur]))) s->cur++; if (s->len - s->cur < 2 @@ -1472,7 +1212,17 @@ static int parse_response(Scanner *s, HTTP_Response *res) return num_headers; res->num_headers = num_headers; - bool body_expected = true; // TODO + // Responses with certain status codes don't have a body: + // - 1xx (Informational) + // - 204 (No Content) + // - 304 (Not Modified) + // Note: HEAD responses also don't have a body, but we can't determine + // that here without access to the request method + bool body_expected = true; + if ((res->status >= 100 && res->status < 200) || + res->status == 204 || + res->status == 304) + body_expected = false; return parse_body(s, res->headers, res->num_headers, &res->body, body_expected); } @@ -1522,7 +1272,8 @@ int http_parse_response(char *src, int len, HTTP_Response *res) HTTP_String http_get_cookie(HTTP_Request *req, HTTP_String name) { - // TODO: best-effort implementation + // Simple cookie parsing - does not handle quoted values or special characters + // See RFC 6265 for full cookie specification for (int i = 0; i < req->num_headers; i++) { @@ -1671,12 +1422,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,368 +1436,1723 @@ bool http_match_host(HTTP_Request *req, HTTP_String domain, int port) } //////////////////////////////////////////////////////////////////////////////////////// -// src/engine.c +// src/thread.c //////////////////////////////////////////////////////////////////////////////////////// -#line 1 "src/engine.c" -#include -#include -#include -#include -#include -#include // TODO: remove some of these headers -#include -#include -#include -#include +int mutex_init(Mutex *mutex) +{ +#ifdef _WIN32 + InitializeCriticalSection(mutex); + return 0; +#else + if (pthread_mutex_init(mutex, NULL)) + return -1; + return 0; +#endif +} -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#include "engine.h" +int mutex_free(Mutex *mutex) +{ +#ifdef _WIN32 + DeleteCriticalSection(mutex); + return 0; +#else + if (pthread_mutex_destroy(mutex)) + return -1; + return 0; +#endif +} + +int mutex_lock(Mutex *mutex) +{ +#ifdef _WIN32 + EnterCriticalSection(mutex); + return 0; +#else + if (pthread_mutex_lock(mutex)) + return -1; + return 0; +#endif +} + +int mutex_unlock(Mutex *mutex) +{ +#ifdef _WIN32 + LeaveCriticalSection(mutex); + return 0; +#else + if (pthread_mutex_unlock(mutex)) + return -1; + return 0; +#endif +} + +//////////////////////////////////////////////////////////////////////////////////////// +// src/secure_context.c +//////////////////////////////////////////////////////////////////////////////////////// + +int global_secure_context_init(void) +{ +#ifdef HTTPS_ENABLED + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); +#endif + return 0; +} + +int global_secure_context_free(void) +{ +#ifdef HTTPS_ENABLED + EVP_cleanup(); +#endif + return 0; +} + +int client_secure_context_init(ClientSecureContext *ctx) +{ +#ifdef HTTPS_ENABLED + SSL_CTX *p = SSL_CTX_new(TLS_client_method()); + if (!p) + return -1; + + SSL_CTX_set_min_proto_version(p, TLS1_2_VERSION); + + SSL_CTX_set_verify(p, SSL_VERIFY_PEER, NULL); + + if (SSL_CTX_set_default_verify_paths(p) != 1) { + SSL_CTX_free(p); + return -1; + } + + ctx->p = p; + return 0; +#else + (void) ctx; + return -1; +#endif +} + +void client_secure_context_free(ClientSecureContext *ctx) +{ +#ifdef HTTPS_ENABLED + SSL_CTX_free(ctx->p); +#endif +} + +#ifdef HTTPS_ENABLED +static int servername_callback(SSL *ssl, int *ad, void *arg) +{ + ServerSecureContext *ctx = arg; + + // The 'ad' parameter is used to set the alert description when returning + // SSL_TLSEXT_ERR_ALERT_FATAL. Since we only return OK or NOACK, it's unused. + (void) ad; + + const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (servername == NULL) + return SSL_TLSEXT_ERR_NOACK; + + for (int i = 0; i < ctx->num_certs; i++) { + ServerCertificate *cert = &ctx->certs[i]; + if (!strcmp(cert->domain, servername)) { + SSL_set_SSL_CTX(ssl, cert->ctx); + return SSL_TLSEXT_ERR_OK; + } + } + + return SSL_TLSEXT_ERR_NOACK; +} #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 server_secure_context_init(ServerSecureContext *ctx, + HTTP_String cert_file, HTTP_String key_file) { - return queue->memfunc(HTTP_MEMFUNC_MALLOC, NULL, len, queue->memfuncdata); +#ifdef HTTPS_ENABLED + SSL_CTX *p = SSL_CTX_new(TLS_server_method()); + if (!p) + return -1; + + SSL_CTX_set_min_proto_version(p, TLS1_2_VERSION); + + char cert_buffer[1024]; + if (cert_file.len >= (int) sizeof(cert_buffer)) { + SSL_CTX_free(p); + 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(p); + 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(p, cert_buffer, SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(p); + return -1; + } + + if (SSL_CTX_use_PrivateKey_file(p, key_buffer, SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(p); + return -1; + } + + // Verify that the private key matches the certificate + if (SSL_CTX_check_private_key(p) != 1) { + SSL_CTX_free(p); + return -1; + } + + SSL_CTX_set_tlsext_servername_callback(p, servername_callback); + SSL_CTX_set_tlsext_servername_arg(p, ctx); + + ctx->p = p; + ctx->num_certs = 0; + return 0; +#else + (void) ctx; + (void) cert_file; + (void) key_file; + return -1; +#endif } -static void -callback_free(HTTP_ByteQueue *queue, void *ptr, int len) +void server_secure_context_free(ServerSecureContext *ctx) { - queue->memfunc(HTTP_MEMFUNC_FREE, ptr, len, queue->memfuncdata); +#ifdef HTTPS_ENABLED + SSL_CTX_free(ctx->p); + for (int i = 0; i < ctx->num_certs; i++) + SSL_CTX_free(ctx->certs[i].ctx); +#else + (void) ctx; +#endif } -// Initialize the queue -static void -byte_queue_init(HTTP_ByteQueue *queue, unsigned int limit, HTTP_MemoryFunc memfunc, void *memfuncdata) +int server_secure_context_add_certificate(ServerSecureContext *ctx, + HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) { - 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; +#ifdef HTTPS_ENABLED + if (ctx->num_certs == SERVER_CERTIFICATE_LIMIT) + return -1; + + SSL_CTX *p = SSL_CTX_new(TLS_server_method()); + if (!p) + return -1; + + SSL_CTX_set_min_proto_version(p, TLS1_2_VERSION); + + char cert_buffer[1024]; + if (cert_file.len >= (int) sizeof(cert_buffer)) { + SSL_CTX_free(p); + 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(p); + return -1; + } + memcpy(key_buffer, key_file.ptr, key_file.len); + key_buffer[key_file.len] = '\0'; + + if (SSL_CTX_use_certificate_file(p, cert_buffer, SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(p); + return -1; + } + + if (SSL_CTX_use_PrivateKey_file(p, key_buffer, SSL_FILETYPE_PEM) != 1) { + SSL_CTX_free(p); + return -1; + } + + if (SSL_CTX_check_private_key(p) != 1) { + SSL_CTX_free(p); + return -1; + } + + ServerCertificate *cert = &ctx->certs[ctx->num_certs]; + if (domain.len >= (int) sizeof(cert->domain)) { + SSL_CTX_free(p); + return -1; + } + memcpy(cert->domain, domain.ptr, domain.len); + cert->domain[domain.len] = '\0'; + cert->ctx = p; + ctx->num_certs++; + return 0; +#else + (void) ctx; + (void) domain; + (void) cert_file; + (void) key_file; + return -1; +#endif +} + +//////////////////////////////////////////////////////////////////////////////////////// +// 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; + if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR) { + closesocket(sock); + return -1; + } + + *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(HTTP_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) { + CLOSE_NATIVE_SOCKET(sock); + return NATIVE_SOCKET_INVALID; + } + + if (listen(sock, backlog) < 0) { + 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; + + for (int i = 0; i < num_socks; i++) { + socks[i].state = SOCKET_STATE_FREE; + socks[i].gen = 1; + } + 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, + HTTP_String addr, Port port, int backlog, + bool reuse_addr) +{ + if (sm->plain_sock != NATIVE_SOCKET_INVALID) + return -1; + + 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, + HTTP_String addr, Port port, int backlog, + bool reuse_addr, HTTP_String cert_file, + HTTP_String key_file) +{ + if (sm->secure_sock != NATIVE_SOCKET_INVALID) + return -1; + + 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, + cert_file, key_file) < 0) { + CLOSE_NATIVE_SOCKET(sm->secure_sock); + sm->secure_sock = NATIVE_SOCKET_INVALID; + return -1; + } + + return 0; +} + +int socket_manager_add_certificate(SocketManager *sm, + HTTP_String domain, HTTP_String cert_file, HTTP_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 is_secure(Socket *s) +{ +#ifdef HTTPS_ENABLED + return s->server_secure_context != NULL + || s->client_secure_context != NULL; +#else + return false; +#endif +} + +static bool connect_pending(void) +{ +#ifdef _WIN32 + return WSAGetLastError() == WSAEWOULDBLOCK; +#else + return errno == EINPROGRESS; +#endif +} + +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 +} + +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 void free_addr_list(AddressAndPort *addrs, int num_addr) +{ +#ifdef HTTPS_ENABLED + for (int i = 0; i < num_addr; i++) { + RegisteredName *name = addrs[i].name; + if (name) { + assert(name->refs > 0); + name->refs--; + if (name->refs == 0) + free(name); + } + } +#else + (void) addrs; + (void) num_addr; +#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 *s) +{ + // 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 (s->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 (s->sock != NATIVE_SOCKET_INVALID) { + // This is not the first attempt + +#ifdef HTTPS_ENABLED + if (s->ssl) { + SSL_free(s->ssl); + s->ssl = NULL; + } +#endif + + CLOSE_NATIVE_SOCKET(s->sock); + + s->next_addr++; + if (s->next_addr == s->num_addr) { + // All addresses have been tried and failed + s->state = SOCKET_STATE_DIED; + s->events = 0; + continue; + } + } + AddressAndPort addr = s->addrs[s->next_addr]; + + int family = (addr.is_ipv4 ? AF_INET : AF_INET6); + NATIVE_SOCKET sock = socket(family, SOCK_STREAM, 0); + if (sock == NATIVE_SOCKET_INVALID) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + continue; + } + + if (set_socket_blocking(sock, false) < 0) { + CLOSE_NATIVE_SOCKET(sock); + s->state = SOCKET_STATE_DIED; + s->events = 0; + continue; + } + + int ret; + if (addr.is_ipv4) { + struct sockaddr_in buf; + buf.sin_family = AF_INET; + buf.sin_port = htons(addr.port); + memcpy(&buf.sin_addr, &addr.ipv4, sizeof(HTTP_IPv4)); + ret = connect(sock, (struct sockaddr*) &buf, sizeof(buf)); + } else { + struct sockaddr_in6 buf; + buf.sin6_family = AF_INET6; + buf.sin6_port = htons(addr.port); + memcpy(&buf.sin6_addr, &addr.ipv6, sizeof(HTTP_IPv6)); + ret = connect(sock, (struct sockaddr*) &buf, sizeof(buf)); + } + + if (ret == 0) { + // Connect resolved immediately + s->sock = sock; + s->state = SOCKET_STATE_CONNECTED; + s->events = 0; + again = true; + } else if (connect_pending()) { + // Connect is pending, which is expected + s->sock = sock; + s->state = SOCKET_STATE_CONNECTING; + s->events = POLLOUT; + } else if (connect_failed_because_of_peer()) { + // Conenct failed due to the peer host + // We should try a different address. + s->sock = sock; + s->state = SOCKET_STATE_PENDING; + s->events = 0; + again = true; + } else { + // An error occurred that we can't recover from + s->sock = sock; + s->state = SOCKET_STATE_DIED; + s->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(s->sock, SOL_SOCKET, SO_ERROR, (void*) &err, &len) < 0) { + // Failed to get socket error status + s->state = SOCKET_STATE_DIED; + s->events = 0; + continue; + } + + if (err == 0) { + // Connection succeded + s->state = SOCKET_STATE_CONNECTED; + s->events = 0; + again = true; + } else if (connect_failed_because_of_peer_2(err)) { + // Try the next address + s->state = SOCKET_STATE_PENDING; + s->events = 0; + again = true; + } else { + s->state = SOCKET_STATE_DIED; + s->events = 0; + } + } + break; + + case SOCKET_STATE_CONNECTED: + { + if (!is_secure(s)) { + + // We managed to connect to the peer. + // We can free the target array if it + // was allocated dynamically. + if (s->num_addr > 1) + free(s->addrs); + + s->events = 0; + s->state = SOCKET_STATE_ESTABLISHED_READY; + } else { +#ifdef HTTPS_ENABLED + if (s->ssl == NULL) { + s->ssl = SSL_new(s->client_secure_context->p); + if (s->ssl == NULL) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + break; + } + + if (SSL_set_fd(s->ssl, s->sock) != 1) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + break; + } + + AddressAndPort addr; + if (s->num_addr > 1) + addr = s->addrs[s->next_addr]; + else + addr = s->addr; + + if (addr.name) + SSL_set_tlsext_host_name(s->ssl, addr.name->data); + } + + int ret = SSL_connect(s->ssl); + if (ret == 1) { + // Handshake done + + // We managed to connect to the peer. + // We can free the target array if it + // was allocated dynamically. + if (s->num_addr == 1) + free_addr_list(&s->addr, 1); + else { + assert(s->num_addr > 1); + free_addr_list(s->addrs, s->num_addr); + free(s->addrs); + } + + s->state = SOCKET_STATE_ESTABLISHED_READY; + s->events = 0; + break; + } + + int err = SSL_get_error(s->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + s->events = POLLIN; + break; + } + + if (err == SSL_ERROR_WANT_WRITE) { + s->events = POLLOUT; + break; + } + + s->state = SOCKET_STATE_PENDING; + s->events = 0; + again = true; +#endif + } + } + break; + + case SOCKET_STATE_ACCEPTED: + { + if (!is_secure(s)) { + s->state = SOCKET_STATE_ESTABLISHED_READY; + s->events = 0; + } else { +#ifdef HTTPS_ENABLED + // Start server-side SSL handshake + if (!s->ssl) { + + s->ssl = SSL_new(s->server_secure_context->p); + if (s->ssl == NULL) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + break; + } + + if (SSL_set_fd(s->ssl, s->sock) != 1) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + break; + } + } + + int ret = SSL_accept(s->ssl); + if (ret == 1) { + // Handshake done + s->state = SOCKET_STATE_ESTABLISHED_READY; + s->events = 0; + break; + } + + int err = SSL_get_error(s->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + s->events = POLLIN; + break; + } + + if (err == SSL_ERROR_WANT_WRITE) { + s->events = POLLOUT; + break; + } + + // Server socket error - close the connection + s->state = SOCKET_STATE_DIED; + s->events = 0; +#endif + } + } + break; + + case SOCKET_STATE_ESTABLISHED_WAIT: + s->state = SOCKET_STATE_ESTABLISHED_READY; + s->events = 0; + break; + + case SOCKET_STATE_SHUTDOWN: + { + if (!is_secure(s)) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + } else { +#ifdef HTTPS_ENABLED + int ret = SSL_shutdown(s->ssl); + if (ret == 1) { + s->state = SOCKET_STATE_DIED; + s->events = 0; + break; + } + + int err = SSL_get_error(s->ssl, ret); + if (err == SSL_ERROR_WANT_READ) { + s->events = POLLIN; + break; + } + + if (err == SSL_ERROR_WANT_WRITE) { + s->events = POLLOUT; + break; + } + + s->state = SOCKET_STATE_DIED; + s->events = 0; +#endif + } + } + break; + + default: + // Do nothing + break; + } + } while (again); +} + +int socket_manager_wakeup(SocketManager *sm) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + + // Send a byte through the signal socket to wake up any thread + // blocked on poll() with the wait socket + char byte = 1; + int ret = 0; +#ifdef _WIN32 + if (send(sm->signal_sock, &byte, 1, 0) < 0) + ret = -1; +#else + if (write(sm->signal_sock, &byte, 1) < 0) + ret = -1; +#endif + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return ret; +} + +static int socket_manager_register_events_nolock( + SocketManager *sm, EventRegister *reg) +{ + // 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 (reg->max_polled < sm->max_used+3) + return -1; + reg->num_polled = 0; + + reg->polled[reg->num_polled].fd = sm->wait_sock; + reg->polled[reg->num_polled].events = POLLIN; + reg->polled[reg->num_polled].revents = 0; + reg->ptrs[reg->num_polled] = NULL; + reg->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) { + reg->polled[reg->num_polled].fd = sm->plain_sock; + reg->polled[reg->num_polled].events = POLLIN; + reg->polled[reg->num_polled].revents = 0; + reg->ptrs[reg->num_polled] = NULL; + reg->num_polled++; + } + + if (sm->secure_sock != NATIVE_SOCKET_INVALID) { + reg->polled[reg->num_polled].fd = sm->secure_sock; + reg->polled[reg->num_polled].events = POLLIN; + reg->polled[reg->num_polled].revents = 0; + reg->ptrs[reg->num_polled] = NULL; + reg->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 at least one socket can be processed, return an + // empty list. + if (s->state == SOCKET_STATE_DIED || s->state == SOCKET_STATE_ESTABLISHED_READY) { + reg->num_polled = 0; + return 0; + } + + if (s->events) { + reg->polled[reg->num_polled].fd = s->sock; + reg->polled[reg->num_polled].events = s->events; + reg->polled[reg->num_polled].revents = 0; + reg->ptrs[reg->num_polled] = s; + reg->num_polled++; + } + } + + return 0; +} + +int socket_manager_register_events(SocketManager *sm, + EventRegister *reg) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + + int ret = socket_manager_register_events_nolock(sm, reg); + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return ret; +} + +static SocketHandle +socket_to_handle(SocketManager *sm, Socket *s) +{ + return ((uint32_t) (s - sm->sockets) << 16) | s->gen; +} + +static Socket *handle_to_socket(SocketManager *sm, SocketHandle handle) +{ + uint16_t gen = handle & 0xFFFF; + uint16_t idx = handle >> 16; + if (idx >= sm->max_used) + return NULL; + if (sm->sockets[idx].gen != gen) + return NULL; + return &sm->sockets[idx]; +} + +static int socket_manager_translate_events_nolock( + SocketManager *sm, SocketEvent *events, + EventRegister *reg) +{ + int num_events = 0; + for (int i = 0; i < reg->num_polled; i++) { + + if (!reg->polled[i].revents) + continue; + + if (reg->polled[i].fd == sm->plain_sock || + reg->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 = (reg->polled[i].fd == sm->secure_sock); + + Socket *s = sm->sockets; + while (s->state != SOCKET_STATE_FREE) { + s++; + assert(s - sm->sockets < + sm->max_used); + } + + NATIVE_SOCKET sock = accept(reg->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; +#ifdef HTTPS_ENABLED + s->ssl = NULL; + s->server_secure_context = NULL; + s->client_secure_context = NULL; + if (secure) + &s->server_secure_context = sm->server_secure_context; +#endif + + socket_update(s); + if (s->state == SOCKET_STATE_DIED) { + CLOSE_NATIVE_SOCKET(sock); + s->state = SOCKET_STATE_FREE; + s->gen++; + if (s->gen == 0) + s->gen = 1; + continue; + } + + sm->num_used++; + + } else if (reg->polled[i].fd == sm->wait_sock) { + + // Consume one byte from the wakeup signal + char byte; +#ifdef _WIN32 + recv(sm->wait_sock, &byte, 1, 0); +#else + read(sm->wait_sock, &byte, 1); +#endif + + } else { + + Socket *s = reg->ptrs[i]; + if (reg->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_STATE_FREE) + continue; + j++; + + if (s->state == SOCKET_STATE_DIED) { + + events[num_events++] = (SocketEvent) { + SOCKET_EVENT_DISCONNECT, + SOCKET_HANDLE_INVALID, + s->user + }; + + // Free resources associated to socket + s->state = SOCKET_STATE_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); + } + sm->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, EventRegister *reg) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + + int ret = socket_manager_translate_events_nolock(sm, events, reg); + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return ret; +} + +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", targets[i].port); + assert(len > 1 && len < (int) sizeof(portstr)); + + struct addrinfo hints = {0}; + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + +#ifdef HTTPS_ENABLED + RegisteredName *name = malloc(sizeof(RegisteredName) + targets[i].name.len + 1); + if (name == NULL) { + free_addr_list(resolved, num_resolved); + return -1; + } + name->refs = 0; + memcpy(name->data, targets[i].name.ptr, targets[i].name.len); + name->data[targets[i].name.len] = '\0'; + char *hostname = name->data; +#else + // 512 bytes is more than enough for a DNS hostname (max 253 chars) + char hostname[1<<9]; + if (targets[i].name.len >= (int) sizeof(hostname)) + return -1; + memcpy(hostname, targets[i].name.ptr, targets[i].name.len); + hostname[targets[i].name.len] = '\0'; +#endif + struct addrinfo *res = NULL; + int ret = getaddrinfo(hostname, portstr, &hints, &res); + if (ret != 0) { +#ifdef HTTPS_ENABLED + // Free the name allocated for this target + free(name); +#endif + free_addr_list(resolved, num_resolved); + return -1; + } + + for (struct addrinfo *rp = res; rp; rp = rp->ai_next) { + if (rp->ai_family == AF_INET) { + HTTP_IPv4 ipv4 = *(HTTP_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; +#ifdef HTTPS_ENABLED + resolved[num_resolved].name = name; + name->refs++; +#endif + num_resolved++; + } + } else if (rp->ai_family == AF_INET6) { + HTTP_IPv6 ipv6 = *(HTTP_IPv6*) &((struct sockaddr_in6*)rp->ai_addr)->sin6_addr; + if (num_resolved < max_resolved) { + resolved[num_resolved].is_ipv4 = false; + resolved[num_resolved].ipv6 = ipv6; + resolved[num_resolved].port = targets[i].port; +#ifdef HTTPS_ENABLED + resolved[num_resolved].name = name; + name->refs++; +#endif + num_resolved++; + } + } + } + +#ifdef HTTPS_ENABLED + if (name->refs == 0) + free(name); +#endif + + 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; +#ifdef HTTPS_ENABLED + resolved[num_resolved].name = NULL; +#endif + 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; +#ifdef HTTPS_ENABLED + resolved[num_resolved].name = NULL; +#endif + num_resolved++; + } + break; + } + } + return num_resolved; +} + +#define MAX_CONNECT_TARGETS 16 + +int socket_connect(SocketManager *sm, int num_targets, + ConnectTarget *targets, bool secure, void *user) +{ + if (sm->num_used == sm->max_used) + return -1; + +#ifdef HTTPS_ENABLED + if (!sm->at_least_one_secure_connect) { + if (client_secure_context_init(&sm->client_secure_context) < 0) + return -1; + sm->at_least_one_secure_connect = true; + } +#else + if (secure) + return -1; +#endif + + 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->state != SOCKET_STATE_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->state = SOCKET_STATE_PENDING; + s->sock = NATIVE_SOCKET_INVALID; + s->user = user; +#ifdef HTTPS_ENABLED + s->server_secure_context = NULL; + s->client_secure_context = NULL; + s->ssl = NULL; + if (secure) + s->client_secure_context = &sm->client_secure_context; +#endif + sm->num_used++; + return 0; +} + +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 +} + +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 ret; + } 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 (!is_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_STATE_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; +} + +int socket_close(SocketManager *sm, SocketHandle handle) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + + int ret; + Socket *s = handle_to_socket(sm, handle); + if (s == NULL) + ret = -1; + else { + // Only transition to SHUTDOWN if socket is not already DIED + if (s->state != SOCKET_STATE_DIED) { + s->state = SOCKET_STATE_SHUTDOWN; + s->events = 0; + socket_update(s); + } + } + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return ret; +} + +int socket_is_secure(SocketManager *sm, SocketHandle handle) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + + Socket *s = handle_to_socket(sm, handle); + + int ret; + if (s == NULL) + ret = -1; + else + ret = is_secure(s); + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return ret; +} + +int socket_set_user(SocketManager *sm, SocketHandle handle, void *user) +{ + if (mutex_lock(&sm->mutex) < 0) + return -1; + + int ret; + Socket *s = handle_to_socket(sm, handle); + if (s == NULL) + ret = -1; + else { + s->user = user; + ret = 0; + } + + if (mutex_unlock(&sm->mutex) < 0) + return -1; + return ret; +} + +//////////////////////////////////////////////////////////////////////////////////////// +// 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 -static void -byte_queue_free(HTTP_ByteQueue *queue) +void byte_queue_free(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; - } + if (queue->read_target) { + if (queue->read_target != queue->data) + free(queue->read_target); + queue->read_target = NULL; + queue->read_target_size = 0; + } - callback_free(queue, queue->data, queue->size); - queue->data = NULL; + free(queue->data); + queue->data = NULL; } -static int -byte_queue_error(HTTP_ByteQueue *queue) +int byte_queue_error(ByteQueue *queue) { - return queue->flags & BYTE_QUEUE_ERROR; + return queue->flags & BYTE_QUEUE_ERROR; } -static int -byte_queue_empty(HTTP_ByteQueue *queue) +int byte_queue_empty(ByteQueue *queue) { - return queue->used == 0; + 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) +int byte_queue_full(ByteQueue *queue) { - 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; + return queue->used == queue->limit; } -// Complete a previously started operation on the queue. -static void -byte_queue_read_ack(HTTP_ByteQueue *queue, int num) +ByteView byte_queue_read_buf(ByteQueue *queue) { - HTTP_ASSERT(num >= 0); + if (queue->flags & BYTE_QUEUE_ERROR) + return (ByteView) {NULL, 0}; - if (queue->flags & BYTE_QUEUE_ERROR) - return; + 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->flags & BYTE_QUEUE_READ) == 0) - return; + if (queue->data == NULL) + return (ByteView) {NULL, 0}; - 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; - } + return (ByteView) { queue->data + queue->head, queue->used }; } -static char* -byte_queue_write_buf(HTTP_ByteQueue *queue, int *cap) +void byte_queue_read_ack(ByteQueue *queue, uint32_t num) { - if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL) { - *cap = 0; - return NULL; - } + if (queue->flags & BYTE_QUEUE_ERROR) + return; - HTTP_ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0); - queue->flags |= BYTE_QUEUE_WRITE; + if ((queue->flags & BYTE_QUEUE_READ) == 0) + return; - unsigned int ucap = queue->size - (queue->head + queue->used); - if (ucap > INT_MAX) ucap = INT_MAX; + queue->flags &= ~BYTE_QUEUE_READ; - *cap = (int) ucap; - return queue->data + (queue->head + queue->used); + 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; + } } -static void -byte_queue_write_ack(HTTP_ByteQueue *queue, int num) +ByteView byte_queue_write_buf(ByteQueue *queue) { - HTTP_ASSERT(num >= 0); + if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL) + return (ByteView) {NULL, 0}; - if (queue->flags & BYTE_QUEUE_ERROR) - return; + assert((queue->flags & BYTE_QUEUE_WRITE) == 0); + queue->flags |= BYTE_QUEUE_WRITE; - if ((queue->flags & BYTE_QUEUE_WRITE) == 0) - return; - - queue->flags &= ~BYTE_QUEUE_WRITE; - queue->used += (unsigned int) num; + return (ByteView) { + queue->data + (queue->head + queue->used), + queue->size - (queue->head + queue->used), + }; } -// 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) +void byte_queue_write_ack(ByteQueue *queue, uint32_t num) { - HTTP_ASSERT(mincap >= 0); - unsigned int umincap = (unsigned int) mincap; + if (queue->flags & BYTE_QUEUE_ERROR) + return; - // Sticky error - if (queue->flags & BYTE_QUEUE_ERROR) - return 0; + if ((queue->flags & BYTE_QUEUE_WRITE) == 0) + return; - // 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; + queue->flags &= ~BYTE_QUEUE_WRITE; + queue->used += num; } -static HTTP_ByteQueueOffset -byte_queue_offset(HTTP_ByteQueue *queue) +int byte_queue_write_setmincap(ByteQueue *queue, uint32_t mincap) { - if (queue->flags & BYTE_QUEUE_ERROR) - return (HTTP_ByteQueueOffset) { 0 }; - return (HTTP_ByteQueueOffset) { queue->curs + queue->used }; + // 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; } -static unsigned int -byte_queue_size_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off) +void byte_queue_write(ByteQueue *queue, void *ptr, uint32_t len) { - return queue->curs + queue->used - off; + 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); + } } -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) +void byte_queue_write_fmt2(ByteQueue *queue, + const char *fmt, va_list args) { if (queue->flags & BYTE_QUEUE_ERROR) return; @@ -2054,22 +3160,21 @@ byte_queue_write_fmt2(HTTP_ByteQueue *queue, const char *fmt, va_list args) va_list args2; va_copy(args2, args); - int cap; byte_queue_write_setmincap(queue, 128); - char *dst = byte_queue_write_buf(queue, &cap); + ByteView dst = byte_queue_write_buf(queue); - int len = vsnprintf(dst, cap, fmt, args); + int len = vsnprintf(dst.ptr, dst.len, fmt, args); if (len < 0) { queue->flags |= BYTE_QUEUE_ERROR; va_end(args2); return; } - if (len > cap) { + if (len > dst.len) { 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); + dst = byte_queue_write_buf(queue); + vsnprintf(dst.ptr, dst.len, fmt, args2); } byte_queue_write_ack(queue, len); @@ -2077,8 +3182,8 @@ byte_queue_write_fmt2(HTTP_ByteQueue *queue, const char *fmt, va_list args) va_end(args2); } -static void -byte_queue_write_fmt(HTTP_ByteQueue *queue, const char *fmt, ...) +void byte_queue_write_fmt(ByteQueue *queue, + const char *fmt, ...) { va_list args; va_start(args, fmt); @@ -2086,620 +3191,50 @@ byte_queue_write_fmt(HTTP_ByteQueue *queue, const char *fmt, ...) va_end(args); } -#define TEN_SPACES " " - -void http_engine_init(HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata) +ByteQueueOffset byte_queue_offset(ByteQueue *queue) { - 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); + if (queue->flags & BYTE_QUEUE_ERROR) + return (ByteQueueOffset) { 0 }; + return (ByteQueueOffset) { queue->curs + queue->used }; } -void http_engine_free(HTTP_Engine *eng) +void byte_queue_patch(ByteQueue *queue, ByteQueueOffset off, + void *src, uint32_t len) { - byte_queue_free(&eng->input); - byte_queue_free(&eng->output); - eng->state = HTTP_ENGINE_STATE_NONE; + 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); } -void http_engine_close(HTTP_Engine *eng) +uint32_t byte_queue_size_from_offset(ByteQueue *queue, ByteQueueOffset off) { - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - else - eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; + return queue->curs + queue->used - off; } -HTTP_EngineState http_engine_state(HTTP_Engine *eng) +void byte_queue_remove_from_offset(ByteQueue *queue, ByteQueueOffset offset) { - return eng->state; -} + if (queue->flags & BYTE_QUEUE_ERROR) + return; -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"; - } -} + uint64_t num = (queue->curs + queue->used) - offset; + assert(num <= queue->used); -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; + queue->used -= num; } //////////////////////////////////////////////////////////////////////////////////////// // 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) @@ -2736,10 +3271,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 @@ -2853,2271 +3388,994 @@ int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, #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) - 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]); -} - //////////////////////////////////////////////////////////////////////////////////////// // 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) +static void http_client_conn_init(HTTP_ClientConn *conn, + SocketHandle handle, uint32_t input_buffer_limit, + uint32_t output_buffer_limit) { - int ret = socket_pool_global_init(); - if (ret < 0) - return -1; - return 0; + conn->state = HTTP_CLIENT_CONN_WAIT_LINE; + conn->handle = handle; + conn->gen = 0; + byte_queue_init(&conn->input, input_buffer_limit); + byte_queue_init(&conn->output, output_buffer_limit); } -void http_global_free(void) +static void http_client_conn_free(HTTP_ClientConn *conn) { - socket_pool_global_free(); + byte_queue_free(&conn->output); + byte_queue_free(&conn->input); } -// 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) +int http_client_init(HTTP_Client *client) { - 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->input_buffer_limit = 1<<20; + client->output_buffer_limit = 1<<20; client->num_conns = 0; - client->ready_head = 0; - client->ready_count = 0; + for (int i = 0; i < HTTP_CLIENT_CAPACITY; i++) { + client->conns[i].state = HTTP_CLIENT_CONN_FREE; + client->conns[i].gen = 0; + } - return client; + 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) { - 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_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder) +void http_client_set_input_limit(HTTP_Client *client, uint32_t limit) { - if (client->num_conns == CLIENT_MAX_CONNS) + client->input_buffer_limit = limit; +} + +void http_client_set_output_limit(HTTP_Client *client, uint32_t limit) +{ + client->output_buffer_limit = limit; +} + +int http_client_wakeup(HTTP_Client *client) +{ + if (socket_manager_wakeup(&client->sockets) < 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_client_register_events(HTTP_Client *client, + EventRegister *reg) { - while (client->ready_count == 0) { - - SocketEvent event = socket_pool_wait(client->socket_pool); - switch (event.type) { - - case SOCKET_EVENT_DIED: - { - ClientConnection *conn = event.user_data; - 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++; - } - break; - - case SOCKET_EVENT_READY: - { - ClientConnection *conn = event.user_data; - - if (conn->sock == -1) - conn->sock = event.handle; - - HTTP_EngineState engine_state; - engine_state = http_engine_state(&conn->eng); - - 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); - } - } - - engine_state = http_engine_state(&conn->eng); - - 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; - } - + if (socket_manager_register_events(&client->sockets, reg) < 0) + return -1; return 0; } -static ClientConnection *client_builder_to_conn(HTTP_RequestBuilder handle) +// Get a connection pointer from a request builder. +// If the builder is invalid, returns NULL. +static HTTP_ClientConn* +request_builder_to_conn(HTTP_RequestBuilder builder) { - if (handle.data0 == NULL) + HTTP_Client *client = builder.client; + if (client == NULL) return NULL; - HTTP_Client *client = handle.data0; - - if (handle.data1 >= CLIENT_MAX_CONNS) + if (builder.index >= HTTP_CLIENT_CAPACITY) return NULL; - ClientConnection *conn = &client->conns[handle.data1]; - - if (handle.data2 != conn->gen) + HTTP_ClientConn *conn = &client->conns[builder.index]; + if (builder.gen != conn->gen) return NULL; return conn; } -void http_request_builder_user_data(HTTP_RequestBuilder builder, void *user_data) +int http_client_get_builder(HTTP_Client *client, + HTTP_Response *response, HTTP_RequestBuilder *builder) { - ClientConnection *conn = client_builder_to_conn(builder); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT) - return; + HTTP_ClientConn *conn = NULL; - conn->user_data = user_data; -} + if (response != NULL && response->context != NULL) { + // Reuse the connection from the previous response + conn = (HTTP_ClientConn*) response->context; -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; + // Mark the response as freed + response->context = NULL; - 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++; + // Reset the connection for a new request + byte_queue_read_ack(&conn->input, byte_queue_read_buf(&conn->input).len); + byte_queue_read_ack(&conn->output, byte_queue_read_buf(&conn->output).len); + conn->state = HTTP_CLIENT_CONN_WAIT_LINE; } else { - http_engine_done(&conn->eng); - conn->state = CLIENT_CONNECTION_WAIT; + // Find a free connection slot + if (client->num_conns == HTTP_CLIENT_CAPACITY) + return -1; + + int i = 0; + while (client->conns[i].state != HTTP_CLIENT_CONN_FREE) + i++; + + conn = &client->conns[i]; + conn->state = HTTP_CLIENT_CONN_WAIT_LINE; + conn->handle = SOCKET_HANDLE_INVALID; + conn->client = client; + byte_queue_init(&conn->input, client->input_buffer_limit); + byte_queue_init(&conn->output, client->output_buffer_limit); + client->num_conns++; } + + *builder = (HTTP_RequestBuilder) { + client, + conn - client->conns, + conn->gen + }; + + return 0; } -void http_response_free(HTTP_Response *res) +void http_request_builder_url(HTTP_RequestBuilder builder, + HTTP_Method method, HTTP_String url) { - if (res == NULL) + HTTP_ClientConn *conn = request_builder_to_conn(builder); + if (conn == NULL) return; - HTTP_Client *client = res->context; + if (conn->state != HTTP_CLIENT_CONN_WAIT_LINE) + return; - ClientConnection *conn = NULL; - for (int i = 0, j = 0; j < client->num_conns; i++) { + // Parse the URL to extract components + HTTP_URL parsed_url; + if (http_parse_url(url.ptr, url.len, &parsed_url) != 1) + return; - if (client->conns[i].state == CLIENT_CONNECTION_FREE) - continue; - j++; + // Store method and parsed URL for connection establishment + conn->method = method; + conn->url = parsed_url; - if (client->conns[i].state != CLIENT_CONNECTION_DONE) - continue; + // Convert method enum to string + const char *method_str; + switch (method) { + case HTTP_METHOD_GET: method_str = "GET"; break; + case HTTP_METHOD_HEAD: method_str = "HEAD"; break; + case HTTP_METHOD_POST: method_str = "POST"; break; + case HTTP_METHOD_PUT: method_str = "PUT"; break; + case HTTP_METHOD_DELETE: method_str = "DELETE"; break; + case HTTP_METHOD_CONNECT: method_str = "CONNECT"; break; + case HTTP_METHOD_OPTIONS: method_str = "OPTIONS"; break; + case HTTP_METHOD_TRACE: method_str = "TRACE"; break; + case HTTP_METHOD_PATCH: method_str = "PATCH"; break; + default: return; + } - if (http_engine_getres(&client->conns[i].eng) == res) { - conn = &client->conns[i]; - break; + // Build request line: METHOD path HTTP/1.1\r\n + byte_queue_write_fmt(&conn->output, "%s %.*s HTTP/1.1\r\n", + method_str, + parsed_url.path.len, parsed_url.path.ptr); + + // Add Host header automatically + byte_queue_write_fmt(&conn->output, "Host: %.*s", + parsed_url.authority.host.text.len, + parsed_url.authority.host.text.ptr); + + if (parsed_url.authority.port > 0) { + byte_queue_write_fmt(&conn->output, ":%d", parsed_url.authority.port); + } + byte_queue_write(&conn->output, "\r\n", 2); + + conn->state = HTTP_CLIENT_CONN_WAIT_HEADER; +} + +void http_request_builder_header(HTTP_RequestBuilder builder, + HTTP_String str) +{ + HTTP_ClientConn *conn = request_builder_to_conn(builder); + if (conn == NULL) + return; + + if (conn->state != HTTP_CLIENT_CONN_WAIT_HEADER) + return; + + // Validate header: must contain a colon and no control characters + bool has_colon = false; + for (int i = 0; i < str.len; i++) { + char c = str.ptr[i]; + if (c == ':') + has_colon = true; + // Reject control characters (especially \r and \n) + if (c < 0x20 && c != '\t') + return; + } + if (!has_colon) + return; + + byte_queue_write(&conn->output, str.ptr, str.len); + byte_queue_write(&conn->output, "\r\n", 2); +} + +void http_request_builder_body(HTTP_RequestBuilder builder, + HTTP_String str) +{ + HTTP_ClientConn *conn = request_builder_to_conn(builder); + if (conn == NULL) + return; + + // Transition from WAIT_HEADER to WAIT_BODY if needed + if (conn->state == HTTP_CLIENT_CONN_WAIT_HEADER) { + // End headers section + byte_queue_write(&conn->output, "\r\n", 2); + conn->state = HTTP_CLIENT_CONN_WAIT_BODY; + } + + if (conn->state != HTTP_CLIENT_CONN_WAIT_BODY) + return; + + byte_queue_write(&conn->output, str.ptr, str.len); +} + +int http_request_builder_send(HTTP_RequestBuilder builder) +{ + HTTP_ClientConn *conn = request_builder_to_conn(builder); + if (conn == NULL) + return -1; + + // Finalize the request + if (conn->state == HTTP_CLIENT_CONN_WAIT_HEADER) { + // No body, just end headers + byte_queue_write(&conn->output, "\r\n", 2); + } + + // Establish connection if not already connected + if (conn->handle == SOCKET_HANDLE_INVALID) { + + // Determine if connection should be secure + bool secure = false; + if (conn->url.scheme.len == 5 && + strncmp(conn->url.scheme.ptr, "https", 5) == 0) { + secure = true; + } + + // Prepare connection target + ConnectTarget target; + target.port = conn->url.authority.port; + if (target.port <= 0) + target.port = secure ? 443 : 80; + + // Set up target based on host type + if (conn->url.authority.host.mode == HTTP_HOST_MODE_NAME) { + target.type = CONNECT_TARGET_NAME; + target.name = conn->url.authority.host.name; + } else if (conn->url.authority.host.mode == HTTP_HOST_MODE_IPV4) { + target.type = CONNECT_TARGET_IPV4; + target.ipv4 = conn->url.authority.host.ipv4; + } else if (conn->url.authority.host.mode == HTTP_HOST_MODE_IPV6) { + target.type = CONNECT_TARGET_IPV6; + target.ipv6 = conn->url.authority.host.ipv6; + } else { + // Invalid host mode - clean up connection + http_client_conn_free(conn); + conn->state = HTTP_CLIENT_CONN_FREE; + conn->client->num_conns--; + return -1; + } + + if (socket_connect(&conn->client->sockets, 1, &target, secure, conn) < 0) { + // Connection failed - clean up + http_client_conn_free(conn); + conn->state = HTTP_CLIENT_CONN_FREE; + conn->client->num_conns--; + return -1; } } - HTTP_ASSERT(conn); + conn->state = HTTP_CLIENT_CONN_FLUSHING; + conn->gen++; - http_engine_free(&conn->eng); - conn->state = CLIENT_CONNECTION_FREE; - client->num_conns--; + return 0; } -static HTTP_Client *default_client___; // TODO: deinitialize the default client when http_global_free is called - -static HTTP_Client *get_default_client(void) +// Look at the input buffer to see if a complete response +// was buffered. If it was, change the connection's status +// to COMPLETE and push it to the ready queue. +static void +check_response_buffer(HTTP_Client *client, HTTP_ClientConn *conn) { - if (default_client___ == NULL) - default_client___ = http_client_init(); - return default_client___; + assert(conn->state == HTTP_CLIENT_CONN_BUFFERING); + + ByteView src = byte_queue_read_buf(&conn->input); + int ret = http_parse_response(src.ptr, src.len, &conn->response); + + if (ret < 0) { + // Invalid response + byte_queue_read_ack(&conn->input, 0); + socket_close(&client->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 response, abort the exchange. + if (byte_queue_full(&conn->input)) + socket_close(&client->sockets, conn->handle); + + } else { + // Ready + assert(ret == 1); + + conn->state = HTTP_CLIENT_CONN_COMPLETE; + conn->response.context = conn; + + // Push to the ready queue + assert(client->num_ready < HTTP_CLIENT_CAPACITY); + int tail = (client->ready_head + client->num_ready) % HTTP_CLIENT_CAPACITY; + client->ready[tail] = conn - client->conns; + client->num_ready++; + } } -HTTP_Response *http_get(HTTP_String url, HTTP_String *headers, int num_headers) +int http_client_process_events(HTTP_Client *client, + EventRegister *reg) { - HTTP_Client *client = get_default_client(); - if (client == NULL) - return NULL; + SocketEvent events[HTTP_CLIENT_CAPACITY]; + int num_events = socket_manager_translate_events( + &client->sockets, events, reg); + if (num_events < 0) + return -1; - 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); + for (int i = 0; i < num_events; i++) { - 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; + HTTP_ClientConn *conn = events[i].user; - return res; + if (events[i].type == SOCKET_EVENT_DISCONNECT) { + + if (conn != NULL) { + http_client_conn_free(conn); + conn->state = HTTP_CLIENT_CONN_FREE; + client->num_conns--; + } + + } else if (events[i].type == SOCKET_EVENT_READY) { + + if (conn == NULL) + continue; + + // Store the handle if this is a new connection + if (conn->handle == SOCKET_HANDLE_INVALID) + conn->handle = events[i].handle; + + if (conn->state == HTTP_CLIENT_CONN_FLUSHING) { + + // Send request data + int num = 0; + ByteView src = byte_queue_read_buf(&conn->output); + if (src.len) + num = socket_send(&client->sockets, conn->handle, src.ptr, src.len); + byte_queue_read_ack(&conn->output, num); + + if (byte_queue_error(&conn->output)) { + socket_close(&client->sockets, conn->handle); + } else if (byte_queue_empty(&conn->output)) { + // Request fully sent, now wait for response + conn->state = HTTP_CLIENT_CONN_BUFFERING; + } + + } else if (conn->state == HTTP_CLIENT_CONN_BUFFERING) { + + // Receive response data + int min_recv = 1<<10; + byte_queue_write_setmincap(&conn->input, min_recv); + + int num = 0; + ByteView dst = byte_queue_write_buf(&conn->input); + if (dst.len) + num = socket_recv(&client->sockets, conn->handle, dst.ptr, dst.len); + byte_queue_write_ack(&conn->input, num); + + if (byte_queue_error(&conn->input)) + socket_close(&client->sockets, conn->handle); + else + check_response_buffer(client, conn); + } + } + } + + return 0; } -HTTP_Response *http_post(HTTP_String url, HTTP_String *headers, int num_headers, HTTP_String body) +bool http_client_next_response(HTTP_Client *client, + HTTP_Response **response) { - HTTP_Client *client = get_default_client(); - if (client == NULL) - return NULL; + if (client->num_ready == 0) + return false; - 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_ClientConn *conn = &client->conns[client->ready[client->ready_head]]; + client->ready_head = (client->ready_head + 1) % HTTP_CLIENT_CAPACITY; + client->num_ready--; - 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; + assert(conn->state == HTTP_CLIENT_CONN_COMPLETE); + *response = &conn->response; + return true; +} - return res; +void http_free_response(HTTP_Response *response) +{ + if (response == NULL || response->context == NULL) + return; + + HTTP_ClientConn *conn = (HTTP_ClientConn*) response->context; + + // Free the connection resources + http_client_conn_free(conn); + conn->state = HTTP_CLIENT_CONN_FREE; + conn->client->num_conns--; + + // Mark response as freed + response->context = NULL; } //////////////////////////////////////////////////////////////////////////////////////// // 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) +static void http_server_conn_init(HTTP_ServerConn *conn, + SocketHandle handle, uint32_t input_buffer_limit, + uint32_t output_buffer_limit) { - return http_server_init_ex(addr, port, 0, HTTP_STR(""), HTTP_STR("")); + conn->state = HTTP_SERVER_CONN_BUFFERING; + conn->handle = handle; + conn->closing = false; + byte_queue_init(&conn->input, input_buffer_limit); + byte_queue_init(&conn->output, output_buffer_limit); } -HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port, - uint16_t secure_port, HTTP_String cert_file, HTTP_String key_file) +static void http_server_conn_free(HTTP_ServerConn *conn) { - HTTP_Server *server = malloc(sizeof(HTTP_Server)); - if (server == NULL) - return NULL; + byte_queue_free(&conn->output); + byte_queue_free(&conn->input); +} - 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; - } +int http_server_init(HTTP_Server *server) +{ + server->input_buffer_limit = 1<<20; + server->output_buffer_limit = 1<<20; + + server->trace_bytes = false; + server->reuse_addr = false; + server->backlog = 32; - 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; + for (int i = 0; i < HTTP_SERVER_CAPACITY; i++) { + server->conns[i].state = HTTP_SERVER_CONN_FREE; + server->conns[i].gen = 0; } - return server; + server->num_ready = 0; + server->ready_head = 0; + + 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) +void http_server_set_input_limit(HTTP_Server *server, uint32_t limit) { - return socket_pool_add_cert(server->socket_pool, domain, cert_file, key_file); + server->input_buffer_limit = limit; } -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) +void http_server_set_output_limit(HTTP_Server *server, uint32_t limit) { - while (server->ready_count == 0) { + server->output_buffer_limit = limit; +} - SocketEvent event = socket_pool_wait(server->socket_pool); - switch (event.type) { +void http_server_set_trace_bytes(HTTP_Server *server, bool value) +{ + server->trace_bytes = value; +} - 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; +void http_server_set_reuse_addr(HTTP_Server *server, bool reuse) +{ + server->reuse_addr = reuse; +} - case SOCKET_EVENT_READY: - { - Connection *conn = event.user_data; - if (conn == NULL) { +void http_server_set_backlog(HTTP_Server *server, int backlog) +{ + server->backlog = backlog; +} - // 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 }; +int http_server_listen_tcp(HTTP_Server *server, + HTTP_String addr, Port port) +{ + if (socket_manager_listen_tcp(&server->sockets, addr, + port, server->backlog, server->reuse_addr) < 0) + return -1; return 0; } -static Connection* -server_builder_to_conn(HTTP_ResponseBuilder builder) +int http_server_listen_tls(HTTP_Server *server, + HTTP_String addr, Port port, HTTP_String cert_file_name, + HTTP_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, server->backlog, server->reuse_addr, + 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, + HTTP_String domain, HTTP_String cert_file, HTTP_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, + EventRegister *reg) { - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; - - http_engine_body(&conn->engine, str); + if (socket_manager_register_events(&server->sockets, reg) < 0) + return -1; + return 0; } -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 > 0); + + conn->state = HTTP_SERVER_CONN_WAIT_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) +static void +http_server_conn_process_events(HTTP_Server *server, HTTP_ServerConn *conn) { - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) { - *cap = 0; - return NULL; + if (conn->state == HTTP_SERVER_CONN_FLUSHING) { + + ByteView src = byte_queue_read_buf(&conn->output); + + int num = 0; + if (src.len) + num = socket_send(&server->sockets, conn->handle, src.ptr, src.len); + + if (server->trace_bytes) + print_bytes(HTTP_STR("<< "), (HTTP_String) { src.ptr, num }); + + byte_queue_read_ack(&conn->output, num); + + if (byte_queue_error(&conn->output)) { + socket_close(&server->sockets, conn->handle); + return; + } + + 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); + return; + } + conn->state = HTTP_SERVER_CONN_BUFFERING; + } + } + + 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. + ByteView dst = byte_queue_write_buf(&conn->input); + + int num = 0; + if (dst.len) + num = socket_recv(&server->sockets, conn->handle, dst.ptr, dst.len); + + if (server->trace_bytes) + print_bytes(HTTP_STR(">> "), (HTTP_String) { dst.ptr, num }); + + 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); + } + } +} + +int http_server_process_events(HTTP_Server *server, + EventRegister *reg) +{ + SocketEvent events[HTTP_SERVER_CAPACITY]; + int num_events = socket_manager_translate_events(&server->sockets, events, reg); + 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 j = 0; + while (server->conns[j].state != HTTP_SERVER_CONN_FREE) { + j++; + assert(i < HTTP_SERVER_CAPACITY); + } + + conn = &server->conns[j]; + http_server_conn_init(conn, + events[i].handle, + server->input_buffer_limit, + server->output_buffer_limit); + server->num_conns++; + + socket_set_user(&server->sockets, events[i].handle, conn); + } + + http_server_conn_process_events(server, conn); + } + } + + return 0; +} + +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[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; +} + +// 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) +{ + HTTP_Server *server = builder.server; + if (server == NULL) + return NULL; + + if (builder.index > HTTP_SERVER_CAPACITY) + return NULL; + + HTTP_ServerConn *conn = &server->conns[builder.index]; + if (builder.gen != conn->gen) + return NULL; + + return conn; +} + +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 http_engine_bodybuf(&conn->engine, cap); + return "???"; } -void http_response_builder_bodyack(HTTP_ResponseBuilder res, int num) +static void +write_status(HTTP_ServerConn *conn, int status) { - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; - - http_engine_bodyack(&conn->engine, num); + byte_queue_write_fmt(&conn->output, + "HTTP/1.1 %d %s\r\n", + status, get_status_text(status)); } -void http_response_builder_undo(HTTP_ResponseBuilder res) +void http_response_builder_status(HTTP_ResponseBuilder builder, int status) { - 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); - - 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); + write_status(conn, status); + + conn->state = HTTP_SERVER_CONN_WAIT_HEADER; } + +void http_response_builder_header(HTTP_ResponseBuilder builder, HTTP_String str) +{ + HTTP_ServerConn *conn = builder_to_conn(builder); + if (conn == NULL) + return; + + if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER) + return; + + // Validate header: must contain a colon and no control characters + // (to prevent HTTP response splitting attacks) + bool has_colon = false; + for (int i = 0; i < str.len; i++) { + char c = str.ptr[i]; + if (c == ':') + has_colon = true; + // Reject control characters (especially \r and \n) + if (c < 0x20 && c != '\t') + return; + } + if (!has_colon) + return; + + byte_queue_write(&conn->output, str.ptr, str.len); + byte_queue_write(&conn->output, "\r\n", 2); +} + +static void append_special_headers(HTTP_ServerConn *conn) +{ + HTTP_String s; + + if (conn->closing) { + s = HTTP_STR("Connection: Close\r\n"); + byte_queue_write(&conn->output, s.ptr, s.len); + } else { + s = HTTP_STR("Connection: Keep-Alive\r\n"); + byte_queue_write(&conn->output, s.ptr, s.len); + } + + s = HTTP_STR("Content-Length: "); + byte_queue_write(&conn->output, s.ptr, s.len); + + conn->content_length_value_offset = byte_queue_offset(&conn->output); + + #define TEN_SPACES " " + _Static_assert(sizeof(TEN_SPACES) == 10+1); + + s = HTTP_STR(TEN_SPACES "\r\n"); + byte_queue_write(&conn->output, s.ptr, s.len); + + byte_queue_write(&conn->output, "\r\n", 2); + conn->content_length_offset = byte_queue_offset(&conn->output); +} + +static void patch_special_headers(HTTP_ServerConn *conn) +{ + int content_length = byte_queue_size_from_offset(&conn->output, conn->content_length_offset); + + char tmp[11]; + int len = snprintf(tmp, sizeof(tmp), "%d", content_length); + assert(len > 0 && len < 11); + + byte_queue_patch(&conn->output, conn->content_length_value_offset, tmp, len); +} + +void http_response_builder_body(HTTP_ResponseBuilder builder, HTTP_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.ptr, str.len); +} + +void http_response_builder_send(HTTP_ResponseBuilder builder) +{ + 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++; + + http_server_conn_process_events(builder.server, conn); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// 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/web/chttp.h b/web/chttp.h index 0c8548e..0a747e1 100644 --- a/web/chttp.h +++ b/web/chttp.h @@ -1,18 +1,46 @@ -#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 +#include +#include +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#ifdef HTTPS_ENABLED +#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; @@ -33,7 +61,8 @@ bool http_streqcase(HTTP_String s1, HTTP_String s2); // the new one references the contents of the original one. HTTP_String http_trim(HTTP_String s); -// TODO: comment +// Print the contents of a byte string with the given prefix. +// This is primarily used for debugging purposes. void print_bytes(HTTP_String prefix, HTTP_String src); // Macro to simplify converting string literals to @@ -60,48 +89,15 @@ void print_bytes(HTTP_String prefix, HTTP_String src); // Returns the number of items of a static array. #define HTTP_COUNT(X) (sizeof(X) / sizeof((X)[0])) -// TODO: comment +// Macro to unpack an HTTP_String into its length and pointer components. +// Useful for passing HTTP_String to printf-style functions with "%.*s" format. +// Example: printf("%.*s", HTTP_UNPACK(str)); #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 - //////////////////////////////////////////////////////////////////////////////////////// // 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,148 +193,550 @@ 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 +// src/thread.h //////////////////////////////////////////////////////////////////////////////////////// -#line 1 "src/engine.h" -#ifndef HTTP_ENGINE_INCLUDED -#define HTTP_ENGINE_INCLUDED - -#ifndef HTTP_AMALGAMATION -#include "parse.h" +#ifdef _WIN32 +typedef CRITICAL_SECTION Mutex; +#else +typedef pthread_mutex_t Mutex; #endif -typedef enum { - HTTP_MEMFUNC_MALLOC, - HTTP_MEMFUNC_FREE, -} HTTP_MemoryFuncTag; +int mutex_init(Mutex *mutex); +int mutex_free(Mutex *mutex); +int mutex_lock(Mutex *mutex); +int mutex_unlock(Mutex *mutex); -typedef void*(*HTTP_MemoryFunc)(HTTP_MemoryFuncTag tag, - void *ptr, int len, void *data); +//////////////////////////////////////////////////////////////////////////////////////// +// src/secure_context.h +//////////////////////////////////////////////////////////////////////////////////////// + +#ifndef SERVER_CERTIFICATE_LIMIT +// Maximum number of certificates that can be +// associated to a TLS server. This doesn't include +// the default certificate. +#define SERVER_CERTIFICATE_LIMIT 8 +#endif + +int global_secure_context_init(void); +int global_secure_context_free(void); typedef struct { +#ifdef HTTPS_ENABLED + SSL_CTX *p; +#endif +} ClientSecureContext; - 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; +int client_secure_context_init(ClientSecureContext *ctx); +void client_secure_context_free(ClientSecureContext *ctx); 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; +#ifdef HTTPS_ENABLED + char domain[128]; + SSL_CTX *ctx; +#endif +} ServerCertificate; -void http_engine_init (HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata); -void http_engine_free (HTTP_Engine *eng); +typedef struct { +#ifdef HTTPS_ENABLED + SSL_CTX *p; + int num_certs; + ServerCertificate certs[SERVER_CERTIFICATE_LIMIT]; +#endif +} ServerSecureContext; -void http_engine_close (HTTP_Engine *eng); -HTTP_EngineState http_engine_state (HTTP_Engine *eng); +int server_secure_context_init(ServerSecureContext *ctx, + HTTP_String cert_file, HTTP_String key_file); +void server_secure_context_free(ServerSecureContext *ctx); +int server_secure_context_add_certificate(ServerSecureContext *ctx, + HTTP_String domain, HTTP_String cert_file, HTTP_String key_file); -const char* http_engine_statestr(HTTP_EngineState state); // TODO: remove +//////////////////////////////////////////////////////////////////////////////////////// +// src/socket.h +//////////////////////////////////////////////////////////////////////////////////////// +// 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. -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); +#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 -HTTP_Request* http_engine_getreq (HTTP_Engine *eng); -HTTP_Response* http_engine_getres (HTTP_Engine *eng); +typedef uint32_t SocketHandle; +#define SOCKET_HANDLE_INVALID ((SocketHandle) 0) -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); +typedef uint16_t Port; -#endif // HTTP_ENGINE_INCLUDED +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, + + // Outgoing connection was established, but + // a TLS handshake may need to be performed. + SOCKET_STATE_CONNECTED, + + // Incoming connection was established, but + // a TLS handshake may need to be performed. + SOCKET_STATE_ACCEPTED, + + // 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; + +typedef struct { + int refs; + char data[]; +} RegisteredName; + +// Internal use only +typedef struct { + union { + HTTP_IPv4 ipv4; + HTTP_IPv6 ipv6; + }; + bool is_ipv4; + Port port; + +#ifdef HTTPS_ENABLED + // When connecting to a peer using TLS, if the address + // was resolved from a registered name, that name is + // used to request the correct certificate once the TCP + // handshake is established, and therefore need to + // store it somewhere until that happens. + RegisteredName *name; +#endif +} 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; + + // Generation counter to invalidate any SocketHandle + // referring to this socket when it is freed. + // Note that this counter may wrap but always skips + // the 0 value to ensure the 0 SocketHandle is always + // invalid. + uint16_t gen; + + // 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 + }; + +#ifdef HTTPS_ENABLED + ClientSecureContext *client_secure_context; + ServerSecureContext *server_secure_context; + SSL *ssl; +#endif + +} 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, + HTTP_String addr, Port port, int backlog, + bool reuse_addr); + +// 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, + HTTP_String addr, Port port, int backlog, + bool reuse_addr, HTTP_String cert_file, + HTTP_String key_file); + +// 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, + HTTP_String domain, HTTP_String cert_file, HTTP_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); + +typedef struct { + void **ptrs; + struct pollfd *polled; + int max_polled; + int num_polled; +} EventRegister; + +// Resets the event register with the list of descriptors +// the socket manager wants monitored. Returns 0 on +// success, -1 if the event register's capacity isn't +// large enough. +int socket_manager_register_events(SocketManager *sm, + EventRegister *reg); + +// 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. +// +// The maximum number of events this will write +// to the events array is equal to the numero of +// socket structs provided to the socket manager +// via the init function. +int socket_manager_translate_events(SocketManager *sm, + SocketEvent *events, EventRegister *reg); + +typedef enum { + CONNECT_TARGET_NAME, + CONNECT_TARGET_IPV4, + CONNECT_TARGET_IPV6, +} ConnectTargetType; + +typedef struct { + ConnectTargetType type; + Port port; + union { + HTTP_IPv4 ipv4; + HTTP_IPv6 ipv6; + HTTP_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); + +int 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 +int socket_set_user(SocketManager *sm, SocketHandle handle, void *user); + +//////////////////////////////////////////////////////////////////////////////////////// +// 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); + +// Write the result of the format into the queue +void byte_queue_write_fmt(ByteQueue *queue, const char *fmt, ...); + +// Write the result of the format into the queue +void byte_queue_write_fmt2(ByteQueue *queue, const char *fmt, + va_list args); + +// 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 //////////////////////////////////////////////////////////////////////////////////////// - -#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 @@ -356,150 +754,409 @@ void http_engine_undo (HTTP_Engine *eng); 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" +#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. +// Fields of this struct are private 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; + HTTP_ClientConnState state; -// Initialize a client object. If something goes wrong, -// NULL is returned. -HTTP_Client *http_client_init(void); + // Handle to the socket + SocketHandle handle; -// Deinitialize a client object + // Pointer back to the client + HTTP_Client *client; + + // Generation counter for request builder validation + uint16_t gen; + + // Data received from the server + ByteQueue input; + + // Data being sent to the server + ByteQueue output; + + // HTTP method for the request + HTTP_Method method; + + // Parsed URL for connection establishment + HTTP_URL url; + + // Parsed response once complete + HTTP_Response response; +} HTTP_ClientConn; + +// Fields of this struct are private +struct HTTP_Client { + + // Size limit of the input and output buffer of each + // connection. + uint32_t input_buffer_limit; + uint32_t output_buffer_limit; + + // 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 client 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]; + +}; + +// 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); +// Set input and output buffer size limit for any +// given connection. The default value is 1MB +void http_client_set_input_limit(HTTP_Client *client, uint32_t limit); +void http_client_set_output_limit(HTTP_Client *client, uint32_t limit); -void http_request_builder_user_data(HTTP_RequestBuilder builder, void *user_data); +// 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); -// 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); +typedef struct { + HTTP_Client *client; + uint16_t index; + uint16_t gen; +} HTTP_RequestBuilder; -// 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); +// 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); -// Append a header to the specified request. You must call -// this after http_request_line and may do so multiple times. +// Set the method and 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, + HTTP_Method method, HTTP_String url); + +// After the URL, the user may set zero or more headers. 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. +// 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, HTTP_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. +// Returns 0 on success, -1 on error. +int 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); +// Resets the event register with the list of descriptors +// the client wants monitored. Returns 0 on success, -1 if +// the event register's capacity isn't large enough. +int http_client_register_events(HTTP_Client *client, + EventRegister *reg); -// 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, + EventRegister *reg); -// 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 +// Free a response object. You can't access its fields +// again after this. +void http_free_response(HTTP_Response *response); //////////////////////////////////////////////////////////////////////////////////////// // 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; + + // Generation counter. This is used to invalidate + // response builders that refer to this connection. + uint16_t gen; + + // This is set during the WAIT_XXX states or + // the FLUSHING state. When the connection + // completes flushing and no more bytes are + // in the output buffer, it frees the connection + // instead of turning it back to BUFFERING. + bool closing; + + // 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; + + // When the first byte of the response content is + // written, before it are prepended special headers, + // including Content-Length and Connection. This + // offset points to the first byte that comes after + // the string "Content-Length: ". + ByteQueueOffset content_length_value_offset; + + // Similarly to the previous field, this one points + // to the first byte of the body. This allows calculating + // the length of the response content byte subtracting + // it from the offset reached when the response is marked + // as done. + ByteQueueOffset content_length_offset; +} HTTP_ServerConn; + +typedef struct { + + // Size limit of the input and output buffer of each + // connection. + uint32_t input_buffer_limit; + uint32_t output_buffer_limit; + + bool trace_bytes; + bool reuse_addr; + int backlog; + + // 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); + +// Set input and output buffer size limit for any +// given connection. The default value is 1MB +void http_server_set_input_limit(HTTP_Server *server, uint32_t limit); +void http_server_set_output_limit(HTTP_Server *server, uint32_t limit); + +// TODO: Comment +void http_server_set_trace_bytes(HTTP_Server *server, bool value); + +// TODO: Comment +void http_server_set_reuse_addr(HTTP_Server *server, bool reuse); + +// TODO: comment +void http_server_set_backlog(HTTP_Server *server, int backlog); + +// Enable listening for plain HTTP requests at the +// specified interface. +int http_server_listen_tcp(HTTP_Server *server, + HTTP_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, HTTP_String addr, Port port, + HTTP_String cert_file_name, HTTP_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, + HTTP_String domain, HTTP_String cert_file, HTTP_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); + +// Resets the event register with the list of descriptors +// the server wants monitored. Returns 0 on success, -1 if +// the event register's capacity isn't large enough. +int http_server_register_events(HTTP_Server *server, + EventRegister *reg); + +// 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, + EventRegister *reg); + +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, HTTP_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, HTTP_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); + +//////////////////////////////////////////////////////////////////////////////////////// +// 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/web/main.c b/web/main.c index 6a3d8b1..130b0ae 100644 --- a/web/main.c +++ b/web/main.c @@ -1,24 +1,37 @@ -#include -#include - -#include #include -#define MAX_WAITING (1<<10) -#define MAX_PENDING (1<<9) +#include "chttp.h" + +#ifdef _WIN32 +#define POLL WSAPoll +#else +#define POLL poll +#endif + +#define UNREACHABLE __builtin_trap() typedef enum { - OPERATION_FREE, - OPERATION_CREATE_DIR, - OPERATION_CREATE_FILE, - OPERATION_DELETE, - OPERATION_READ_DIR, - OPERATION_READ_FILE, - OPERATION_WRITE, -} OperationType; + PROXIED_OPERATION_FREE, + PROXIED_OPERATION_CREATE_DIR, + PROXIED_OPERATION_CREATE_FILE, + PROXIED_OPERATION_DELETE, + PROXIED_OPERATION_READ_DIR, + PROXIED_OPERATION_READ_FILE, + PROXIED_OPERATION_WRITE, +} ProxiedOperationType; typedef struct { - OperationType type; + ProxiedOperationType type; + + // Don't write the content to the response + // when reading a file or directory. + bool head_only; + + // Offset of the read/write + int offset; + + // Length of the read/write + int length; // Number of bytes read/written int transferred; @@ -27,291 +40,303 @@ typedef struct { HTTP_ResponseBuilder builder; ToastyHandle handle; -} Operation; -static int waiting_head; -static int num_waiting; -static int num_pending; -static Operation waiting[MAX_WAITING]; -static Operation pending[MAX_PENDING]; +} ProxiedOperation; -void worker(Worker *w) +#define MAX_PROXIED_OPERATIONS (1<<10) + +static int find_unused_struct(ProxiedOperation *arr, int num) { - for (;;) { - - ToastyResult result; - int ret = toasty_wait_result(toasty, TOASTY_INVALID, &result, -1); - // TODO: check return value - - // First, process completed requests. This frees up - // space for new ones. - // - // TODO: What if there was a WAKEUP request and no pending operations - // are present? - - int i = 0; - while (pending[i].handle != result.handle) { - i++; - assert(i < MAX_PENDING_OPERATIONS); - } - - bool incomplete = false; - switch (pending[i].type) { - - int cap; - int mincap; - char *dst; - ToastyString path; - - case OPERATION_CREATE_DIR: - case OPERATION_CREATE_FILE: - if (result.type == TOASTY_RESULT_CREATE_SUCCESS) { - http_response_builder_status(pending[i].builder, 204); // TODO: What is the proper response to a CREATE request? - http_response_builder_done(pending[i].builder); - } else { - http_response_builder_undo(pending[i].builder); - http_response_builder_status(pending[i].builder, 500); - http_response_builder_done(pending[i].builder); - } - break; - - case OPERATION_DELETE: - if (result.type == TOASTY_RESULT_DELETE_SUCCESS) { - http_response_builder_status(pending[i].builder, 204); // TODO: What is the proper response to a DELETE request? - http_response_builder_done(pending[i].builder); - } else { - http_response_builder_undo(pending[i].builder); - http_response_builder_status(pending[i].builder, 500); - http_response_builder_done(pending[i].builder); - } - break; - - case OPERATION_READ_DIR: - - // If the listing failed, abort the request - if (result.type != TOASTY_RESULT_READ_SUCCESS) { - http_response_builder_status(pending[i].builder, 500); - http_response_builder_done(pending[i].builder); - break; - } - - http_response_builder_status(pending[i].builder, 500); - for (int i = 0; i < result.listing.count; i++) - http_response_builder_body(pending[i].builder, (HTTP_String) { - result.listing.items[i].name, - result.listing.items[i].name_len - }); - http_response_builder_done(pending[i].builder); - - toasty_free_listing(&result.listing); - break; - - case OPERATION_READ_FILE: - - // If the read failed, abort the request - if (result.type != TOASTY_RESULT_READ_SUCCESS) { - http_response_builder_bodyack(pending[i].builder, 0); - http_response_builder_undo(pending[i].builder); - http_response_builder_status(pending[i].builder, 500); - http_response_builder_done(pending[i].builder); - break; - } - - // First, ACK the byte we just read, even if it's - // just 0 bytes (every bodybuf must by paired with - // a bodyack). - http_response_builder_bodyack(pending[i].builder, result.count); - pending[i].transferred += result.count; - - // If we read 0 bytes, there is no more to read. - if (result.count == 0) { - http_response_builder_done(pending[i].builder); - break; - } - - // There is more to read, so we need to start a - // new read. - - // Make sure there is some free space in the buffer - mincap = 1<<10; // TODO: Choose based on overall file size - http_response_builder_bodycap(pending[i].builder, mincap); - - // Get the location of that buffer - dst = http_response_builder_bodybuf(pending[i].builder, &cap); - if (dst == NULL) { - http_response_builder_done(pending[i].builder); - break; - } - - // Begin the read. On error, abort. - path = (ToastyString) { pending[i].request->path.ptr, pending[i].request.path.len }; - pending[i].handle = toasty_begin_read(toasty, path, off, dst, cap); - if (pending[i].handle == TOASTY_INVALID) { - http_response_builder_undo(pending[i].builder); - http_response_builder_status(pending[i].builder, 500); - http_response_builder_done(pending[i].builder); - break; - } - - // Most of the time this switch will complete the pending - // operation, but not for reads. - incomplete = true; - break; - - case OPERATION_WRITE: - if (pending[i].type != TOASTY_RESULT_WRITE_SUCCESS) { - http_response_builder_undo(pending[i].builder); - http_response_builder_status(pending[i].builder, 500); - http_response_builder_done(pending[i].builder); - break; - } - assert(pending[i].transferred == result.count); - http_response_builder_done(pending[i].builder); - break; - - default: - assert(0); // TODO - } - - if (!incomplete) { - // Free the pending structure - pending[i].type = OPERATION_FREE; - num_pending--; - } - - // Now accept operations - for (Operation req; num_pending < MAX_PENDING_OPERATIONS - && wait_queue_pop(&wait_queue, &req); ) { - - int i = 0; - while (pending[i].type != PENDING_OPERATION_FREE) { - i++; - assert(i < MAX_PENDING_OPERATIONS); - } - pending[i] = req; - - ToastyString path = { pending[i].request->path.ptr, pending[i].request.path.len }; - ToastyString body = { pending[i].request->body.ptr, pending[i].request.body.len }; - switch (pending[i].type) { - int cap; - int mincap; - char *dst; - uint32_t chunk_size; - case OPERATION_CREATE_DIR, - pending[i].handle = toasty_begin_create_dir(toasty, path); - break; - case OPERATION_CREATE_FILE, - chunk_size = 1<<10; // TODO: determine a better chunk size - pending[i].handle = toasty_begin_create_file(toasty, path, chunk_size); - break; - case OPERATION_DELETE, - pending[i].handle = toasty_begin_delete(toasty, path); - break; - case OPERATION_READ, - http_response_builder_status(pending[i].builder, 200); // TODO: Is statis 200 correct? - - mincap = 1<<10; // TODO: do something smart to choose this - http_response_builder_bodycap(pending[i].builder, mincap); - - dst = http_response_builder_bodybuf(pending[i].builder, &cap); - if (dst == NULL) - break; - - pending[i].handle = toasty_begin_read(toasty, path, off, dst, cap); - pending[i].transferred = 0; - break; - case OPERATION_WRITE: - pending[i].handle = toasty_begin_write(toasty, body.ptr, body.len); - pending[i].transferred = 0; - break; - } - - if (pending[i].handle == TOASTY_INVALID) { - http_response_builder_undo(pending[i].builder); - http_response_builder_status(pending[i].builder, 500); - http_response_builder_done(pending[i].builder); - pending[i].type = OPERATION_FREE; - } - num_pending++; - } + if (num == MAX_PROXIED_OPERATIONS) + return -1; + int i = 0; + while (arr[i].type != PROXIED_OPERATION_FREE) { + i++; + assert(i < MAX_PROXIED_OPERATIONS); } + return i; } int main(void) { - http_global_init(); + ToastyString upstream_addr = TOASTY_STR("127.0.0.1"); + uint16_t upstream_port = 9000; - HTTP_String addr = HTTP_STR("127.0.0.1"); - uint16_t port = 8080; + HTTP_String local_addr = HTTP_STR("127.0.0.1"); + uint16_t local_port = 8080; - ToastyString backend_addr = TOASTY_STR("127.0.0.1"); - uint16_t backend_port = 9000; - - HTTP_Server *server = http_server_init(addr, port); - if (server == NULL) - return -1; - - ToastyFS *toasty = toasty_connect(backend_addr, backend_port); + ToastyFS *toasty = toasty_connect(upstream_addr, upstream_port); if (toasty == NULL) return -1; + HTTP_Server server; + if (http_server_init(&server) < 0) + return -1; + + http_server_set_reuse_addr(&server, true); + http_server_set_trace_bytes(&server, true); + + if (http_server_listen_tcp(&server, local_addr, local_port) < 0) + return -1; + + int num_proxied = 0; + ProxiedOperation proxied[MAX_PROXIED_OPERATIONS]; + for (;;) { + #define HTTP_SERVER_POLL_CAPACITY (HTTP_SERVER_CAPACITY+3) + #define TOASTY_POLL_CAPACITY (MAX_CONNS+1) + + #define POLL_CAPACITY (HTTP_SERVER_POLL_CAPACITY + TOASTY_POLL_CAPACITY) + + EventRegister reg; + void *ptrs[POLL_CAPACITY]; + struct pollfd polled[POLL_CAPACITY]; + + void **http_ptrs = ptrs; + struct pollfd *http_polled = polled; + + reg = (EventRegister) { + ptrs, + polled, + POLL_CAPACITY, + 0 + }; + if (http_server_register_events(&server, ®) < 0) + return -1; + int num_http_polled = reg.num_polled; + + void **toasty_ptrs = ptrs + num_http_polled; + struct pollfd *toasty_polled = polled + num_http_polled; + int num_toasty_polled = toasty_process_events(toasty, toasty_ptrs, toasty_polled, 0); + if (num_toasty_polled < 0) + return -1; + + int num_polled = num_http_polled + num_toasty_polled; + if (num_polled > 0) + POLL(polled, num_polled, -1); + + // First, process toasty events so that we free space + // for incoming requests + + if (toasty_process_events(toasty, toasty_ptrs, toasty_polled, num_toasty_polled) < 0) + return -1; + + for (;;) { + + ToastyResult result; + int ret = toasty_get_result(toasty, TOASTY_INVALID, &result); + + if (ret == 1) + break; // No completion + + if (ret < 0) + return -1; // Error + + // Completed + assert(ret == 0); + + // Find the operation associated to this completion + int i = 0; + while (proxied[i].handle != result.handle) { + i++; + assert(i < MAX_PROXIED_OPERATIONS); + } + + switch (proxied[i].type) { + case PROXIED_OPERATION_CREATE_DIR: + case PROXIED_OPERATION_CREATE_FILE: + { + if (result.type == TOASTY_RESULT_CREATE_SUCCESS) { + http_response_builder_status(proxied[i].builder, 201); // Created + http_response_builder_send(proxied[i].builder); + } else { + // TODO: Should differentiate between error conditions + http_response_builder_status(proxied[i].builder, 500); // Internal Server Error + http_response_builder_send(proxied[i].builder); + } + proxied[i].type = PROXIED_OPERATION_FREE; + num_proxied--; + } + break; + case PROXIED_OPERATION_DELETE: + { + if (result.type == TOASTY_RESULT_DELETE_SUCCESS) { + http_response_builder_status(proxied[i].builder, 204); // No Content + http_response_builder_send(proxied[i].builder); + } else { + // TODO: Should differentiate between error conditions + http_response_builder_status(proxied[i].builder, 500); // Internal Server Error + http_response_builder_send(proxied[i].builder); + } + proxied[i].type = PROXIED_OPERATION_FREE; + num_proxied--; + } + break; + case PROXIED_OPERATION_READ_DIR: + { + if (result.type == TOASTY_RESULT_LIST_SUCCESS) { + http_response_builder_status(proxied[i].builder, 200); + for (int i = 0; i < result.listing.count; i++) + http_response_builder_body(proxied[i].builder, (HTTP_String) { + result.listing.items[i].name, + result.listing.items[i].name_len + }); + http_response_builder_send(proxied[i].builder); + toasty_free_listing(&result.listing); + } else { + // TODO: Should differentiate between error conditions + http_response_builder_status(proxied[i].builder, 500); // Internal Server Error + http_response_builder_send(proxied[i].builder); + } + proxied[i].type = PROXIED_OPERATION_FREE; + num_proxied--; + } + break; + case PROXIED_OPERATION_READ_FILE: + { + bool again = false; + if (result.type != TOASTY_RESULT_READ_SUCCESS) { + http_response_builder_bodyack(proxied[i].builder, 0); + http_response_builder_status(proxied[i].builder, 500); + http_response_builder_send(proxied[i].builder); + } else { + + // First, ACK the byte we just read, even if it's + // just 0 bytes (every bodybuf must by paired with + // a bodyack). + proxied[i].transferred += result.count; + int ack = proxied[i].head_only ? 0 : result.count; + http_response_builder_bodyack(proxied[i].builder, ack); + + // If we didn't reach the end of the file, start + // a new read. + if (result.count > 0) { + + // Make sure there is some free space in the buffer + int mincap = 1<<10; // TODO: Choose based on overall file size + http_response_builder_bodycap(proxied[i].builder, mincap); + + // Get the location of that buffer + int cap; + char *dst = http_response_builder_bodybuf(proxied[i].builder, &cap); + if (dst == NULL) { + assert(0); // TODO + } + + proxied[i].handle = toasty_begin_read(toasty, path, off, dst, cap); + if (proxied[i].handle == TOASTY_INVALID) { + assert(0); // TODO + } + + again = true; + } + } + + if (!again) { + proxied[i].type = PROXIED_OPERATION_FREE; + num_proxied--; + } + } + break; + case PROXIED_OPERATION_WRITE: + { + if (result.type == TOASTY_RESULT_WRITE_SUCCESS) { + http_response_builder_status(proxied[i].builder, 201); // Created + http_response_builder_send(proxied[i].builder); + } else { + // TODO: Should differentiate between error conditions + http_response_builder_status(proxied[i].builder, 500); // Internal Server Error + http_response_builder_send(proxied[i].builder); + } + proxied[i].type = PROXIED_OPERATION_FREE; + num_proxied--; + } + break; + default: + UNREACHABLE; + break; + } + } + + reg = (EventRegister) { http_ptrs, http_polled, HTTP_SERVER_POLL_CAPACITY, num_http_polled }; + if (http_server_process_events(&server, ®) < 0) + return -1; + HTTP_Request *request; HTTP_ResponseBuilder builder; - int ret = http_server_wait(server, &req, &builder); - if (ret < 0) - return -1; - - ToastyString path = { - req->url.path.ptr, - req->url.path.len - }; - - Operation op; - op.type = OPERATION_FREE; - - switch (req->method) { + if (http_server_next_request(&server, &request, &builder)) { + switch (request->method) { case HTTP_METHOD_GET: - // TODO - break; - + http_response_builder_status(builder, 501); // Not Implemented + http_response_builder_send(builder); + break; case HTTP_METHOD_HEAD: - // TODO - break; - + http_response_builder_status(builder, 501); // Not Implemented + http_response_builder_send(builder); + break; case HTTP_METHOD_PUT: - // TODO - break; + { + int i = find_unused_struct(proxied, num_proxied); + if (i < 0) { + http_response_builder_status(builder, 503); // Service Unavailable + http_response_builder_send(builder); + break; + } - case HTTP_METHOD_PATCH: - // TODO - break; + ToastyHandle handle = toasty_begin_write(toasty, path, 0, request->body.ptr, request->body.len); + if (handle == TOASTY_INVALID) { + http_response_builder_status(builder, 500); // Internal Server Error + http_response_builder_send(builder); + break; + } + proxied[num_proxied].type = PROXIED_OPERATION_WRITE; + proxied[num_proxied].request = request; + proxied[num_proxied].builder = builder; + proxied[num_proxied].handle = handle; + num_proxied++; + } + break; case HTTP_METHOD_DELETE: - // TODO - break; + { + int i = find_unused_struct(proxied, num_proxied); + if (i < 0) { + http_response_builder_status(builder, 503); // Service Unavailable + http_response_builder_send(builder); + break; + } + ToastyHandle handle = toasty_begin_delete(toasty, path); + if (handle == TOASTY_INVALID) { + http_response_builder_status(builder, 500); // Internal Server Error + http_response_builder_send(builder); + break; + } + + proxied[num_proxied].type = PROXIED_OPERATION_DELETE; + proxied[num_proxied].request = request; + proxied[num_proxied].builder = builder; + proxied[num_proxied].handle = handle; + num_proxied++; + } + break; case HTTP_METHOD_OPTIONS: - http_response_builder_status(builder, 200); - http_response_builder_header(builder, HTTP_STR("Allow: GET, HEAD, PUT, PATCH, DELETE, OPTIONS")); - http_response_builder_done(builder); - break; - + http_response_builder_status(builder, 200); // OK + http_response_builder_header(builder, HTTP_STR("Allow: GET, HEAD, PUT, DELETE, OPTIONS")); + http_response_builder_send(builder); + break; default: - http_response_builder_status(builder, 200); // TODO: use the status code for invalid methods - http_response_builder_header(builder, HTTP_STR("Allow: GET, HEAD, PUT, PATCH, DELETE, OPTIONS")); - http_response_builder_done(builder); - break; - } - - if (op.type != OPERATION_FREE) { - assert(0); // TODO: Append to the queue + http_response_builder_status(builder, 405); // Method not allowed + http_response_builder_header(builder, HTTP_STR("Allow: GET, HEAD, PUT, DELETE, OPTIONS")); + http_response_builder_send(builder); + break; + } } } - http_server_free(server); - http_global_free(); + http_server_free(&server); + toasty_disconnect(toasty); return 0; }