From acf474661140edeca513a63a926857a94a2ea6cc Mon Sep 17 00:00:00 2001 From: Francesco Cozzuto Date: Mon, 21 Jul 2025 02:21:33 +0200 Subject: [PATCH] Simplify amalgamation script --- Makefile | 29 +-- amalg.py | 53 +++++ chttp.c | 589 ++++++++++++++++++++++++++++++-------------------- chttp.h | 136 +++++++----- misc/amalg.py | 276 ----------------------- src/basic.c | 5 +- src/cert.c | 2 + src/cert.h | 2 + src/client.c | 2 + src/client.h | 3 + src/engine.c | 2 + src/engine.h | 3 + src/parse.c | 3 + src/parse.h | 3 + src/router.c | 5 +- src/router.h | 2 + src/server.c | 7 + src/server.h | 3 + src/socket.c | 26 ++- src/socket.h | 2 + 20 files changed, 542 insertions(+), 611 deletions(-) create mode 100644 amalg.py delete mode 100644 misc/amalg.py diff --git a/Makefile b/Makefile index 1666ff9..4068a27 100644 --- a/Makefile +++ b/Makefile @@ -11,12 +11,10 @@ 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 @@ -36,10 +34,10 @@ EXAMPLES_ENGINE := $(patsubst %.c,%$(EXT),$(EXAMPLES_ENGINE_SRC)) all: chttp.c chttp.h examples lib -lib: $(STATIC_LIB) $(SHARED_LIB) +lib: $(STATIC_LIB) chttp.c chttp.h: $(HFILES) $(CFILES) - python misc/amalg.py + python amalg.py # Object files from source files %.o: %.c $(HFILES) @@ -49,10 +47,6 @@ chttp.c chttp.h: $(HFILES) $(CFILES) $(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 @@ -64,28 +58,11 @@ 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 $(STATIC_LIB) rm -f chttp.c chttp.h .PHONY: all lib examples install install-lib install-headers uninstall clean diff --git a/amalg.py b/amalg.py new file mode 100644 index 0000000..6abb063 --- /dev/null +++ b/amalg.py @@ -0,0 +1,53 @@ +class Amalgamator: + def __init__(self): + self.out = "" + + def append_text(self, text): + self.out += text + + def append_file(self, file): + + self.out += "\n" + self.out += "////////////////////////////////////////////////////////////////////////////////////////\n" + self.out += "// " + file + "\n" + self.out += "////////////////////////////////////////////////////////////////////////////////////////\n" + self.out += "\n" + self.out += "#line 1 \"" + file + "\"\n" + self.out += open(file).read() + + if len(self.out) > 0 and self.out[len(self.out)-1] != '\n': + self.out += "\n" + + def save(self, file): + open(file, 'w').write(self.out) + +desc = """ +// This file was generated automatically. Do not modify directly! +""" + +header = Amalgamator() +header.append_text("#ifndef HTTP_AMALGAMATION\n") +header.append_text("#define HTTP_AMALGAMATION\n") +header.append_text(desc) +header.append_file("src/basic.h") +header.append_file("src/parse.h") +header.append_file("src/engine.h") +header.append_file("src/cert.h") +header.append_file("src/client.h") +header.append_file("src/server.h") +header.append_file("src/router.h") +header.append_text("#endif // HTTP_AMALGAMATION\n") +header.save("chttp.h") + +source = Amalgamator() +source.append_text("#include \"chttp.h\"\n") +source.append_file("src/socket.h") +source.append_file("src/basic.c") +source.append_file("src/parse.c") +source.append_file("src/engine.c") +source.append_file("src/cert.c") +source.append_file("src/socket.c") +source.append_file("src/client.c") +source.append_file("src/server.c") +source.append_file("src/router.c") +source.save("chttp.c") diff --git a/chttp.c b/chttp.c index 0820f1d..88cb9b4 100644 --- a/chttp.c +++ b/chttp.c @@ -1,55 +1,10 @@ -/* - * cHTTP Library - Amalgamated Source - * Generated automatically - do not edit manually - */ - #include "chttp.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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 -// and simplifying the zero to one phase. -// -// The C, O, and CN are respectively country name, organization name, -// and common name of the certificate. For instance: -// -// C="IT" -// O="My Organization" -// CN="my_website.com" -// -// The output is a certificate file in PEM format and a private -// key file with the key used to sign the certificate. -int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, - HTTP_String cert_file, HTTP_String key_file); - -#endif // CERT_INCLUDED////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////// // src/socket.h -////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////// +#line 1 "src/socket.h" #ifndef SOCKET_INCLUDED #define SOCKET_INCLUDED // This is a socket abstraction module for non-blocking TCP and TLS sockets. @@ -105,13 +60,17 @@ 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. +#include + #ifdef HTTPS_ENABLED #include #include #include #endif +#ifndef HTTP_AMALGAMATION #include "parse.h" +#endif typedef struct { int is_ipv6; @@ -187,9 +146,19 @@ void socket_close (Socket *sock); void socket_free (Socket *sock); int socket_wait (Socket **socks, int num_socks); -#endif // SOCKET_INCLUDED////////////////////////////////////////////////////////////////////// +#endif // SOCKET_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) { @@ -246,6 +215,7 @@ static bool is_printable(char c) return c >= ' ' && c <= '~'; } +#include void print_bytes(HTTP_String prefix, HTTP_String src) { if (src.len == 0) @@ -285,9 +255,22 @@ void print_bytes(HTTP_String prefix, HTTP_String src) cur++; } putc('\n', stream); -}////////////////////////////////////////////////////////////////////// +} + +//////////////////////////////////////////////////////////////////////////////////////// // src/parse.c -////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////// + +#line 1 "src/parse.c" +#include +#include +#include +#include + +#ifndef HTTP_AMALGAMATION +#include "parse.h" +#include "basic.h" +#endif // From RFC 9112 // request-target = origin-form @@ -1318,9 +1301,28 @@ HTTP_String http_getcookie(HTTP_Request *req, HTTP_String name) { // TODO return (HTTP_String) {NULL, 0}; -}////////////////////////////////////////////////////////////////////// +} + +//////////////////////////////////////////////////////////////////////////////////////// // src/engine.c -////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////// + +#line 1 "src/engine.c" +#include +#include +#include +#include +#include +#include // TODO: remove some of these headers +#include +#include +#include +#include + +#ifndef HTTP_AMALGAMATION +#include "basic.h" +#include "engine.h" +#endif // This is the implementation of a byte queue useful // for systems that need to process engs of bytes. @@ -2301,9 +2303,16 @@ void http_engine_undo(HTTP_Engine *eng) eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_URL; else eng->state = HTTP_ENGINE_STATE_SERVER_PREP_STATUS; -}////////////////////////////////////////////////////////////////////// -// src/socket.c -////////////////////////////////////////////////////////////////////// +} + +//////////////////////////////////////////////////////////////////////////////////////// +// src/cert.c +//////////////////////////////////////////////////////////////////////////////////////// + +#line 1 "src/cert.c" +#include +#include +#include #ifdef HTTPS_ENABLED #include @@ -2313,12 +2322,195 @@ void http_engine_undo(HTTP_Engine *eng) #include #include #endif + +#ifndef HTTP_AMALGAMATION +#include "cert.h" +#endif + +#ifdef HTTPS_ENABLED + +static EVP_PKEY *generate_rsa_key_pair(int key_bits) +{ + EVP_PKEY_CTX *ctx; + EVP_PKEY *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/socket.c +//////////////////////////////////////////////////////////////////////////////////////// + +#line 1 "src/socket.c" +#include // TODO: organize these includes +#include +#include +#include +#include +#include +#include +#include + #ifdef _WIN32 #include #endif + #ifdef __linux__ +#include #include #endif + +#ifdef HTTPS_ENABLED +#include +#include +#include +#include +#include +#include +#endif + +#ifndef HTTP_AMALGAMATION +#include "socket.h" +#endif + void socket_global_init(void) { #ifdef HTTPS_ENABLED @@ -3173,12 +3365,34 @@ int socket_wait(Socket **socks, int num_socks) // TODO: is this used? } return -1; -}////////////////////////////////////////////////////////////////////// -// src/client.c -////////////////////////////////////////////////////////////////////// +} -// TODO -#define ERROR printf("error at %s:%d\n", __FILE__, __LINE__); +//////////////////////////////////////////////////////////////////////////////////////// +// 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 "socket.h" +#include "engine.h" +#endif #define CLIENT_MAX_CONNS 256 @@ -3370,7 +3584,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++) { @@ -3445,7 +3659,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; } @@ -3454,7 +3667,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; } @@ -3474,7 +3686,6 @@ void http_request_line(HTTP_RequestHandle handle, HTTP_Method method, HTTP_Strin case HTTP_HOST_MODE_VOID: // TODO - ERROR; return; } @@ -3619,167 +3830,40 @@ 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 -////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////// + +#line 1 "src/server.c" +#include +#include +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#define POLL WSAPoll +#define CLOSE_SOCKET closesocket +#endif + +#ifdef __linux__ +#include +#include +#include +#include +#include +#define POLL poll +#define CLOSE_SOCKET close +#endif + +#ifndef HTTP_AMALGAMATION +#include "engine.h" +#include "socket.h" +#include "server.h" +#endif #define MAX_CONNS (1<<10) @@ -3813,12 +3897,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; } } @@ -3835,7 +3919,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; } } @@ -3845,12 +3929,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; } @@ -3893,7 +3977,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; } @@ -3901,7 +3985,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; } @@ -3930,8 +4014,8 @@ 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); @@ -4006,7 +4090,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++) { @@ -4212,16 +4296,43 @@ void http_response_done(HTTP_ResponseHandle res) http_engine_free(&conn->engine); server->num_conns--; } -}////////////////////////////////////////////////////////////////////// -// src/router.c -////////////////////////////////////////////////////////////////////// +} -#ifndef _WIN32 +//////////////////////////////////////////////////////////////////////////////////////// +// src/router.c +//////////////////////////////////////////////////////////////////////////////////////// + +#line 1 "src/router.c" +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +#ifdef __linux__ #include #include #include #include #endif + +#ifndef HTTP_AMALGAMATION +#include "router.h" +#endif + +#ifndef HTTP_AMALGAMATION +bool is_alpha(char c) +{ + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} +bool is_digit(char c) +{ + return c >= '0' && c <= '9'; +} +#endif // HTTP_AMALGAMATION + typedef enum { ROUTE_STATIC_DIR, ROUTE_DYNAMIC, @@ -4638,4 +4749,4 @@ int http_serve(char *addr, int port, HTTP_Router *router) http_server_free(server); http_router_free(router); return 0; -} \ No newline at end of file +} diff --git a/chttp.h b/chttp.h index 4fec928..fdf294d 100644 --- a/chttp.h +++ b/chttp.h @@ -1,19 +1,13 @@ -/* - * cHTTP Library - Amalgamated Header - * Generated automatically - do not edit manually - */ +#ifndef HTTP_AMALGAMATION +#define HTTP_AMALGAMATION -#ifndef HTTP_AMALGAMATION_H -#define HTTP_AMALGAMATION_H +// This file was generated automatically. Do not modify directly! -#ifdef __cplusplus -extern "C" { -#endif - -////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////// // src/basic.h -////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////// +#line 1 "src/basic.h" #ifndef CHTTP_BASIC_INCLUDED #define CHTTP_BASIC_INCLUDED @@ -91,13 +85,19 @@ HTTP_String http_trim(HTTP_String s); #define HTTP_ASSERT(X) {if (!(X)) { __builtin_trap(); }} #endif -#endif // CHTTP_BASIC_INCLUDED////////////////////////////////////////////////////////////////////// -// src/parse.h -////////////////////////////////////////////////////////////////////// +#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 @@ -187,13 +187,19 @@ 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 -////////////////////////////////////////////////////////////////////// +#endif // PARSE_INCLUDED +//////////////////////////////////////////////////////////////////////////////////////// +// src/engine.h +//////////////////////////////////////////////////////////////////////////////////////// + +#line 1 "src/engine.h" #ifndef HTTP_ENGINE_INCLUDED #define HTTP_ENGINE_INCLUDED + +#ifndef HTTP_AMALGAMATION #include "parse.h" +#endif typedef enum { HTTP_MEMFUNC_MALLOC, @@ -309,15 +315,52 @@ 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 -////////////////////////////////////////////////////////////////////// +#endif // HTTP_ENGINE_INCLUDED +//////////////////////////////////////////////////////////////////////////////////////// +// src/cert.h +//////////////////////////////////////////////////////////////////////////////////////// + +#line 1 "src/cert.h" +#ifndef CERT_INCLUDED +#define CERT_INCLUDED + +#ifndef HTTP_AMALGAMATION +#include "basic.h" +#endif + +// This is an utility to create self-signed certificates +// useful when testing HTTPS servers locally. This is only +// meant to be used by people starting out with a library +// and simplifying the zero to one phase. +// +// The C, O, and CN are respectively country name, organization name, +// and common name of the certificate. For instance: +// +// C="IT" +// O="My Organization" +// CN="my_website.com" +// +// The output is a certificate file in PEM format and a private +// key file with the key used to sign the certificate. +int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, + HTTP_String cert_file, HTTP_String key_file); + +#endif // CERT_INCLUDED + +//////////////////////////////////////////////////////////////////////////////////////// +// src/client.h +//////////////////////////////////////////////////////////////////////////////////////// + +#line 1 "src/client.h" #ifndef CLIENT_INCLUDED #define CLIENT_INCLUDED #include + +#ifndef HTTP_AMALGAMATION #include "parse.h" +#endif // Initialize the global state of cHTTP. // @@ -415,15 +458,21 @@ 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 -////////////////////////////////////////////////////////////////////// +#endif // CLIENT_INCLUDED +//////////////////////////////////////////////////////////////////////////////////////// +// src/server.h +//////////////////////////////////////////////////////////////////////////////////////// + +#line 1 "src/server.h" #ifndef HTTP_SERVER_INCLUDED #define HTTP_SERVER_INCLUDED #include + +#ifndef HTTP_AMALGAMATION #include "parse.h" +#endif typedef struct { void *data0; @@ -450,40 +499,19 @@ 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 -////////////////////////////////////////////////////////////////////// +#endif // HTTP_SERVER_INCLUDED -#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 -// and simplifying the zero to one phase. -// -// The C, O, and CN are respectively country name, organization name, -// and common name of the certificate. For instance: -// -// C="IT" -// O="My Organization" -// CN="my_website.com" -// -// The output is a certificate file in PEM format and a private -// key file with the key used to sign the certificate. -int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, - HTTP_String cert_file, HTTP_String key_file); - -#endif // CERT_INCLUDED////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////// // src/router.h -////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////// +#line 1 "src/router.h" #ifndef HTTP_ROUTER_INCLUDED #define HTTP_ROUTER_INCLUDED +#ifndef HTTP_AMALGAMATION #include "server.h" +#endif typedef struct HTTP_Router HTTP_Router; typedef void (*HTTP_RouterFunc)(HTTP_Request*, HTTP_ResponseHandle, void*);; @@ -496,8 +524,4 @@ void http_router_func (HTTP_Router *router, HTTP_Method method, HTTP_ int http_serve (char *addr, int port, HTTP_Router *router); #endif // HTTP_ROUTER_INCLUDED -#ifdef __cplusplus -} -#endif - -#endif /* HTTP_AMALGAMATION_H */ +#endif // HTTP_AMALGAMATION diff --git a/misc/amalg.py b/misc/amalg.py deleted file mode 100644 index 6b6fcac..0000000 --- a/misc/amalg.py +++ /dev/null @@ -1,276 +0,0 @@ -#!/usr/bin/env python3 -""" -AmalgamationBuilder - Implementation with improved #line support and conditional include preservation -""" - -import re -import os -from typing import Set - -class AmalgamationBuilder: - def __init__(self): - self.body = "" - self.local_includes: Set[str] = set() # These should deduplicate items - self.global_includes: Set[str] = set() - - def add(self, filepath: str): - """Add a file to the amalgamation""" - try: - with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: - lines = f.readlines() - except FileNotFoundError: - print(f"Warning: File '{filepath}' not found, skipping") - return - - 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" - self.body += "//////////////////////////////////////////////////////////////////////\n" - self.body += "\n" - - for line in lines: - # 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: - filename, uses_angular_brackets = include_info - if uses_angular_brackets: - self.global_includes.add(filename) - else: - self.local_includes.add(filename) - - original_line_number += 1 - skipped_lines += 1 - continue - - # Check if the line is part of a header guard - if self._is_header_guard_line(line): - original_line_number += 1 - skipped_lines += 1 - continue - - # Skip empty lines at the beginning of files - if first_content_line and line.strip() == '': - original_line_number += 1 - skipped_lines += 1 - continue - - # Add #line directive when starting a new file or after skipping significant content - if first_content_line or skipped_lines > 0: - # Only add #line if we skipped lines or it's a new file - # self.body += f'#line {original_line_number} "{filepath}"\n' - first_content_line = False - skipped_lines = 0 - - # Append the current line to body - 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 - if '#include' not in line: - return False - - # Check if this line is commented out - if self._is_line_commented(line): - return False - - # Regex to match include pattern - include_pattern = re.compile(r'^\s*#\s*include\s*[<"][^>"]+[>"]') - return bool(include_pattern.match(line.strip())) - - def _extract_include_info(self, line: str) -> tuple[str, bool] | None: - """Extract filename and bracket type from include line""" - # Pattern to capture include details - include_pattern = re.compile(r'^\s*#\s*include\s*([<"])([^>"]+)([>"])') - match = include_pattern.match(line.strip()) - - if match: - open_delim = match.group(1) - filename = match.group(2) - close_delim = match.group(3) - - # Validate matching delimiters - if (open_delim == '<' and close_delim == '>') or \ - (open_delim == '"' and close_delim == '"'): - uses_angular_brackets = (open_delim == '<') - return filename, uses_angular_brackets - - return None - - def _is_header_guard_line(self, line: str) -> bool: - """Check if a line is part of a header guard (assumes _INCLUDED suffix)""" - stripped = line.strip() - - # Simplified header guard patterns for _INCLUDED suffix - header_guard_patterns = [ - re.compile(r'#ifndef\s+\w+_INCLUDED'), - re.compile(r'#define\s+\w+_INCLUDED'), - re.compile(r'#endif\s*//.*_INCLUDED'), - re.compile(r'#endif\s*/\*.*_INCLUDED.*\*/'), - ] - - return any(pattern.match(stripped) for pattern in header_guard_patterns) - - def _is_line_commented(self, line: str) -> bool: - """Basic check if line is commented out""" - include_pos = line.find('#include') - if include_pos == -1: - return False - - # Check for // comment before #include - comment_pos = line.find('//') - if comment_pos != -1 and comment_pos < include_pos: - return True - - # Check for /* comment before #include (basic case) - before_include = line[:include_pos] - if '/*' in before_include and '*/' not in before_include: - return True - - return False - - def result(self) -> str: - """Generate the final amalgamated result""" - result = "" - - # Write all global includes at the top - for item in sorted(self.global_includes): - result += f'#include <{item}>\n' - - if self.global_includes: - result += "\n" # Add spacing after includes - - # Add the body content - result += self.body - - return result - - -def main(): - """Example usage of AmalgamationBuilder""" - - # Build the header - print("Building header...") - header_builder = AmalgamationBuilder() - - header_files = [ - "src/basic.h", - "src/parse.h", - "src/engine.h", - "src/client.h", - "src/server.h", - "src/cert.h", - "src/router.h" - ] - - for file in header_files: - header_builder.add(file) - - header = header_builder.result() - - # Build the source - print("Building source...") - source_builder = AmalgamationBuilder() - - source_files = [ - "src/cert.h", - "src/socket.h", - "src/basic.c", - "src/parse.c", - "src/engine.c", - "src/socket.c", - "src/client.c", - "src/cert.c", - "src/server.c", - "src/router.c" - ] - - for file in source_files: - source_builder.add(file) - - source = source_builder.result() - - # Write results - print("Writing chttp.h...") - with open("chttp.h", 'w', encoding='utf-8') as f: - f.write('/*\n') - f.write(' * cHTTP Library - Amalgamated Header\n') - f.write(' * Generated automatically - do not edit manually\n') - f.write(' */\n\n') - f.write('#ifndef HTTP_AMALGAMATION_H\n') - f.write('#define HTTP_AMALGAMATION_H\n\n') - f.write('#ifdef __cplusplus\n') - f.write('extern "C" {\n') - f.write('#endif\n\n') - f.write(header) - f.write('\n#ifdef __cplusplus\n') - f.write('}\n') - f.write('#endif\n\n') - f.write('#endif /* HTTP_AMALGAMATION_H */\n') - - print("Writing chttp.c...") - with open("chttp.c", 'w', encoding='utf-8') as f: - f.write('/*\n') - f.write(' * cHTTP Library - Amalgamated Source\n') - f.write(' * Generated automatically - do not edit manually\n') - f.write(' */\n\n') - f.write('#include "chttp.h"\n\n') - f.write(source) - - print("Amalgamation complete!") - print(f"Header: {len(header.splitlines())} lines") - print(f"Source: {len(source.splitlines())} lines") - - # Show some statistics - print(f"\nHeader global includes: {len(header_builder.global_includes)}") - print(f"Source global includes: {len(source_builder.global_includes)}") - print(f"Local includes found: {len(header_builder.local_includes | source_builder.local_includes)}") - - -if __name__ == "__main__": - main() \ No newline at end of file diff --git a/src/basic.c b/src/basic.c index 9d79d43..e3502e5 100644 --- a/src/basic.c +++ b/src/basic.c @@ -1,7 +1,10 @@ -#include "basic.h" #include #include +#ifndef HTTP_AMALGAMATION +#include "basic.h" +#endif + bool http_streq(HTTP_String s1, HTTP_String s2) { if (s1.len != s2.len) diff --git a/src/cert.c b/src/cert.c index d7b59df..2b42231 100644 --- a/src/cert.c +++ b/src/cert.c @@ -11,7 +11,9 @@ #include #endif +#ifndef HTTP_AMALGAMATION #include "cert.h" +#endif #ifdef HTTPS_ENABLED diff --git a/src/cert.h b/src/cert.h index ae274a3..4d1f9d1 100644 --- a/src/cert.h +++ b/src/cert.h @@ -1,7 +1,9 @@ #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 diff --git a/src/client.c b/src/client.c index a680184..08df314 100644 --- a/src/client.c +++ b/src/client.c @@ -14,9 +14,11 @@ #define POLL poll #endif +#ifndef HTTP_AMALGAMATION #include "client.h" #include "socket.h" #include "engine.h" +#endif #define CLIENT_MAX_CONNS 256 diff --git a/src/client.h b/src/client.h index 4cdaea2..bebee99 100644 --- a/src/client.h +++ b/src/client.h @@ -2,7 +2,10 @@ #define CLIENT_INCLUDED #include + +#ifndef HTTP_AMALGAMATION #include "parse.h" +#endif // Initialize the global state of cHTTP. // diff --git a/src/engine.c b/src/engine.c index 94fac0a..f5ce571 100644 --- a/src/engine.c +++ b/src/engine.c @@ -9,8 +9,10 @@ #include #include +#ifndef HTTP_AMALGAMATION #include "basic.h" #include "engine.h" +#endif // This is the implementation of a byte queue useful // for systems that need to process engs of bytes. diff --git a/src/engine.h b/src/engine.h index 39a7011..70926a6 100644 --- a/src/engine.h +++ b/src/engine.h @@ -1,6 +1,9 @@ #ifndef HTTP_ENGINE_INCLUDED #define HTTP_ENGINE_INCLUDED + +#ifndef HTTP_AMALGAMATION #include "parse.h" +#endif typedef enum { HTTP_MEMFUNC_MALLOC, diff --git a/src/parse.c b/src/parse.c index 39208e4..7996b52 100644 --- a/src/parse.c +++ b/src/parse.c @@ -2,8 +2,11 @@ #include #include #include + +#ifndef HTTP_AMALGAMATION #include "parse.h" #include "basic.h" +#endif // From RFC 9112 // request-target = origin-form diff --git a/src/parse.h b/src/parse.h index 21c511f..077bac3 100644 --- a/src/parse.h +++ b/src/parse.h @@ -1,6 +1,9 @@ #ifndef PARSE_INCLUDED #define PARSE_INCLUDED + +#ifndef HTTP_AMALGAMATION #include "basic.h" +#endif #define HTTP_MAX_HEADERS 32 diff --git a/src/router.c b/src/router.c index 7e14072..a828263 100644 --- a/src/router.c +++ b/src/router.c @@ -13,17 +13,20 @@ #include #endif +#ifndef HTTP_AMALGAMATION #include "router.h" +#endif +#ifndef HTTP_AMALGAMATION bool is_alpha(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } - bool is_digit(char c) { return c >= '0' && c <= '9'; } +#endif // HTTP_AMALGAMATION typedef enum { ROUTE_STATIC_DIR, diff --git a/src/router.h b/src/router.h index 4c42fac..1d41da1 100644 --- a/src/router.h +++ b/src/router.h @@ -1,7 +1,9 @@ #ifndef HTTP_ROUTER_INCLUDED #define HTTP_ROUTER_INCLUDED +#ifndef HTTP_AMALGAMATION #include "server.h" +#endif typedef struct HTTP_Router HTTP_Router; typedef void (*HTTP_RouterFunc)(HTTP_Request*, HTTP_ResponseHandle, void*);; diff --git a/src/server.c b/src/server.c index bc215ab..b6f43ed 100644 --- a/src/server.c +++ b/src/server.c @@ -1,4 +1,5 @@ #include +#include #include #include @@ -10,14 +11,20 @@ #endif #ifdef __linux__ +#include +#include #include +#include #include +#define POLL poll #define CLOSE_SOCKET close #endif +#ifndef HTTP_AMALGAMATION #include "engine.h" #include "socket.h" #include "server.h" +#endif #define MAX_CONNS (1<<10) diff --git a/src/server.h b/src/server.h index 337abb2..73d2490 100644 --- a/src/server.h +++ b/src/server.h @@ -2,7 +2,10 @@ #define HTTP_SERVER_INCLUDED #include + +#ifndef HTTP_AMALGAMATION #include "parse.h" +#endif typedef struct { void *data0; diff --git a/src/socket.c b/src/socket.c index 12f361b..2779738 100644 --- a/src/socket.c +++ b/src/socket.c @@ -1,20 +1,8 @@ -#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 #include #include @@ -24,9 +12,23 @@ #endif #ifdef __linux__ +#include #include #endif +#ifdef HTTPS_ENABLED +#include +#include +#include +#include +#include +#include +#endif + +#ifndef HTTP_AMALGAMATION +#include "socket.h" +#endif + void socket_global_init(void) { #ifdef HTTPS_ENABLED diff --git a/src/socket.h b/src/socket.h index 2527976..ec147ad 100644 --- a/src/socket.h +++ b/src/socket.h @@ -61,7 +61,9 @@ #include #endif +#ifndef HTTP_AMALGAMATION #include "parse.h" +#endif typedef struct { int is_ipv6;