diff --git a/.gitignore b/.gitignore index aeb6744..be81331 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ coverage_report *.out *.exe +*.o +*.a +*.so \ No newline at end of file diff --git a/Makefile b/Makefile index ee11970..1666ff9 100644 --- a/Makefile +++ b/Makefile @@ -2,17 +2,30 @@ CC = gcc CFLAGS = -I. -Wall -Wextra -O0 -g3 LFLAGS = -lssl -lcrypto +AR = ar CFILES = $(shell find src -name "*.c") HFILES = $(shell find src -name "*.h") +OFILES = $(patsubst %.c,%.o,$(CFILES)) + +# Library names +LIBNAME = chttp +STATIC_LIB = lib$(LIBNAME).a +SHARED_LIB = lib$(LIBNAME).so # Detect OS and set executable extension ifeq ($(OS),Windows_NT) EXT = .exe + SHARED_LIB = $(LIBNAME).dll else EXT = .out endif +# Installation directories +PREFIX ?= /usr/local +LIBDIR = $(PREFIX)/lib +INCDIR = $(PREFIX)/include + EXAMPLES_CLIENT_SRC := $(shell ls examples/client/*.c 2>/dev/null) EXAMPLES_SERVER_SRC := $(shell ls examples/server/*.c 2>/dev/null) EXAMPLES_ENGINE_SRC := $(shell ls examples/engine/*.c 2>/dev/null) @@ -21,11 +34,25 @@ EXAMPLES_CLIENT := $(patsubst %.c,%$(EXT),$(EXAMPLES_CLIENT_SRC)) EXAMPLES_SERVER := $(patsubst %.c,%$(EXT),$(EXAMPLES_SERVER_SRC)) EXAMPLES_ENGINE := $(patsubst %.c,%$(EXT),$(EXAMPLES_ENGINE_SRC)) -all: chttp.c chttp.h examples +all: chttp.c chttp.h examples lib + +lib: $(STATIC_LIB) $(SHARED_LIB) chttp.c chttp.h: $(HFILES) $(CFILES) python misc/amalg.py +# Object files from source files +%.o: %.c $(HFILES) + $(CC) $(CFLAGS) -c $< -o $@ + +# Static library +$(STATIC_LIB): $(OFILES) + $(AR) rcs $@ $^ + +# Shared library +$(SHARED_LIB): $(OFILES) + $(CC) -shared -o $@ $^ $(LFLAGS) + examples: $(EXAMPLES_CLIENT) $(EXAMPLES_SERVER) $(EXAMPLES_ENGINE) examples/client/%$(EXT): examples/client/%.c chttp.c chttp.h @@ -37,6 +64,28 @@ examples/server/%$(EXT): examples/server/%.c chttp.c chttp.h examples/engine/%$(EXT): examples/engine/%.c chttp.c chttp.h $(CC) $(CFLAGS) $< chttp.c -o $@ $(LFLAGS) +# Installation targets +install: install-lib install-headers + +install-lib: $(STATIC_LIB) $(SHARED_LIB) + install -d $(LIBDIR) + install -m 644 $(STATIC_LIB) $(LIBDIR)/ + install -m 755 $(SHARED_LIB) $(LIBDIR)/ + +install-headers: chttp.h + install -d $(INCDIR) + install -m 644 chttp.h $(INCDIR)/ + +uninstall: + rm -f $(LIBDIR)/$(STATIC_LIB) + rm -f $(LIBDIR)/$(SHARED_LIB) + rm -f $(INCDIR)/chttp.h + clean: rm -f client_example server_example rm -f examples/client/*$(EXT) examples/server/*$(EXT) examples/engine/*$(EXT) + rm -f $(OFILES) + rm -f $(STATIC_LIB) $(SHARED_LIB) + rm -f chttp.c chttp.h + +.PHONY: all lib examples install install-lib install-headers uninstall clean diff --git a/chttp.c b/chttp.c index 5fec3ac..0820f1d 100644 --- a/chttp.c +++ b/chttp.c @@ -9,15 +9,6 @@ #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include #include @@ -27,13 +18,17 @@ #include #include #include -#include #include ////////////////////////////////////////////////////////////////////// // src/cert.h ////////////////////////////////////////////////////////////////////// +#ifndef CERT_INCLUDED +#define CERT_INCLUDED + +#include "basic.h" + // This is an utility to create self-signed certificates // useful when testing HTTPS servers locally. This is only // meant to be used by people starting out with a library @@ -51,10 +46,12 @@ 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/socket.h ////////////////////////////////////////////////////////////////////// +#ifndef SOCKET_INCLUDED +#define SOCKET_INCLUDED // This is a socket abstraction module for non-blocking TCP and TLS sockets. // // Sockets may be in a number of states based on if they are plain TCP or TLS @@ -108,7 +105,13 @@ int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, // which means the user needs to call socket_free to free the socket // as it's not unusable. +#ifdef HTTPS_ENABLED +#include +#include +#include +#endif +#include "parse.h" typedef struct { int is_ipv6; @@ -139,8 +142,10 @@ typedef struct { SocketState state; SocketWantEvent event; int fd; +#if HTTPS_ENABLED SSL *ssl; SSL_CTX *ssl_ctx; +#endif AddrInfo *addr_list; int addr_count; int addr_cursor; @@ -148,16 +153,20 @@ typedef struct { uint16_t port; } Socket; +#ifdef HTTPS_ENABLED typedef struct { char name[128]; SSL_CTX *ssl_ctx; } Domain; +#endif typedef struct { +#ifdef HTTPS_ENABLED SSL_CTX *ssl_ctx; int num_domains; int max_domains; Domain *domains; +#endif } SocketGroup; void socket_global_init (void); @@ -178,7 +187,7 @@ void socket_close (Socket *sock); void socket_free (Socket *sock); int socket_wait (Socket **socks, int num_socks); -////////////////////////////////////////////////////////////////////// +#endif // SOCKET_INCLUDED////////////////////////////////////////////////////////////////////// // src/basic.c ////////////////////////////////////////////////////////////////////// @@ -2296,21 +2305,40 @@ void http_engine_undo(HTTP_Engine *eng) // src/socket.c ////////////////////////////////////////////////////////////////////// +#ifdef HTTPS_ENABLED +#include +#include +#include +#include +#include +#include +#endif +#ifdef _WIN32 +#include +#endif +#ifdef __linux__ +#include +#endif void socket_global_init(void) { +#ifdef HTTPS_ENABLED SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); +#endif } void socket_global_free(void) { +#ifdef HTTPS_ENABLED EVP_cleanup(); ERR_free_strings(); +#endif } int socket_group_init(SocketGroup *group) { +#ifdef HTTPS_ENABLED SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_client_method()); if (!ssl_ctx) { fprintf(stderr, "Unable to create SSL context\n"); @@ -2336,9 +2364,13 @@ int socket_group_init(SocketGroup *group) group->domains = NULL; group->num_domains = 0; group->max_domains = 0; +#else + (void) group; +#endif return 0; } +#ifdef HTTPS_ENABLED static int servername_callback(SSL *ssl, int *ad, void *arg) { SocketGroup *group = arg; @@ -2357,9 +2389,11 @@ static int servername_callback(SSL *ssl, int *ad, void *arg) return SSL_TLSEXT_ERR_NOACK; } +#endif int socket_group_init_server(SocketGroup *group, HTTP_String cert_file, HTTP_String key_file) { +#ifdef HTTPS_ENABLED SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_server_method()); if (!ssl_ctx) { fprintf(stderr, "Unable to create server SSL context\n"); @@ -2420,16 +2454,27 @@ int socket_group_init_server(SocketGroup *group, HTTP_String cert_file, HTTP_Str group->domains = NULL; group->num_domains = 0; group->max_domains = 0; +#else + (void) group; + if (cert_file.len > 0 || key_file.len > 0) + return -1; +#endif + return 0; } void socket_group_free(SocketGroup *group) { +#ifdef HTTPS_ENABLED SSL_CTX_free(group->ssl_ctx); +#else + (void) group; +#endif } int socket_group_add_domain(SocketGroup *group, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) { +#ifdef HTTPS_ENABLED if (group->num_domains == group->max_domains) { int new_max_domains = 2 * group->max_domains; @@ -2513,6 +2558,13 @@ int socket_group_add_domain(SocketGroup *group, HTTP_String domain, HTTP_String domain_info->ssl_ctx = ssl_ctx; group->num_domains++; return 0; +#else + (void) group; + (void) domain; + (void) cert_file; + (void) key_file; + return -1; +#endif } SocketState socket_state(Socket *sock) @@ -2522,12 +2574,20 @@ SocketState socket_state(Socket *sock) void socket_accept(Socket *sock, SocketGroup *group, int fd) { +#ifdef HTTPS_ENABLED + sock->ssl = NULL; + sock->ssl_ctx = group ? group->ssl_ctx : NULL; +#else + if (group) { + sock->state = SOCKET_STATE_DIED; + return; + } +#endif + // Initialize socket for server-side TLS handshake sock->state = SOCKET_STATE_ACCEPTED; // TCP connection already established sock->event = SOCKET_WANT_NONE; sock->fd = fd; - sock->ssl = NULL; - sock->ssl_ctx = group ? group->ssl_ctx : NULL; sock->addr_list = NULL; sock->addr_count = 0; sock->addr_cursor = 0; @@ -2535,7 +2595,7 @@ void socket_accept(Socket *sock, SocketGroup *group, int fd) sock->port = 0; // Set non-blocking mode for the accepted socket - int flags = fcntl(fd, F_GETFL, 0); + int flags = fcntl(fd, F_GETFL, 0); // TODO: fail on error if (flags >= 0) { fcntl(fd, F_SETFL, flags | O_NONBLOCK); } @@ -2544,12 +2604,20 @@ void socket_accept(Socket *sock, SocketGroup *group, int fd) socket_update(sock); } -void socket_connect(Socket *sock, SocketGroup *group, HTTP_String host, uint16_t port) { +void socket_connect(Socket *sock, SocketGroup *group, HTTP_String host, uint16_t port) +{ +#ifdef HTTPS_ENABLED + sock->ssl = NULL; + sock->ssl_ctx = group ? group->ssl_ctx : NULL; +#else + if (group) { + sock->state = SOCKET_STATE_DIED; + return; + } +#endif sock->state = SOCKET_STATE_PENDING; sock->event = SOCKET_WANT_NONE; sock->fd = -1; - sock->ssl = NULL; - sock->ssl_ctx = group ? group->ssl_ctx : NULL; sock->addr_list = NULL; sock->addr_count = 0; sock->addr_cursor = 0; @@ -2599,12 +2667,20 @@ void socket_connect(Socket *sock, SocketGroup *group, HTTP_String host, uint16_t socket_update(sock); } -void socket_connect_ipv4(Socket *sock, SocketGroup *group, HTTP_IPv4 addr, uint16_t port) { +void socket_connect_ipv4(Socket *sock, SocketGroup *group, HTTP_IPv4 addr, uint16_t port) +{ +#ifdef HTTPS_ENABLED + sock->ssl = NULL; + sock->ssl_ctx = group ? group->ssl_ctx : NULL; +#else + if (group) { + sock->state = SOCKET_STATE_DIED; + return; + } +#endif sock->state = SOCKET_STATE_PENDING; sock->event = SOCKET_WANT_NONE; sock->fd = -1; - sock->ssl = NULL; - sock->ssl_ctx = group ? group->ssl_ctx : NULL; sock->addr_list = NULL; sock->addr_count = 0; sock->addr_cursor = 0; @@ -2620,12 +2696,20 @@ void socket_connect_ipv4(Socket *sock, SocketGroup *group, HTTP_IPv4 addr, uint1 socket_update(sock); } -void socket_connect_ipv6(Socket *sock, SocketGroup *group, HTTP_IPv6 addr, uint16_t port) { +void socket_connect_ipv6(Socket *sock, SocketGroup *group, HTTP_IPv6 addr, uint16_t port) +{ +#ifdef HTTPS_ENABLED + sock->ssl = NULL; + sock->ssl_ctx = group ? group->ssl_ctx : NULL; +#else + if (group) { + sock->state = SOCKET_STATE_DIED; + return; + } +#endif sock->state = SOCKET_STATE_PENDING; sock->event = SOCKET_WANT_NONE; sock->fd = -1; - sock->ssl = NULL; - sock->ssl_ctx = group ? group->ssl_ctx : NULL; sock->addr_list = NULL; sock->addr_count = 0; sock->addr_cursor = 0; @@ -2641,6 +2725,16 @@ void socket_connect_ipv6(Socket *sock, SocketGroup *group, HTTP_IPv6 addr, uint1 socket_update(sock); } +bool socket_secure(Socket *sock) +{ +#ifdef HTTPS_ENABLED + return sock->ssl_ctx != NULL; +#else + (void) sock; + return false; +#endif +} + void socket_update(Socket *sock) { sock->event = SOCKET_WANT_NONE; @@ -2653,10 +2747,12 @@ void socket_update(Socket *sock) switch (sock->state) { case SOCKET_STATE_PENDING: { +#ifdef HTTPS_ENABLED if (sock->ssl) { SSL_free(sock->ssl); sock->ssl = NULL; } +#endif if (sock->fd != -1) close(sock->fd); @@ -2762,11 +2858,11 @@ void socket_update(Socket *sock) case SOCKET_STATE_CONNECTED: { - if (sock->ssl_ctx == NULL) { + if (!socket_secure(sock)) { sock->event = SOCKET_WANT_NONE; sock->state = SOCKET_STATE_ESTABLISHED_READY; } else { - +#ifdef HTTPS_ENABLED // Start SSL handshake if (!sock->ssl) { sock->ssl = SSL_new(sock->ssl_ctx); @@ -2798,17 +2894,20 @@ void socket_update(Socket *sock) sock->event = SOCKET_WANT_NONE; sock->state = SOCKET_STATE_PENDING; again = true; +#else + HTTP_ASSERT(0); +#endif } } break; case SOCKET_STATE_ACCEPTED: { - if (sock->ssl_ctx == NULL) { + if (!socket_secure(sock)) { sock->event = SOCKET_WANT_NONE; sock->state = SOCKET_STATE_ESTABLISHED_READY; } else { - +#ifdef HTTPS_ENABLED // Start server-side SSL handshake if (!sock->ssl) { sock->ssl = SSL_new(sock->ssl_ctx); @@ -2837,6 +2936,9 @@ void socket_update(Socket *sock) // Server socket error - close the connection sock->event = SOCKET_WANT_NONE; sock->state = SOCKET_STATE_DIED; +#else + HTTP_ASSERT(0); +#endif } } break; @@ -2850,11 +2952,11 @@ void socket_update(Socket *sock) case SOCKET_STATE_SHUTDOWN: { - if (sock->ssl_ctx == NULL) { + if (!socket_secure(sock)) { sock->event = SOCKET_WANT_NONE; sock->state = SOCKET_STATE_DIED; } else { - +#ifdef HTTPS_ENABLED int ret = SSL_shutdown(sock->ssl); if (ret == 1) { sock->event = SOCKET_WANT_NONE; @@ -2875,6 +2977,9 @@ void socket_update(Socket *sock) sock->event = SOCKET_WANT_NONE; sock->state = SOCKET_STATE_DIED; +#else + HTTP_ASSERT(0); +#endif } } break; @@ -2895,7 +3000,7 @@ int socket_read(Socket *sock, char *dst, int max) { return -1; } - if (sock->ssl_ctx == NULL) { + if (!socket_secure(sock)) { int ret = read(sock->fd, dst, max); if (ret == 0) { sock->event = SOCKET_WANT_NONE; @@ -2916,6 +3021,7 @@ int socket_read(Socket *sock, char *dst, int max) { } 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); @@ -2934,6 +3040,10 @@ int socket_read(Socket *sock, char *dst, int max) { ret = 0; } return ret; +#else + HTTP_ASSERT(0); + return -1; +#endif } } @@ -2945,7 +3055,7 @@ int socket_write(Socket *sock, char *src, int len) { return 0; } - if (sock->ssl_ctx == NULL) { + if (!socket_secure(sock)) { int ret = write(sock->fd, src, len); if (ret < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { @@ -2961,6 +3071,7 @@ int socket_write(Socket *sock, char *src, int len) { } 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); @@ -2979,6 +3090,9 @@ int socket_write(Socket *sock, char *src, int len) { ret = 0; } return ret; +#else + HTTP_ASSERT(0); +#endif } } @@ -2989,20 +3103,23 @@ void socket_close(Socket *sock) { socket_update(sock); } -void socket_free(Socket *sock) { - // Release all resources associated to the socket - if (sock->ssl) { +void socket_free(Socket *sock) +{ +#ifdef HTTPS_ENABLED + if (sock->ssl) SSL_free(sock->ssl); - sock->ssl = NULL; - } +#endif + if (sock->fd >= 0) { close(sock->fd); sock->fd = -1; } + if (sock->hostname) { free(sock->hostname); sock->hostname = NULL; } + if (sock->addr_list) { free(sock->addr_list); sock->addr_list = NULL; @@ -3503,6 +3620,164 @@ HTTP_Response *http_post(HTTP_String url, HTTP_String *headers, int num_headers, *phandle = handle; return res; }////////////////////////////////////////////////////////////////////// +// src/cert.c +////////////////////////////////////////////////////////////////////// + +#ifdef HTTPS_ENABLED +#include +#include +#include +#include +#include +#include +#endif +#ifdef HTTPS_ENABLED + +static EVP_PKEY *generate_rsa_key_pair(int key_bits) +{ + EVP_PKEY_CTX *ctx; + EVP_PKEY *pkey; + + // Create the context for key generation + ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (!ctx) + return NULL; + + if (EVP_PKEY_keygen_init(ctx) <= 0) { + EVP_PKEY_CTX_free(ctx); + return NULL; + } + + if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, key_bits) <= 0) { + EVP_PKEY_CTX_free(ctx); + return NULL; + } + + if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { + EVP_PKEY_CTX_free(ctx); + return NULL; + } + + EVP_PKEY_CTX_free(ctx); + return pkey; +} + +static X509 *create_certificate(EVP_PKEY *pkey, HTTP_String C, HTTP_String O, HTTP_String CN, int days) +{ + X509 *x509 = X509_new(); + if (!x509) + return NULL; + + // Set version (version 3) + X509_set_version(x509, 2); + + // Set serial number + ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); + + // Set validity period + X509_gmtime_adj(X509_get_notBefore(x509), 0); + X509_gmtime_adj(X509_get_notAfter(x509), 31536000L * days); // days * seconds_per_year + + // Set public key + X509_set_pubkey(x509, pkey); + + // Set subject name + X509_NAME *name = X509_get_subject_name(x509); + X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char*) C.ptr, C.len, -1, 0); + X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char*) O.ptr, O.len, -1, 0); + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*) CN.ptr, CN.len, -1, 0); + + // Set issuer name (same as subject for self-signed) + X509_set_issuer_name(x509, name); + + if (!X509_sign(x509, pkey, EVP_sha256())) { + X509_free(x509); + return NULL; + } + + return x509; +} + +static int save_private_key(EVP_PKEY *pkey, const char *filename) +{ + FILE *fp = fopen(filename, "wb"); + if (!fp) + return -1; + + // Write private key in PEM format + if (!PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL)) { + fclose(fp); + return -1; + } + + fclose(fp); + return 0; +} + +static int cert_save(X509 *x509, const char *filename) +{ + FILE *fp = fopen(filename, "wb"); + if (!fp) { + fprintf(stderr, "Error opening file for certificate: %s\n", filename); + return -1; + } + + // Write certificate in PEM format + if (!PEM_write_X509(fp, x509)) { + fprintf(stderr, "Error writing certificate\n"); + fclose(fp); + return -1; + } + + fclose(fp); + printf("Certificate saved to: %s\n", filename); + return 0; +} + +int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, + HTTP_String cert_file, HTTP_String key_file) +{ + EVP_PKEY *pkey = generate_rsa_key_pair(2048); + if (pkey == NULL) + return -1; + + X509 *x509 = create_certificate(pkey, C, O, CN, 1) + if (x509 == NULL) { + EVP_PKEY_free(pkey); + return -1; + } + + if (save_private_key(pkey, key_file) < 0) { + X509_free(x509); + EVP_PKEY_free(pkey); + return -1; + } + + if (save_certificate(x509, cert_file) < 0) { + X509_free(x509); + EVP_PKEY_free(pkey); + return -1; + } + + X509_free(x509); + EVP_PKEY_free(pkey); + return 0; +} + +#else + +int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, + HTTP_String cert_file, HTTP_String key_file) +{ + (void) C; + (void) O; + (void) CN; + (void) cert_file; + (void) key_file; + return -1; +} + +#endif////////////////////////////////////////////////////////////////////// // src/server.c ////////////////////////////////////////////////////////////////////// @@ -3662,7 +3937,7 @@ void http_server_free(HTTP_Server *server) free(server); } -int http_server_website(HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) +int http_server_add_website(HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) { return socket_group_add_domain(&server->group, domain, cert_file, key_file); } @@ -3708,24 +3983,21 @@ int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseHandl for (int i = 0, j = 0; i < server->num_conns; i++) { - if (!server->conns[i].used) + Connection *conn = &server->conns[i]; + + if (!conn->used) continue; j++; int events = 0; - - if (server->conns[i].socket.ssl_ctx) - events = server->conns[i].socket.event; - else { - switch (http_engine_state(&server->conns[i].engine)) { - case HTTP_ENGINE_STATE_SERVER_RECV_BUF: events = POLLIN; break; - case HTTP_ENGINE_STATE_SERVER_SEND_BUF: events = POLLOUT; break; - default:break; - } + switch (conn->socket.event) { + case SOCKET_WANT_NONE: events = 0; break; + case SOCKET_WANT_READ: events = POLLIN; break; + case SOCKET_WANT_WRITE: events = POLLOUT; break; } if (events) { - polled[num_polled].fd = server->conns[i].socket.fd; + polled[num_polled].fd = conn->socket.fd; polled[num_polled].events = events; polled[num_polled].revents = 0; indices[num_polled] = i; @@ -3754,6 +4026,7 @@ int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseHandl server->conns[k].used = true; socket_accept(&server->conns[k].socket, secure ? &server->group : NULL, new_fd); + http_engine_init(&server->conns[k].engine, 0, server_memfunc, NULL); server->num_conns++; } @@ -3944,8 +4217,11 @@ void http_response_done(HTTP_ResponseHandle res) ////////////////////////////////////////////////////////////////////// #ifndef _WIN32 +#include +#include +#include +#include #endif - typedef enum { ROUTE_STATIC_DIR, ROUTE_DYNAMIC, diff --git a/chttp.h b/chttp.h index 500ddd0..4fec928 100644 --- a/chttp.h +++ b/chttp.h @@ -10,13 +10,15 @@ extern "C" { #endif -#include -#include - ////////////////////////////////////////////////////////////////////// // src/basic.h ////////////////////////////////////////////////////////////////////// +#ifndef CHTTP_BASIC_INCLUDED +#define CHTTP_BASIC_INCLUDED + +#include + // String type used throughout cHTTP. typedef struct { char *ptr; @@ -89,10 +91,14 @@ HTTP_String http_trim(HTTP_String s); #define HTTP_ASSERT(X) {if (!(X)) { __builtin_trap(); }} #endif -////////////////////////////////////////////////////////////////////// +#endif // CHTTP_BASIC_INCLUDED////////////////////////////////////////////////////////////////////// // src/parse.h ////////////////////////////////////////////////////////////////////// +#ifndef PARSE_INCLUDED +#define PARSE_INCLUDED +#include "basic.h" + #define HTTP_MAX_HEADERS 32 typedef struct { @@ -181,10 +187,14 @@ HTTP_String http_getqueryparam (HTTP_Request *req, HTTP_String name); HTTP_String http_getbodyparam (HTTP_Request *req, HTTP_String name); HTTP_String http_getcookie (HTTP_Request *req, HTTP_String name); -////////////////////////////////////////////////////////////////////// +#endif // PARSE_INCLUDED////////////////////////////////////////////////////////////////////// // src/engine.h ////////////////////////////////////////////////////////////////////// +#ifndef HTTP_ENGINE_INCLUDED +#define HTTP_ENGINE_INCLUDED +#include "parse.h" + typedef enum { HTTP_MEMFUNC_MALLOC, HTTP_MEMFUNC_FREE, @@ -299,10 +309,16 @@ void http_engine_bodyack (HTTP_Engine *eng, int num); void http_engine_done (HTTP_Engine *eng); void http_engine_undo (HTTP_Engine *eng); -////////////////////////////////////////////////////////////////////// +#endif // HTTP_ENGINE_INCLUDED////////////////////////////////////////////////////////////////////// // src/client.h ////////////////////////////////////////////////////////////////////// +#ifndef CLIENT_INCLUDED +#define CLIENT_INCLUDED + +#include +#include "parse.h" + // Initialize the global state of cHTTP. // // cHTTP tries to avoid global state. What this function @@ -399,10 +415,16 @@ HTTP_Response *http_post(HTTP_String url, HTTP_String *headers, int num_headers, HTTP_String body, HTTP_RequestHandle *phandle); -////////////////////////////////////////////////////////////////////// +#endif // CLIENT_INCLUDED////////////////////////////////////////////////////////////////////// // src/server.h ////////////////////////////////////////////////////////////////////// +#ifndef HTTP_SERVER_INCLUDED +#define HTTP_SERVER_INCLUDED + +#include +#include "parse.h" + typedef struct { void *data0; int data1; @@ -428,10 +450,15 @@ void http_response_bodyack (HTTP_ResponseHandle res, int num); void http_response_undo (HTTP_ResponseHandle res); void http_response_done (HTTP_ResponseHandle res); -////////////////////////////////////////////////////////////////////// +#endif // HTTP_SERVER_INCLUDED////////////////////////////////////////////////////////////////////// // src/cert.h ////////////////////////////////////////////////////////////////////// +#ifndef CERT_INCLUDED +#define CERT_INCLUDED + +#include "basic.h" + // This is an utility to create self-signed certificates // useful when testing HTTPS servers locally. This is only // meant to be used by people starting out with a library @@ -449,10 +476,15 @@ void http_response_done (HTTP_ResponseHandle res); 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/router.h ////////////////////////////////////////////////////////////////////// +#ifndef HTTP_ROUTER_INCLUDED +#define HTTP_ROUTER_INCLUDED + +#include "server.h" + typedef struct HTTP_Router HTTP_Router; typedef void (*HTTP_RouterFunc)(HTTP_Request*, HTTP_ResponseHandle, void*);; @@ -463,7 +495,7 @@ void http_router_dir (HTTP_Router *router, HTTP_String endpoint, HTT void http_router_func (HTTP_Router *router, HTTP_Method method, HTTP_String endpoint, HTTP_RouterFunc func, void*); int http_serve (char *addr, int port, HTTP_Router *router); - +#endif // HTTP_ROUTER_INCLUDED #ifdef __cplusplus } #endif diff --git a/examples/engine/blocking_server.c b/examples/engine/blocking_server.c index 0a73c45..730e33d 100644 --- a/examples/engine/blocking_server.c +++ b/examples/engine/blocking_server.c @@ -51,10 +51,10 @@ static void produce_response(HTTP_Engine *eng) http_engine_status(eng, 200); // Set zero or more headers - http_engine_header(eng, "Server: tinyhttp", -1); + http_engine_header(eng, HTTP_STR("Server: tinyhttp")); // Set some bytes in the body - http_engine_body(eng, "Hello, world!", 13); + http_engine_body(eng, HTTP_STR("Hello, world!")); // This is one difference from the http_response_XXX API. // It's possible to write response content directly into diff --git a/examples/engine/iocp_server.c b/examples/engine/iocp_server.c index 81fbee4..9192828 100644 --- a/examples/engine/iocp_server.c +++ b/examples/engine/iocp_server.c @@ -1,3 +1,5 @@ +#ifdef _WIN32 + #include #include #include @@ -222,4 +224,16 @@ int main(void) } return 0; -} \ No newline at end of file +} + +#else + +#include + +int main(void) +{ + printf("This example only works on windows!\n"); + return -1; +} + +#endif \ No newline at end of file diff --git a/examples/server/030_using_workers.c b/examples/server/030_using_workers.c index 9cc03fe..ff4cc1d 100644 --- a/examples/server/030_using_workers.c +++ b/examples/server/030_using_workers.c @@ -1,6 +1,24 @@ +#include #include #include +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +typedef void* Thread; +typedef unsigned long ThreadReturn; +typedef CRITICAL_SECTION Mutex; +typedef CONDITION_VARIABLE Condvar; +#endif + +#ifdef __linux__ +#include +typedef pthread_t Thread; +typedef void* ThreadReturn; +typedef pthread_mutex_t Mutex; +typedef pthread_cond_t Condvar; +#endif + // NOTE: This example doesn't work yet! // This example shows how to delegate the response creation @@ -56,7 +74,20 @@ Job pop_job(void); // with no new job pushed. bool push_job(Job job, bool block); -void *worker(void*) +void thread_create(Thread *thread, void *arg, ThreadReturn (*func)(void*)); +ThreadReturn thread_join(Thread thread); + +void mutex_init(Mutex *mutex); +void mutex_free(Mutex *mutex); +void mutex_lock(Mutex *mutex); +void mutex_unlock(Mutex *mutex); + +void condvar_init(Condvar *condvar); +void condvar_free(Condvar *condvar); +void condvar_wait(Condvar *condvar, Mutex *mutex); +void condvar_signal(Condvar *condvar); + +ThreadReturn worker(void*) { for (bool exit = false; !exit; ) { @@ -82,7 +113,7 @@ void *worker(void*) } } - return NULL; + return 0; } int main(void) @@ -93,12 +124,15 @@ int main(void) if (server == NULL) return -1; + Thread worker_id; + thread_create(&worker_id, NULL, worker); + for (;;) { HTTP_Request *req; HTTP_ResponseHandle res; - int ret = http_server_wait(server, &res, &res); + int ret = http_server_wait(server, &req, &res); if (ret < 0) return -1; if (http_streq(req->url.path, HTTP_STR("/endpoint_A"))) { @@ -139,6 +173,7 @@ int main(void) Job job; job.type = NO_JOB; push_job(job, true); + thread_join(worker_id); http_server_free(server); free_job_queue(); @@ -176,8 +211,8 @@ Job pop_job(void) { mutex_lock(&queue_lock); - while (queue_count == 0); - condvar_wait(&queue_produce_event, &queue_lock, -1); + while (queue_count == 0) + condvar_wait(&queue_produce_event, &queue_lock); Job job = queue[queue_head]; queue_head = (queue_head + 1) % MAX_JOBS; @@ -199,7 +234,7 @@ bool push_job(Job job, bool block) } do - condvar_wait(&queue_consume_event, &queue_lock, -1); + condvar_wait(&queue_consume_event, &queue_lock); while (queue_count == 0); } @@ -211,3 +246,134 @@ bool push_job(Job job, bool block) mutex_unlock(&queue_lock); return true; } + +////////////////////////////////////////////// + +void thread_create(Thread *thread, void *arg, ThreadReturn (*func)(void*)) +{ +#ifdef _WIN32 + Thread thread_ = CreateThread(NULL, 0, func, arg, 0, NULL); + if (thread_ == INVALID_HANDLE_VALUE) + abort(); + *thread = thread_; +#endif + +#ifdef __linux__ + int ret = pthread_create(thread, NULL, func, arg); + if (ret) abort(); +#endif +} + +ThreadReturn thread_join(Thread thread) +{ +#ifdef _WIN32 + ThreadReturn result; + WaitForSingleObject(thread, INFINITE); + if (!GetExitCodeThread(thread, &result)) + abort(); + CloseHandle(thread); + return result; +#endif + +#ifdef __linux__ + ThreadReturn result; + int ret = pthread_join(thread, &result); + if (ret) abort(); + return result; +#endif +} + +void mutex_init(Mutex *mutex) +{ +#ifdef _WIN32 + InitializeCriticalSection(mutex); +#endif + +#ifdef __linux__ + if (pthread_mutex_init(mutex, NULL)) + abort(); +#endif +} + +void mutex_free(Mutex *mutex) +{ +#ifdef _WIN32 + DeleteCriticalSection(mutex); +#endif + +#ifdef __linux__ + if (pthread_mutex_destroy(mutex)) + abort(); +#endif +} + +void mutex_lock(Mutex *mutex) +{ +#ifdef _WIN32 + EnterCriticalSection(mutex); +#endif + +#ifdef __linux__ + if (pthread_mutex_lock(mutex)) + abort(); +#endif +} + +void mutex_unlock(Mutex *mutex) +{ +#ifdef _WIN32 + LeaveCriticalSection(mutex); +#endif + +#ifdef __linux__ + if (pthread_mutex_unlock(mutex)) + abort(); +#endif +} + +void condvar_init(Condvar *condvar) +{ +#ifdef _WIN32 + InitializeConditionVariable(condvar); +#endif + +#ifdef __linux__ + if (pthread_cond_init(condvar, NULL)) + abort(); +#endif +} + +void condvar_free(Condvar *condvar) +{ +#ifdef __linux__ + if (pthread_cond_destroy(condvar)) + abort(); +#else + (void) condvar; +#endif +} + +void condvar_wait(Condvar *condvar, Mutex *mutex) +{ +#ifdef _WIN32 + if (!SleepConditionVariableCS(condvar, mutex, INFINITE)) + abort(); +#endif + +#ifdef __linux__ + int err = pthread_cond_wait(condvar, mutex); + if (err) abort(); +#endif +} + +void condvar_signal(Condvar *condvar) +{ +#ifdef _WIN32 + WakeConditionVariable(condvar); +#endif + +#ifdef __linux__ + if (pthread_cond_signal(condvar)) + abort(); +#endif +} diff --git a/examples/server/040_virtual_hosts.c b/examples/server/040_virtual_hosts.c index e69de29..d877a7c 100644 --- a/examples/server/040_virtual_hosts.c +++ b/examples/server/040_virtual_hosts.c @@ -0,0 +1,2 @@ + +int main(void) {} \ No newline at end of file diff --git a/examples/server/050_https_server.c b/examples/server/050_https_server.c index cbd00de..ce68691 100644 --- a/examples/server/050_https_server.c +++ b/examples/server/050_https_server.c @@ -1,3 +1,4 @@ +#include #include #include @@ -59,7 +60,7 @@ int main(void) HTTP_Request *req; HTTP_ResponseHandle res; - int ret = http_server_wait(server, &res, &res); + int ret = http_server_wait(server, &req, &res); if (ret < 0) return -1; http_response_status(res, 200); diff --git a/examples/server/060_virtual_hosts_over_https.c b/examples/server/060_virtual_hosts_over_https.c index 39a5c2b..aec2c06 100644 --- a/examples/server/060_virtual_hosts_over_https.c +++ b/examples/server/060_virtual_hosts_over_https.c @@ -1,3 +1,4 @@ +#include #include // This is an example of how to serve different websites @@ -41,6 +42,7 @@ int main(void) // certificates using the add_website function ret = http_server_add_website(server, + HTTP_STR("websiteB.com"), HTTP_STR("websiteB_cert.pem"), HTTP_STR("websiteB_key.pem") ); @@ -48,6 +50,7 @@ int main(void) return -1; ret = http_server_add_website(server, + HTTP_STR("websiteC.com"), HTTP_STR("websiteC_cert.pem"), HTTP_STR("websiteC_key.pem") ); @@ -94,13 +97,13 @@ int main(void) if (http_streq(host, HTTP_STR("websiteB.com"))) { http_response_status(res, 200); - http_response_body(res, "Hello from websiteB.com!"); + http_response_body(res, HTTP_STR("Hello from websiteB.com!")); http_response_done(res); } else if (http_streq(host, HTTP_STR("websiteC.com"))) { http_response_status(res, 200); - http_response_body(res, "Hello from websiteC.com!"); + http_response_body(res, HTTP_STR("Hello from websiteC.com!")); http_response_done(res); } else { @@ -109,7 +112,7 @@ int main(void) // with the certificate setup http_response_status(res, 200); - http_response_body(res, "Hello from websiteA.com!"); + http_response_body(res, HTTP_STR("Hello from websiteA.com!")); http_response_done(res); } } diff --git a/misc/amalg.py b/misc/amalg.py index 46b2ad5..6b6fcac 100644 --- a/misc/amalg.py +++ b/misc/amalg.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -AmalgamationBuilder - Implementation with improved #line support +AmalgamationBuilder - Implementation with improved #line support and conditional include preservation """ import re @@ -25,6 +25,8 @@ class AmalgamationBuilder: original_line_number = 1 skipped_lines = 0 first_content_line = True + in_conditional_block = False + conditional_depth = 0 self.body += "//////////////////////////////////////////////////////////////////////\n" self.body += "// " + filepath + "\n" @@ -32,7 +34,33 @@ class AmalgamationBuilder: self.body += "\n" for line in lines: - # Check if the line is an include + # Check if we're entering a conditional block + if self._is_conditional_start(line): + if conditional_depth == 0: + in_conditional_block = True + conditional_depth += 1 + # Keep conditional blocks in place + self.body += line + original_line_number += 1 + continue + + # Check if we're exiting a conditional block + if self._is_conditional_end(line): + conditional_depth -= 1 + if conditional_depth == 0: + in_conditional_block = False + # Keep conditional blocks in place + self.body += line + original_line_number += 1 + continue + + # If we're inside a conditional block, keep all lines as-is + if in_conditional_block: + self.body += line + original_line_number += 1 + continue + + # Check if the line is an include (outside conditional blocks only) if self._is_include_line(line): include_info = self._extract_include_info(line) if include_info: @@ -69,6 +97,18 @@ class AmalgamationBuilder: self.body += line original_line_number += 1 + def _is_conditional_start(self, line: str) -> bool: + """Check if a line starts a conditional compilation block""" + stripped = line.strip() + return (stripped.startswith('#ifdef ') or + stripped.startswith('#ifndef ') or + stripped.startswith('#if ')) + + def _is_conditional_end(self, line: str) -> bool: + """Check if a line ends a conditional compilation block""" + stripped = line.strip() + return stripped.startswith('#endif') + def _is_include_line(self, line: str) -> bool: """Check if a line is an #include directive""" # Skip obviously non-include lines for performance @@ -185,6 +225,7 @@ def main(): "src/engine.c", "src/socket.c", "src/client.c", + "src/cert.c", "src/server.c", "src/router.c" ] diff --git a/src/cert.c b/src/cert.c index bcc5a19..d7b59df 100644 --- a/src/cert.c +++ b/src/cert.c @@ -1,12 +1,19 @@ #include #include #include + +#ifdef HTTPS_ENABLED #include #include #include #include #include #include +#endif + +#include "cert.h" + +#ifdef HTTPS_ENABLED static EVP_PKEY *generate_rsa_key_pair(int key_bits) { @@ -137,4 +144,19 @@ int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, X509_free(x509); EVP_PKEY_free(pkey); return 0; -} \ No newline at end of file +} + +#else + +int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, + HTTP_String cert_file, HTTP_String key_file) +{ + (void) C; + (void) O; + (void) CN; + (void) cert_file; + (void) key_file; + return -1; +} + +#endif \ No newline at end of file diff --git a/src/client.c b/src/client.c index 19b6ccf..a680184 100644 --- a/src/client.c +++ b/src/client.c @@ -1,14 +1,22 @@ +#include +#include +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#define POLL WSAPoll +#endif + +#ifdef __linux__ +#include +#define POLL poll +#endif + #include "client.h" #include "socket.h" #include "engine.h" -#include -#include -#include -#include - -// TODO -#include -#define ERROR printf("error at %s:%d\n", __FILE__, __LINE__); #define CLIENT_MAX_CONNS 256 @@ -200,7 +208,7 @@ int http_client_wait(HTTP_Client *client, HTTP_RequestHandle *handle) if (num_polled == 0) return -1; - poll(polled, num_polled, -1); + POLL(polled, num_polled, -1); for (int i = 0; i < num_polled; i++) { @@ -275,7 +283,6 @@ void http_request_line(HTTP_RequestHandle handle, HTTP_Method method, HTTP_Strin int ret = http_parse_url(url.ptr, url.len, &parsed_url); if (ret != url.len) { // TODO - ERROR; return; } @@ -284,7 +291,6 @@ void http_request_line(HTTP_RequestHandle handle, HTTP_Method method, HTTP_Strin secure = true; } else if (!http_streq(parsed_url.scheme, HTTP_STR("http"))) { // TODO - ERROR; return; } @@ -304,7 +310,6 @@ void http_request_line(HTTP_RequestHandle handle, HTTP_Method method, HTTP_Strin case HTTP_HOST_MODE_VOID: // TODO - ERROR; return; } diff --git a/src/engine.c b/src/engine.c index 5ffd1ea..94fac0a 100644 --- a/src/engine.c +++ b/src/engine.c @@ -1,16 +1,16 @@ +#include #include #include #include #include #include // TODO: remove some of these headers -#include #include #include #include #include + #include "basic.h" #include "engine.h" -#include "byte_queue.h" // This is the implementation of a byte queue useful // for systems that need to process engs of bytes. diff --git a/src/router.c b/src/router.c index 405704d..7e14072 100644 --- a/src/router.c +++ b/src/router.c @@ -1,15 +1,30 @@ #include #include #include -#include "router.h" -#ifndef _WIN32 +#ifdef _WIN32 +#include +#endif + +#ifdef __linux__ #include #include #include #include #endif +#include "router.h" + +bool is_alpha(char c) +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +bool is_digit(char c) +{ + return c >= '0' && c <= '9'; +} + typedef enum { ROUTE_STATIC_DIR, ROUTE_DYNAMIC, diff --git a/src/server.c b/src/server.c index f387098..bc215ab 100644 --- a/src/server.c +++ b/src/server.c @@ -1,8 +1,20 @@ #include +#include #include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#define POLL WSAPoll +#define CLOSE_SOCKET closesocket +#endif + +#ifdef __linux__ #include #include -#include +#define CLOSE_SOCKET close +#endif + #include "engine.h" #include "socket.h" #include "server.h" @@ -39,12 +51,12 @@ static int listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int b { int flags = fcntl(listen_fd, F_GETFL, 0); if (flags < 0) { - close(listen_fd); + CLOSE_SOCKET(listen_fd); return -1; } if (fcntl(listen_fd, F_SETFL, flags | O_NONBLOCK) < 0) { - close(listen_fd); + CLOSE_SOCKET(listen_fd); return -1; } } @@ -61,7 +73,7 @@ static int listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int b _Static_assert(sizeof(struct in_addr) == sizeof(HTTP_IPv4)); if (http_parse_ipv4(addr.ptr, addr.len, (HTTP_IPv4*) &addr_buf) < 0) { - close(listen_fd); + CLOSE_SOCKET(listen_fd); return -1; } } @@ -71,12 +83,12 @@ static int listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int b bind_buf.sin_addr = addr_buf; bind_buf.sin_port = htons(port); if (bind(listen_fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) { - close(listen_fd); + CLOSE_SOCKET(listen_fd); return -1; } if (listen(listen_fd, backlog) < 0) { - close(listen_fd); + CLOSE_SOCKET(listen_fd); return -1; } @@ -119,7 +131,7 @@ HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port, else { if (socket_group_init_server(&server->group, cert_key, private_key) < 0) { - close(server->listen_fd); + CLOSE_SOCKET(server->listen_fd); free(server); return NULL; } @@ -127,7 +139,7 @@ HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port, server->secure_fd = listen_socket(addr, secure_port, reuse_addr, backlog); if (server->secure_fd < 0) { socket_group_free(&server->group); - close(server->listen_fd); + CLOSE_SOCKET(server->listen_fd); free(server); return NULL; } @@ -156,14 +168,14 @@ void http_server_free(HTTP_Server *server) // TODO } - close(server->secure_fd); - close(server->listen_fd); + CLOSE_SOCKET(server->secure_fd); + CLOSE_SOCKET(server->listen_fd); if (server->secure_fd != -1) socket_group_free(&server->group); free(server); } -int http_server_website(HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) +int http_server_add_website(HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) { return socket_group_add_domain(&server->group, domain, cert_file, key_file); } @@ -209,24 +221,21 @@ int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseHandl for (int i = 0, j = 0; i < server->num_conns; i++) { - if (!server->conns[i].used) + Connection *conn = &server->conns[i]; + + if (!conn->used) continue; j++; int events = 0; - - if (server->conns[i].socket.ssl_ctx) - events = server->conns[i].socket.event; - else { - switch (http_engine_state(&server->conns[i].engine)) { - case HTTP_ENGINE_STATE_SERVER_RECV_BUF: events = POLLIN; break; - case HTTP_ENGINE_STATE_SERVER_SEND_BUF: events = POLLOUT; break; - default:break; - } + switch (conn->socket.event) { + case SOCKET_WANT_NONE: events = 0; break; + case SOCKET_WANT_READ: events = POLLIN; break; + case SOCKET_WANT_WRITE: events = POLLOUT; break; } if (events) { - polled[num_polled].fd = server->conns[i].socket.fd; + polled[num_polled].fd = conn->socket.fd; polled[num_polled].events = events; polled[num_polled].revents = 0; indices[num_polled] = i; @@ -235,7 +244,7 @@ int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseHandl } int timeout = -1; - poll(polled, num_polled, timeout); + POLL(polled, num_polled, timeout); for (int i = 0; i < num_polled; i++) { @@ -255,6 +264,7 @@ int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseHandl server->conns[k].used = true; socket_accept(&server->conns[k].socket, secure ? &server->group : NULL, new_fd); + http_engine_init(&server->conns[k].engine, 0, server_memfunc, NULL); server->num_conns++; } diff --git a/src/socket.c b/src/socket.c index 599629f..12f361b 100644 --- a/src/socket.c +++ b/src/socket.c @@ -1,16 +1,17 @@ #include -#include +#include // TODO: organize these includes #include "socket.h" +#ifdef HTTPS_ENABLED #include #include #include #include #include #include +#endif #include #include #include -#include #include #include #include @@ -18,22 +19,34 @@ #include #include +#ifdef _WIN32 +#include +#endif + +#ifdef __linux__ +#include +#endif void socket_global_init(void) { +#ifdef HTTPS_ENABLED SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); +#endif } void socket_global_free(void) { +#ifdef HTTPS_ENABLED EVP_cleanup(); ERR_free_strings(); +#endif } int socket_group_init(SocketGroup *group) { +#ifdef HTTPS_ENABLED SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_client_method()); if (!ssl_ctx) { fprintf(stderr, "Unable to create SSL context\n"); @@ -59,9 +72,13 @@ int socket_group_init(SocketGroup *group) group->domains = NULL; group->num_domains = 0; group->max_domains = 0; +#else + (void) group; +#endif return 0; } +#ifdef HTTPS_ENABLED static int servername_callback(SSL *ssl, int *ad, void *arg) { SocketGroup *group = arg; @@ -80,9 +97,11 @@ static int servername_callback(SSL *ssl, int *ad, void *arg) return SSL_TLSEXT_ERR_NOACK; } +#endif int socket_group_init_server(SocketGroup *group, HTTP_String cert_file, HTTP_String key_file) { +#ifdef HTTPS_ENABLED SSL_CTX *ssl_ctx = SSL_CTX_new(TLS_server_method()); if (!ssl_ctx) { fprintf(stderr, "Unable to create server SSL context\n"); @@ -143,16 +162,27 @@ int socket_group_init_server(SocketGroup *group, HTTP_String cert_file, HTTP_Str group->domains = NULL; group->num_domains = 0; group->max_domains = 0; +#else + (void) group; + if (cert_file.len > 0 || key_file.len > 0) + return -1; +#endif + return 0; } void socket_group_free(SocketGroup *group) { +#ifdef HTTPS_ENABLED SSL_CTX_free(group->ssl_ctx); +#else + (void) group; +#endif } int socket_group_add_domain(SocketGroup *group, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) { +#ifdef HTTPS_ENABLED if (group->num_domains == group->max_domains) { int new_max_domains = 2 * group->max_domains; @@ -236,6 +266,13 @@ int socket_group_add_domain(SocketGroup *group, HTTP_String domain, HTTP_String domain_info->ssl_ctx = ssl_ctx; group->num_domains++; return 0; +#else + (void) group; + (void) domain; + (void) cert_file; + (void) key_file; + return -1; +#endif } SocketState socket_state(Socket *sock) @@ -245,12 +282,20 @@ SocketState socket_state(Socket *sock) void socket_accept(Socket *sock, SocketGroup *group, int fd) { +#ifdef HTTPS_ENABLED + sock->ssl = NULL; + sock->ssl_ctx = group ? group->ssl_ctx : NULL; +#else + if (group) { + sock->state = SOCKET_STATE_DIED; + return; + } +#endif + // Initialize socket for server-side TLS handshake sock->state = SOCKET_STATE_ACCEPTED; // TCP connection already established sock->event = SOCKET_WANT_NONE; sock->fd = fd; - sock->ssl = NULL; - sock->ssl_ctx = group ? group->ssl_ctx : NULL; sock->addr_list = NULL; sock->addr_count = 0; sock->addr_cursor = 0; @@ -258,7 +303,7 @@ void socket_accept(Socket *sock, SocketGroup *group, int fd) sock->port = 0; // Set non-blocking mode for the accepted socket - int flags = fcntl(fd, F_GETFL, 0); + int flags = fcntl(fd, F_GETFL, 0); // TODO: fail on error if (flags >= 0) { fcntl(fd, F_SETFL, flags | O_NONBLOCK); } @@ -267,12 +312,20 @@ void socket_accept(Socket *sock, SocketGroup *group, int fd) socket_update(sock); } -void socket_connect(Socket *sock, SocketGroup *group, HTTP_String host, uint16_t port) { +void socket_connect(Socket *sock, SocketGroup *group, HTTP_String host, uint16_t port) +{ +#ifdef HTTPS_ENABLED + sock->ssl = NULL; + sock->ssl_ctx = group ? group->ssl_ctx : NULL; +#else + if (group) { + sock->state = SOCKET_STATE_DIED; + return; + } +#endif sock->state = SOCKET_STATE_PENDING; sock->event = SOCKET_WANT_NONE; sock->fd = -1; - sock->ssl = NULL; - sock->ssl_ctx = group ? group->ssl_ctx : NULL; sock->addr_list = NULL; sock->addr_count = 0; sock->addr_cursor = 0; @@ -322,12 +375,20 @@ void socket_connect(Socket *sock, SocketGroup *group, HTTP_String host, uint16_t socket_update(sock); } -void socket_connect_ipv4(Socket *sock, SocketGroup *group, HTTP_IPv4 addr, uint16_t port) { +void socket_connect_ipv4(Socket *sock, SocketGroup *group, HTTP_IPv4 addr, uint16_t port) +{ +#ifdef HTTPS_ENABLED + sock->ssl = NULL; + sock->ssl_ctx = group ? group->ssl_ctx : NULL; +#else + if (group) { + sock->state = SOCKET_STATE_DIED; + return; + } +#endif sock->state = SOCKET_STATE_PENDING; sock->event = SOCKET_WANT_NONE; sock->fd = -1; - sock->ssl = NULL; - sock->ssl_ctx = group ? group->ssl_ctx : NULL; sock->addr_list = NULL; sock->addr_count = 0; sock->addr_cursor = 0; @@ -343,12 +404,20 @@ void socket_connect_ipv4(Socket *sock, SocketGroup *group, HTTP_IPv4 addr, uint1 socket_update(sock); } -void socket_connect_ipv6(Socket *sock, SocketGroup *group, HTTP_IPv6 addr, uint16_t port) { +void socket_connect_ipv6(Socket *sock, SocketGroup *group, HTTP_IPv6 addr, uint16_t port) +{ +#ifdef HTTPS_ENABLED + sock->ssl = NULL; + sock->ssl_ctx = group ? group->ssl_ctx : NULL; +#else + if (group) { + sock->state = SOCKET_STATE_DIED; + return; + } +#endif sock->state = SOCKET_STATE_PENDING; sock->event = SOCKET_WANT_NONE; sock->fd = -1; - sock->ssl = NULL; - sock->ssl_ctx = group ? group->ssl_ctx : NULL; sock->addr_list = NULL; sock->addr_count = 0; sock->addr_cursor = 0; @@ -364,6 +433,16 @@ void socket_connect_ipv6(Socket *sock, SocketGroup *group, HTTP_IPv6 addr, uint1 socket_update(sock); } +bool socket_secure(Socket *sock) +{ +#ifdef HTTPS_ENABLED + return sock->ssl_ctx != NULL; +#else + (void) sock; + return false; +#endif +} + void socket_update(Socket *sock) { sock->event = SOCKET_WANT_NONE; @@ -376,10 +455,12 @@ void socket_update(Socket *sock) switch (sock->state) { case SOCKET_STATE_PENDING: { +#ifdef HTTPS_ENABLED if (sock->ssl) { SSL_free(sock->ssl); sock->ssl = NULL; } +#endif if (sock->fd != -1) close(sock->fd); @@ -485,11 +566,11 @@ void socket_update(Socket *sock) case SOCKET_STATE_CONNECTED: { - if (sock->ssl_ctx == NULL) { + if (!socket_secure(sock)) { sock->event = SOCKET_WANT_NONE; sock->state = SOCKET_STATE_ESTABLISHED_READY; } else { - +#ifdef HTTPS_ENABLED // Start SSL handshake if (!sock->ssl) { sock->ssl = SSL_new(sock->ssl_ctx); @@ -521,17 +602,20 @@ void socket_update(Socket *sock) sock->event = SOCKET_WANT_NONE; sock->state = SOCKET_STATE_PENDING; again = true; +#else + HTTP_ASSERT(0); +#endif } } break; case SOCKET_STATE_ACCEPTED: { - if (sock->ssl_ctx == NULL) { + if (!socket_secure(sock)) { sock->event = SOCKET_WANT_NONE; sock->state = SOCKET_STATE_ESTABLISHED_READY; } else { - +#ifdef HTTPS_ENABLED // Start server-side SSL handshake if (!sock->ssl) { sock->ssl = SSL_new(sock->ssl_ctx); @@ -560,6 +644,9 @@ void socket_update(Socket *sock) // Server socket error - close the connection sock->event = SOCKET_WANT_NONE; sock->state = SOCKET_STATE_DIED; +#else + HTTP_ASSERT(0); +#endif } } break; @@ -573,11 +660,11 @@ void socket_update(Socket *sock) case SOCKET_STATE_SHUTDOWN: { - if (sock->ssl_ctx == NULL) { + if (!socket_secure(sock)) { sock->event = SOCKET_WANT_NONE; sock->state = SOCKET_STATE_DIED; } else { - +#ifdef HTTPS_ENABLED int ret = SSL_shutdown(sock->ssl); if (ret == 1) { sock->event = SOCKET_WANT_NONE; @@ -598,6 +685,9 @@ void socket_update(Socket *sock) sock->event = SOCKET_WANT_NONE; sock->state = SOCKET_STATE_DIED; +#else + HTTP_ASSERT(0); +#endif } } break; @@ -618,7 +708,7 @@ int socket_read(Socket *sock, char *dst, int max) { return -1; } - if (sock->ssl_ctx == NULL) { + if (!socket_secure(sock)) { int ret = read(sock->fd, dst, max); if (ret == 0) { sock->event = SOCKET_WANT_NONE; @@ -639,6 +729,7 @@ int socket_read(Socket *sock, char *dst, int max) { } 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); @@ -657,6 +748,10 @@ int socket_read(Socket *sock, char *dst, int max) { ret = 0; } return ret; +#else + HTTP_ASSERT(0); + return -1; +#endif } } @@ -668,7 +763,7 @@ int socket_write(Socket *sock, char *src, int len) { return 0; } - if (sock->ssl_ctx == NULL) { + if (!socket_secure(sock)) { int ret = write(sock->fd, src, len); if (ret < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { @@ -684,6 +779,7 @@ int socket_write(Socket *sock, char *src, int len) { } 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); @@ -702,6 +798,9 @@ int socket_write(Socket *sock, char *src, int len) { ret = 0; } return ret; +#else + HTTP_ASSERT(0); +#endif } } @@ -712,20 +811,23 @@ void socket_close(Socket *sock) { socket_update(sock); } -void socket_free(Socket *sock) { - // Release all resources associated to the socket - if (sock->ssl) { +void socket_free(Socket *sock) +{ +#ifdef HTTPS_ENABLED + if (sock->ssl) SSL_free(sock->ssl); - sock->ssl = NULL; - } +#endif + if (sock->fd >= 0) { close(sock->fd); sock->fd = -1; } + if (sock->hostname) { free(sock->hostname); sock->hostname = NULL; } + if (sock->addr_list) { free(sock->addr_list); sock->addr_list = NULL; diff --git a/src/socket.h b/src/socket.h index 42aa242..2527976 100644 --- a/src/socket.h +++ b/src/socket.h @@ -53,12 +53,15 @@ // which means the user needs to call socket_free to free the socket // as it's not unusable. -#include +#include -#include "parse.h" +#ifdef HTTPS_ENABLED #include #include #include +#endif + +#include "parse.h" typedef struct { int is_ipv6; @@ -89,8 +92,10 @@ typedef struct { SocketState state; SocketWantEvent event; int fd; +#if HTTPS_ENABLED SSL *ssl; SSL_CTX *ssl_ctx; +#endif AddrInfo *addr_list; int addr_count; int addr_cursor; @@ -98,16 +103,20 @@ typedef struct { uint16_t port; } Socket; +#ifdef HTTPS_ENABLED typedef struct { char name[128]; SSL_CTX *ssl_ctx; } Domain; +#endif typedef struct { +#ifdef HTTPS_ENABLED SSL_CTX *ssl_ctx; int num_domains; int max_domains; Domain *domains; +#endif } SocketGroup; void socket_global_init (void);