From 0790a11b006bb4334420587b8396c90fb9a0c85f Mon Sep 17 00:00:00 2001 From: Francesco Cozzuto Date: Tue, 23 Sep 2025 09:50:20 +0200 Subject: [PATCH] Reorganize files and fix compiler errors --- Makefile | 2 +- amalg.py | 10 +- cweb.c | 12443 +-------------------------------- cweb.h | 99 +- {3p => demo}/chttp.c | 7 +- {3p => demo}/chttp.h | 0 demo/main.c | 39 +- {3p => demo}/wl.c | 0 {3p => demo}/wl.h | 0 {3p => src}/crypt_blowfish.c | 0 {3p => src}/crypt_blowfish.h | 0 src/main.c | 382 +- src/main.h | 33 +- 13 files changed, 587 insertions(+), 12428 deletions(-) rename {3p => demo}/chttp.c (99%) rename {3p => demo}/chttp.h (100%) rename {3p => demo}/wl.c (100%) rename {3p => demo}/wl.h (100%) rename {3p => src}/crypt_blowfish.c (100%) rename {3p => src}/crypt_blowfish.h (100%) diff --git a/Makefile b/Makefile index 52d55ea..8f3dd8c 100644 --- a/Makefile +++ b/Makefile @@ -16,7 +16,7 @@ sqlite3.o: demo/sqlite3.c gcc -o $@ -c $< cozisnews$(EXT): demo/main.c cweb.c cweb.h sqlite3.o - gcc -o $@ demo/main.c cweb.c sqlite3.o $(FLAGS) + gcc -o $@ demo/main.c demo/chttp.c demo/wl.c cweb.c sqlite3.o $(FLAGS) -Idemo clean: rm *.o *.out *.exe diff --git a/amalg.py b/amalg.py index db06cc1..a08138c 100644 --- a/amalg.py +++ b/amalg.py @@ -35,14 +35,8 @@ header.save("cweb.h") source = Amalgamator() source.append_text("#include \"cweb.h\"\n") -source.append_text("#define WL_NOINCLUDE\n") -source.append_text("#define HTTP_NOINCLUDE\n") source.append_text("#define CRYPT_BLOWFISH_NOINCLUDE") -source.append_file("3p/chttp.h") -source.append_file("3p/chttp.c") -source.append_file("3p/crypt_blowfish.h") -source.append_file("3p/crypt_blowfish.c") -source.append_file("3p/wl.h") -source.append_file("3p/wl.c") +source.append_file("src/crypt_blowfish.h") +source.append_file("src/crypt_blowfish.c") source.append_file("src/main.c") source.save("cweb.c") diff --git a/cweb.c b/cweb.c index e80a02d..df221d7 100644 --- a/cweb.c +++ b/cweb.c @@ -1,6109 +1,7 @@ #include "cweb.h" -#define WL_NOINCLUDE -#define HTTP_NOINCLUDE #define CRYPT_BLOWFISH_NOINCLUDE //////////////////////////////////////////////////////////////////////////////////////// -// 3p/chttp.h -//////////////////////////////////////////////////////////////////////////////////////// - -#ifndef HTTP_AMALGAMATION -#define HTTP_AMALGAMATION - -// This file was generated automatically. Do not modify directly! - -//////////////////////////////////////////////////////////////////////////////////////// -// src/basic.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/basic.h" -#ifndef CHTTP_BASIC_INCLUDED -#define CHTTP_BASIC_INCLUDED - -#include - -// String type used throughout cHTTP. -typedef struct { - char *ptr; - int len; -} HTTP_String; - -// Compare two strings and return true iff they have -// the same contents. -bool http_streq(HTTP_String s1, HTTP_String s2); - -// Compre two strings case-insensitively (uppercase and -// lowercase versions of a letter are considered the same) -// and return true iff they have the same contents. -bool http_streqcase(HTTP_String s1, HTTP_String s2); - -// Remove spaces and tabs from the start and the end of -// a string. This doesn't change the original string and -// the new one references the contents of the original one. -HTTP_String http_trim(HTTP_String s); - -// TODO: comment -void print_bytes(HTTP_String prefix, HTTP_String src); - -// Macro to simplify converting string literals to -// HTTP_String. -// -// Instead of doing this: -// -// char *s = "some string"; -// -// You do this: -// -// HTTP_String s = HTTP_STR("some string") -// -// This is a bit cumbersome, but better than null-terminated -// strings, having a pointer and length variable pairs whenever -// a function operates on a string. If this wasn't a library -// I would have done for -// -// #define S(X) ... -// -// But I don't want to cause collisions with user code. -#define HTTP_STR(X) ((HTTP_String) {(X), sizeof(X)-1}) - -// Returns the number of items of a static array. -#define HTTP_COUNT(X) (sizeof(X) / sizeof((X)[0])) - -// TODO: comment -#define HTTP_UNPACK(X) (X).len, (X).ptr - -// Macro used to make invariants of the code more explicit. -// -// Say you have some function that operates on two integers -// and that by design their sum is always 100. This macro is -// useful to make that explicit: -// -// void func(int a, int b) -// { -// HTTP_ASSERT(a + b == 100); -// ... -// } -// -// Assertions are about documentation, *not* error management. -// -// In non-release builds (where NDEBUG is not defined) asserted -// expressions are evaluated and if not true, the program is halted. -// This is quite nice as they offer a way to document code in -// a way that can be checked at runtime, unlike regular comments -// like this one. -#ifdef NDEBUG -#define HTTP_ASSERT(X) ((void) 0) -#else -#define HTTP_ASSERT(X) {if (!(X)) { __builtin_trap(); }} -#endif - -#endif // CHTTP_BASIC_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/parse.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/parse.h" -#ifndef PARSE_INCLUDED -#define PARSE_INCLUDED - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#endif - -#define HTTP_MAX_HEADERS 32 - -typedef struct { - unsigned int data; -} HTTP_IPv4; - -typedef struct { - unsigned short data[8]; -} HTTP_IPv6; - -typedef enum { - HTTP_HOST_MODE_VOID = 0, - HTTP_HOST_MODE_NAME, - HTTP_HOST_MODE_IPV4, - HTTP_HOST_MODE_IPV6, -} HTTP_HostMode; - -typedef struct { - HTTP_HostMode mode; - HTTP_String text; - union { - HTTP_String name; - HTTP_IPv4 ipv4; - HTTP_IPv6 ipv6; - }; -} HTTP_Host; - -typedef struct { - HTTP_String userinfo; - HTTP_Host host; - int port; -} HTTP_Authority; - -// ZII -typedef struct { - HTTP_String scheme; - HTTP_Authority authority; - HTTP_String path; - HTTP_String query; - HTTP_String fragment; -} HTTP_URL; - -typedef enum { - HTTP_METHOD_GET, - HTTP_METHOD_HEAD, - HTTP_METHOD_POST, - HTTP_METHOD_PUT, - HTTP_METHOD_DELETE, - HTTP_METHOD_CONNECT, - HTTP_METHOD_OPTIONS, - HTTP_METHOD_TRACE, - HTTP_METHOD_PATCH, -} HTTP_Method; - -typedef struct { - HTTP_String name; - HTTP_String value; -} HTTP_Header; - -typedef struct { - bool secure; - HTTP_Method method; - HTTP_URL url; - int minor; - int num_headers; - HTTP_Header headers[HTTP_MAX_HEADERS]; - HTTP_String body; -} HTTP_Request; - -typedef struct { - void* context; - int minor; - int status; - HTTP_String reason; - int num_headers; - HTTP_Header headers[HTTP_MAX_HEADERS]; - HTTP_String body; -} HTTP_Response; - -int http_parse_ipv4 (char *src, int len, HTTP_IPv4 *ipv4); -int http_parse_ipv6 (char *src, int len, HTTP_IPv6 *ipv6); -int http_parse_url (char *src, int len, HTTP_URL *url); -int http_parse_request (char *src, int len, HTTP_Request *req); -int http_parse_response (char *src, int len, HTTP_Response *res); - -int http_find_header (HTTP_Header *headers, int num_headers, HTTP_String name); - -HTTP_String http_get_cookie (HTTP_Request *req, HTTP_String name); -HTTP_String http_get_param (HTTP_String body, HTTP_String str, char *mem, int cap); -int http_get_param_i (HTTP_String body, HTTP_String str); - - -#endif // PARSE_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/engine.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/engine.h" -#ifndef HTTP_ENGINE_INCLUDED -#define HTTP_ENGINE_INCLUDED - -#ifndef HTTP_AMALGAMATION -#include "parse.h" -#endif - -typedef enum { - HTTP_MEMFUNC_MALLOC, - HTTP_MEMFUNC_FREE, -} HTTP_MemoryFuncTag; - -typedef void*(*HTTP_MemoryFunc)(HTTP_MemoryFuncTag tag, - void *ptr, int len, void *data); - -typedef struct { - - HTTP_MemoryFunc memfunc; - void *memfuncdata; - - unsigned long long curs; - - char* data; - unsigned int head; - unsigned int size; - unsigned int used; - unsigned int limit; - - char* read_target; - unsigned int read_target_size; - - int flags; -} HTTP_ByteQueue; - -typedef unsigned long long HTTP_ByteQueueOffset; - -#define HTTP_ENGINE_STATEBIT_CLIENT (1 << 0) -#define HTTP_ENGINE_STATEBIT_CLOSED (1 << 1) -#define HTTP_ENGINE_STATEBIT_RECV_BUF (1 << 2) -#define HTTP_ENGINE_STATEBIT_RECV_ACK (1 << 3) -#define HTTP_ENGINE_STATEBIT_SEND_BUF (1 << 4) -#define HTTP_ENGINE_STATEBIT_SEND_ACK (1 << 5) -#define HTTP_ENGINE_STATEBIT_REQUEST (1 << 6) -#define HTTP_ENGINE_STATEBIT_RESPONSE (1 << 7) -#define HTTP_ENGINE_STATEBIT_PREP (1 << 8) -#define HTTP_ENGINE_STATEBIT_PREP_HEADER (1 << 9) -#define HTTP_ENGINE_STATEBIT_PREP_BODY_BUF (1 << 10) -#define HTTP_ENGINE_STATEBIT_PREP_BODY_ACK (1 << 11) -#define HTTP_ENGINE_STATEBIT_PREP_ERROR (1 << 12) -#define HTTP_ENGINE_STATEBIT_PREP_URL (1 << 13) -#define HTTP_ENGINE_STATEBIT_PREP_STATUS (1 << 14) -#define HTTP_ENGINE_STATEBIT_CLOSING (1 << 15) - -typedef enum { - HTTP_ENGINE_STATE_NONE = 0, - HTTP_ENGINE_STATE_CLIENT_PREP_URL = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_URL, - HTTP_ENGINE_STATE_CLIENT_PREP_HEADER = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER, - HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF, - HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK, - HTTP_ENGINE_STATE_CLIENT_PREP_ERROR = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR, - HTTP_ENGINE_STATE_CLIENT_SEND_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_BUF, - HTTP_ENGINE_STATE_CLIENT_SEND_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_SEND_ACK, - HTTP_ENGINE_STATE_CLIENT_RECV_BUF = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_BUF, - HTTP_ENGINE_STATE_CLIENT_RECV_ACK = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RECV_ACK, - HTTP_ENGINE_STATE_CLIENT_READY = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_RESPONSE, - HTTP_ENGINE_STATE_CLIENT_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT | HTTP_ENGINE_STATEBIT_CLOSED, - HTTP_ENGINE_STATE_SERVER_RECV_BUF = HTTP_ENGINE_STATEBIT_RECV_BUF, - HTTP_ENGINE_STATE_SERVER_RECV_ACK = HTTP_ENGINE_STATEBIT_RECV_ACK, - HTTP_ENGINE_STATE_SERVER_PREP_STATUS = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_STATUS, - HTTP_ENGINE_STATE_SERVER_PREP_HEADER = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_HEADER, - HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_BUF, - HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_BODY_ACK, - HTTP_ENGINE_STATE_SERVER_PREP_ERROR = HTTP_ENGINE_STATEBIT_REQUEST | HTTP_ENGINE_STATEBIT_PREP | HTTP_ENGINE_STATEBIT_PREP_ERROR, - HTTP_ENGINE_STATE_SERVER_SEND_BUF = HTTP_ENGINE_STATEBIT_SEND_BUF, - HTTP_ENGINE_STATE_SERVER_SEND_ACK = HTTP_ENGINE_STATEBIT_SEND_ACK, - HTTP_ENGINE_STATE_SERVER_CLOSED = HTTP_ENGINE_STATEBIT_CLIENT, -} HTTP_EngineState; - -typedef struct { - HTTP_EngineState state; - HTTP_ByteQueue input; - HTTP_ByteQueue output; - int numexch; - int reqsize; - int closing; - int keepalive; - HTTP_ByteQueueOffset response_offset; - HTTP_ByteQueueOffset content_length_offset; - HTTP_ByteQueueOffset content_length_value_offset; - union { - HTTP_Request req; - HTTP_Response res; - } result; -} HTTP_Engine; - -void http_engine_init (HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata); -void http_engine_free (HTTP_Engine *eng); - -void http_engine_close (HTTP_Engine *eng); -HTTP_EngineState http_engine_state (HTTP_Engine *eng); - -const char* http_engine_statestr(HTTP_EngineState state); // TODO: remove - -char* http_engine_recvbuf (HTTP_Engine *eng, int *cap); -void http_engine_recvack (HTTP_Engine *eng, int num); -char* http_engine_sendbuf (HTTP_Engine *eng, int *len); -void http_engine_sendack (HTTP_Engine *eng, int num); - -HTTP_Request* http_engine_getreq (HTTP_Engine *eng); -HTTP_Response* http_engine_getres (HTTP_Engine *eng); - -void http_engine_url (HTTP_Engine *eng, HTTP_Method method, HTTP_String url, int minor); -void http_engine_status (HTTP_Engine *eng, int status); -void http_engine_header (HTTP_Engine *eng, HTTP_String str); -void http_engine_body (HTTP_Engine *eng, HTTP_String str); -void http_engine_bodycap (HTTP_Engine *eng, int mincap); -char* http_engine_bodybuf (HTTP_Engine *eng, int *cap); -void http_engine_bodyack (HTTP_Engine *eng, int num); -void http_engine_done (HTTP_Engine *eng); -void http_engine_undo (HTTP_Engine *eng); - -#endif // HTTP_ENGINE_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/cert.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/cert.h" -#ifndef CERT_INCLUDED -#define CERT_INCLUDED - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#endif - -// This is an utility to create self-signed certificates -// useful when testing HTTPS servers locally. This is only -// meant to be used by people starting out with a library -// and simplifying the zero to one phase. -// -// The C, O, and CN are respectively country name, organization name, -// and common name of the certificate. For instance: -// -// C="IT" -// O="My Organization" -// CN="my_website.com" -// -// The output is a certificate file in PEM format and a private -// key file with the key used to sign the certificate. -int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, - HTTP_String cert_file, HTTP_String key_file); - -#endif // CERT_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/client.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/client.h" -#ifndef CLIENT_INCLUDED -#define CLIENT_INCLUDED - -#include - -#ifndef HTTP_AMALGAMATION -#include "parse.h" -#endif - -// Initialize the global state of cHTTP. -// -// cHTTP tries to avoid global state. What this function -// does is call the global initialization functions of -// its dependencies (OpenSSL and Winsock) -int http_global_init(void); - -// Free the global state of cHTTP. -void http_global_free(void); - -// Opaque type describing an "HTTP client". Any request -// that is started must always be associated to an HTTP -// client object. -typedef struct HTTP_Client HTTP_Client; - -// Handle for a pending request. This should be considered -// opaque. Don't read or modify its fields! -typedef struct { - void *data0; - int data1; - int data2; -} HTTP_RequestBuilder; - -// Initialize a client object. If something goes wrong, -// NULL is returned. -HTTP_Client *http_client_init(void); - -// Deinitialize a client object -void http_client_free(HTTP_Client *client); - -// Create a request object associated to the given client. -// On success, 0 is returned and the handle is initialized. -// On error, -1 is returned. -int http_client_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder); - -void http_request_builder_user_data(HTTP_RequestBuilder builder, void *user_data); - -// Enable/disable I/O tracing for the specified request. -// This must be done when the request is in the initialization -// phase. -void http_request_builder_trace(HTTP_RequestBuilder builder, bool trace); - -// Set the method and URL of the specified request object. -// This must be the first thing you do after http_client_request -// is called (you may http_request_trace before, but nothing -// else!) -void http_request_builder_line(HTTP_RequestBuilder builder, HTTP_Method method, HTTP_String url); - -// Append a header to the specified request. You must call -// this after http_request_line and may do so multiple times. -void http_request_builder_header(HTTP_RequestBuilder builder, HTTP_String str); - -// Append some data to the request's body. You must call -// this after either http_request_line or http_request_header. -void http_request_builder_body(HTTP_RequestBuilder builder, HTTP_String str); - -// Mark the initialization of the request as completed and -// perform the request. -void http_request_builder_submit(HTTP_RequestBuilder builder); - -// Free resources associated to a request. This must be called -// after the request has completed. -// -// TODO: allow aborting pending requests -void http_response_free(HTTP_Response *res); - -// Wait for the completion of one request associated to -// the client. The handle of the resolved request is returned -// through the handle output parameter. If you're not -// interested in which request completed (like when you -// have only one pending request), you can pass NULL. -// -// On error -1 is retutned, else 0 is returned and the -// handle is initialized. -// -// Note that calling this function when no requests are -// pending is considered an error. -int http_client_wait(HTTP_Client *client, HTTP_Response **res, void **user_data); - -// TODO: comment -HTTP_Response *http_get(HTTP_String url, - HTTP_String *headers, int num_headers); - -// TODO: comment -HTTP_Response *http_post(HTTP_String url, - HTTP_String *headers, int num_headers, - HTTP_String body); - -#endif // CLIENT_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/server.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/server.h" -#ifndef HTTP_SERVER_INCLUDED -#define HTTP_SERVER_INCLUDED - -#include - -#ifndef HTTP_AMALGAMATION -#include "parse.h" -#endif - -typedef struct { - void *data0; - int data1; - int data2; -} HTTP_ResponseBuilder; - -typedef struct HTTP_Server HTTP_Server; - -HTTP_Server *http_server_init(HTTP_String addr, uint16_t port); - -HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port, - uint16_t secure_port, HTTP_String cert_key, HTTP_String private_key); - -void http_server_free (HTTP_Server *server); -int http_server_wait (HTTP_Server *server, HTTP_Request **req, HTTP_ResponseBuilder *handle); -int http_server_add_website (HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file); -void http_response_builder_status (HTTP_ResponseBuilder res, int status); -void http_response_builder_header (HTTP_ResponseBuilder res, HTTP_String str); -void http_response_builder_body (HTTP_ResponseBuilder res, HTTP_String str); -void http_response_builder_bodycap (HTTP_ResponseBuilder res, int mincap); -char* http_response_builder_bodybuf (HTTP_ResponseBuilder res, int *cap); -void http_response_builder_bodyack (HTTP_ResponseBuilder res, int num); -void http_response_builder_undo (HTTP_ResponseBuilder res); -void http_response_builder_done (HTTP_ResponseBuilder res); - -#endif // HTTP_SERVER_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_ResponseBuilder, void*);; - -HTTP_Router* http_router_init (void); -void http_router_free (HTTP_Router *router); -void http_router_resolve (HTTP_Router *router, HTTP_Request *req, HTTP_ResponseBuilder res); -void http_router_dir (HTTP_Router *router, HTTP_String endpoint, HTTP_String path); -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 -#endif // HTTP_AMALGAMATION - -//////////////////////////////////////////////////////////////////////////////////////// -// 3p/chttp.c -//////////////////////////////////////////////////////////////////////////////////////// - -#ifndef HTTP_NOINCLUDE -#include "chttp.h" -#endif - -//////////////////////////////////////////////////////////////////////////////////////// -// src/sec.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/sec.h" -#ifndef SEC_INCLUDED -#define SEC_INCLUDED - - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#endif - -#ifndef HTTPS_ENABLED - -typedef struct { -} SecureContext; - -#else - -#define MAX_CERTS 10 - -#include - -#include - -typedef struct { - char domain[128]; - SSL_CTX *ctx; -} CertData; - -typedef struct { - - bool is_server; - - SSL_CTX *ctx; - - // Only used when server - int num_certs; - CertData certs[MAX_CERTS]; - -} SecureContext; - -#endif - -void secure_context_global_init(void); -void secure_context_global_free(void); - -int secure_context_init_as_client(SecureContext *sec); - -int secure_context_init_as_server(SecureContext *sec, - HTTP_String cert_file, HTTP_String key_file); - -int secure_context_add_cert(SecureContext *sec, - HTTP_String domain, HTTP_String cert_file, - HTTP_String key_file); - -void secure_context_free(SecureContext *sec); - -#endif // SEC_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/socket_raw.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/socket_raw.h" -#ifndef SOCKET_RAW_INCLUDED -#define SOCKET_RAW_INCLUDED - -#include -#include - -#ifdef _WIN32 -#include -#define RAW_SOCKET SOCKET -#define BAD_SOCKET INVALID_SOCKET -#define POLL WSAPoll -#define CLOSE_SOCKET closesocket -#endif - -#ifdef __linux__ -#include -#include -#define RAW_SOCKET int -#define BAD_SOCKET -1 -#define POLL poll -#define CLOSE_SOCKET close -#endif - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#endif - -int socket_raw_global_init(void); -void socket_raw_global_free(void); - -int set_socket_blocking(RAW_SOCKET sock, bool value); - -RAW_SOCKET listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int backlog); - -#endif // SOCKET_RAW_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/socket.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/socket.h" -#ifndef SOCKET_INCLUDED -#define SOCKET_INCLUDED - -// This module implements the socket state machine to encapsulate -// the complexity of non-blocking TCP and TLS sockets. -// -// A socket is represented by the "Socket" structure, which may -// be in a number of states. As far as an user of the interface -// is concerned, the socket may be DIED, READY, or in an internal -// state that requires waiting for an event. Therefore, if the -// socket is not DIED or READY, the user needs to wait for the -// events specified in the [socket->events] field, then call the -// socket_update function. At some point the socket will become -// either READY or DIED. -// -// When the socket reaches the DIED state, the user must call -// socket_free. -// -// If the socket is ESTABLISHED_READY, the user may call socket_read, -// socket_write, or socket_close on it. - -#ifndef HTTP_AMALGAMATION -#include "sec.h" -#include "parse.h" -#include "socket_raw.h" -#endif - -typedef struct PendingConnect PendingConnect; - -// These should only be relevant to socket.c -typedef enum { - SOCKET_STATE_FREE, - SOCKET_STATE_DIED, - SOCKET_STATE_ESTABLISHED_WAIT, - SOCKET_STATE_ESTABLISHED_READY, - SOCKET_STATE_PENDING, - SOCKET_STATE_ACCEPTED, - SOCKET_STATE_CONNECTED, - SOCKET_STATE_CONNECTING, - SOCKET_STATE_SHUTDOWN, -} SocketState; - -typedef struct { - SocketState state; - - RAW_SOCKET raw; - int events; - - void *user_data; - PendingConnect *pending_connect; - -#ifdef HTTPS_ENABLED - SSL *ssl; -#endif - - SecureContext *sec; - -} Socket; - -void socket_connect(Socket *sock, SecureContext *sec, HTTP_String hostname, uint16_t port, void *user_data); -void socket_connect_ipv4(Socket *sock, SecureContext *sec, HTTP_IPv4 addr, uint16_t port, void *user_data); -void socket_connect_ipv6(Socket *sock, SecureContext *sec, HTTP_IPv6 addr, uint16_t port, void *user_data); -void socket_accept(Socket *sock, SecureContext *sec, RAW_SOCKET raw); -void socket_update(Socket *sock); -void socket_close(Socket *sock); -bool socket_ready(Socket *sock); -bool socket_died(Socket *sock); -int socket_read(Socket *sock, char *dst, int max); -int socket_write(Socket *sock, char *src, int len); -void socket_free(Socket *sock); -bool socket_secure(Socket *sock); -void socket_set_user_data(Socket *sock, void *user_data); -void* socket_get_user_data(Socket *sock); - -#endif // SOCKET_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/socket_pool.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/socket_pool.h" -#ifndef SOCKET_POOL_INCLUDED -#define SOCKET_POOL_INCLUDED - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#include "socket.h" -#include "socket_raw.h" -#endif - -typedef struct SocketPool SocketPool; - -typedef int SocketHandle; - -typedef enum { - SOCKET_EVENT_DIED, - SOCKET_EVENT_READY, - SOCKET_EVENT_ERROR, - SOCKET_EVENT_SIGNAL, -} SocketEventType; - -typedef struct { - SocketEventType type; - SocketHandle handle; - void *user_data; -} SocketEvent; - -int socket_pool_global_init(void); -void socket_pool_global_free(void); - -SocketPool *socket_pool_init(HTTP_String addr, - uint16_t port, uint16_t secure_port, int max_socks, - bool reuse_addr, int backlog, HTTP_String cert_file, - HTTP_String key_file); - -void socket_pool_free(SocketPool *pool); - -int socket_pool_add_cert(SocketPool *pool, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file); - -SocketEvent socket_pool_wait(SocketPool *pool); - -void socket_pool_set_user_data(SocketPool *pool, SocketHandle handle, void *user_data); - -void socket_pool_close(SocketPool *pool, SocketHandle handle); - -int socket_pool_connect(SocketPool *pool, bool secure, - HTTP_String addr, uint16_t port, void *user_data); - -int socket_pool_connect_ipv4(SocketPool *pool, bool secure, - HTTP_IPv4 addr, uint16_t port, void *user_data); - -int socket_pool_connect_ipv6(SocketPool *pool, bool secure, - HTTP_IPv6 addr, uint16_t port, void *user_data); - -int socket_pool_read(SocketPool *pool, SocketHandle handle, char *dst, int len); - -int socket_pool_write(SocketPool *pool, SocketHandle handle, char *src, int len); - -bool socket_pool_secure(SocketPool *pool, SocketHandle handle); - -#endif // SOCKET_POOL_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/basic.c -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/basic.c" -#include -#include - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#endif - -bool http_streq(HTTP_String s1, HTTP_String s2) -{ - if (s1.len != s2.len) - return false; - - for (int i = 0; i < s1.len; i++) - if (s1.ptr[i] != s2.ptr[i]) - return false; - - return true; -} - -static char to_lower(char c) -{ - if (c >= 'A' && c <= 'Z') - return c - 'A' + 'a'; - return c; -} - -bool http_streqcase(HTTP_String s1, HTTP_String s2) -{ - if (s1.len != s2.len) - return false; - - for (int i = 0; i < s1.len; i++) - if (to_lower(s1.ptr[i]) != to_lower(s2.ptr[i])) - return false; - - return true; -} - -HTTP_String http_trim(HTTP_String s) -{ - int i = 0; - while (i < s.len && (s.ptr[i] == ' ' || s.ptr[i] == '\t')) - i++; - - if (i == s.len) { - s.ptr = NULL; - s.len = 0; - } else { - s.ptr += i; - s.len -= i; - while (s.ptr[s.len-1] == ' ' || s.ptr[s.len-1] == '\t') - s.len--; - } - - return s; -} - -static bool is_printable(char c) -{ - return c >= ' ' && c <= '~'; -} - -#include -void print_bytes(HTTP_String prefix, HTTP_String src) -{ - if (src.len == 0) - return; - - FILE *stream = stdout; - - bool new_line = true; - int cur = 0; - for (;;) { - int start = cur; - - while (cur < src.len && is_printable(src.ptr[cur])) - cur++; - - if (new_line) { - fwrite(prefix.ptr, 1, prefix.len, stream); - new_line = false; - } - - fwrite(src.ptr + start, 1, cur - start, stream); - - if (cur == src.len) - break; - - if (src.ptr[cur] == '\n') { - putc('\\', stream); - putc('n', stream); - putc('\n', stream); - new_line = true; - } else if (src.ptr[cur] == '\r') { - putc('\\', stream); - putc('r', stream); - } else { - putc('.', stream); - } - cur++; - } - putc('\n', stream); -} - -//////////////////////////////////////////////////////////////////////////////////////// -// src/parse.c -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/parse.c" -#include -#include -#include -#include -#include -#include - -#ifndef HTTP_AMALGAMATION -#include "parse.h" -#include "basic.h" -#endif - -// From RFC 9112 -// request-target = origin-form -// / absolute-form -// / authority-form -// / asterisk-form -// origin-form = absolute-path [ "?" query ] -// absolute-form = absolute-URI -// authority-form = uri-host ":" port -// asterisk-form = "*" -// -// From RFC 9110 -// URI-reference = -// absolute-URI = -// relative-part = -// authority = -// uri-host = -// port = -// path-abempty = -// segment = -// query = -// -// absolute-path = 1*( "/" segment ) -// partial-URI = relative-part [ "?" query ] -// -// From RFC 3986: -// segment = *pchar -// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" -// pct-encoded = "%" HEXDIG HEXDIG -// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" -// / "*" / "+" / "," / ";" / "=" -// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" -// query = *( pchar / "/" / "?" ) -// absolute-URI = scheme ":" hier-part [ "?" query ] -// hier-part = "//" authority path-abempty -// / path-absolute -// / path-rootless -// / path-empty -// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) - -typedef struct { - char *src; - int len; - int cur; -} Scanner; - -static int is_digit(char c) -{ - return c >= '0' && c <= '9'; -} - -static int is_alpha(char c) -{ - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); -} - -static int is_hex_digit(char c) -{ - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); -} - -// From RFC 3986: -// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" -// / "*" / "+" / "," / ";" / "=" -static int is_sub_delim(char c) -{ - return c == '!' || c == '$' || c == '&' || c == '\'' - || c == '(' || c == ')' || c == '*' || c == '+' - || c == ',' || c == ';' || c == '='; -} - -// From RFC 3986: -// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" -static int is_unreserved(char c) -{ - return is_alpha(c) || is_digit(c) - || c == '-' || c == '.' - || c == '_' || c == '~'; -} - -// From RFC 3986: -// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" -static int is_pchar(char c) -{ - return is_unreserved(c) || is_sub_delim(c) || c == ':' || c == '@'; -} - -static int is_tchar(char c) -{ - return is_digit(c) || is_alpha(c) - || c == '!' || c == '#' || c == '$' - || c == '%' || c == '&' || c == '\'' - || c == '*' || c == '+' || c == '-' - || c == '.' || c == '^' || c == '_' - || c == '~'; -} - -static int is_vchar(char c) -{ - return c >= ' ' && c <= '~'; -} - -#define CONSUME_OPTIONAL_SEQUENCE(scanner, func) \ - while ((scanner)->cur < (scanner)->len && (func)((scanner)->src[(scanner)->cur])) \ - (scanner)->cur++; - -static int -consume_absolute_path(Scanner *s) -{ - if (s->cur == s->len || s->src[s->cur] != '/') - return -1; // ERROR - s->cur++; - - for (;;) { - - CONSUME_OPTIONAL_SEQUENCE(s, is_pchar); - - if (s->cur == s->len || s->src[s->cur] != '/') - break; - s->cur++; - } - - return 0; -} - -// If abempty=1: -// path-abempty = *( "/" segment ) -// else: -// path-absolute = "/" [ segment-nz *( "/" segment ) ] -// path-rootless = segment-nz *( "/" segment ) -// path-empty = 0 -static int parse_path(Scanner *s, HTTP_String *path, int abempty) -{ - int start = s->cur; - - if (abempty) { - - // path-abempty - while (s->cur < s->len && s->src[s->cur] == '/') { - do - s->cur++; - while (s->cur < s->len && is_pchar(s->src[s->cur])); - } - - } else if (s->cur < s->len && (s->src[s->cur] == '/')) { - - // path-absolute - s->cur++; - if (s->cur < s->len && is_pchar(s->src[s->cur])) { - s->cur++; - for (;;) { - - CONSUME_OPTIONAL_SEQUENCE(s, is_pchar); - - if (s->cur == s->len || s->src[s->cur] != '/') - break; - s->cur++; - } - } - - } else if (s->cur < s->len && is_pchar(s->src[s->cur])) { - - // path-rootless - s->cur++; - for (;;) { - - CONSUME_OPTIONAL_SEQUENCE(s, is_pchar) - - if (s->cur == s->len || s->src[s->cur] != '/') - break; - s->cur++; - } - - } else { - // path->empty - // (do nothing) - } - - *path = (HTTP_String) { - s->src + start, - s->cur - start, - }; - if (path->len == 0) - path->ptr = NULL; - - return 0; -} - -// RFC 3986: -// query = *( pchar / "/" / "?" ) -static int is_query(char c) -{ - return is_pchar(c) || c == '/' || c == '?'; -} - -// RFC 3986: -// fragment = *( pchar / "/" / "?" ) -static int is_fragment(char c) -{ - return is_pchar(c) || c == '/' || c == '?'; -} - -static int little_endian(void) -{ - uint16_t x = 1; - return *((uint8_t*) &x); -} - -static void invert_bytes(void *p, int len) -{ - char *c = p; - for (int i = 0; i < len/2; i++) { - char tmp = c[i]; - c[i] = c[len-i-1]; - c[len-i-1] = tmp; - } -} - -static int parse_ipv4(Scanner *s, HTTP_IPv4 *ipv4) -{ - unsigned int out = 0; - int i = 0; - for (;;) { - - if (s->cur == s->len || !is_digit(s->src[s->cur])) - return -1; - - int b = 0; - do { - int x = s->src[s->cur++] - '0'; - if (b > (UINT8_MAX - x) / 10) - return -1; - b = b * 10 + x; - } while (s->cur < s->len && is_digit(s->src[s->cur])); - - out <<= 8; - out |= (unsigned char) b; - - i++; - if (i == 4) - break; - - if (s->cur == s->len || s->src[s->cur] != '.') - return -1; - s->cur++; - } - - if (little_endian()) - invert_bytes(&out, 4); - - ipv4->data = out; - return 0; -} - -static int hex_digit_to_int(char c) -{ - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - if (c >= '0' && c <= '9') return c - '0'; - return -1; -} - -static int parse_ipv6_comp(Scanner *s) -{ - unsigned short buf; - - if (s->cur == s->len || !is_hex_digit(s->src[s->cur])) - return -1; - buf = hex_digit_to_int(s->src[s->cur]); - s->cur++; - - if (s->cur == s->len || !is_hex_digit(s->src[s->cur])) - return buf; - buf <<= 4; - buf |= hex_digit_to_int(s->src[s->cur]); - s->cur++; - - if (s->cur == s->len || !is_hex_digit(s->src[s->cur])) - return buf; - buf <<= 4; - buf |= hex_digit_to_int(s->src[s->cur]); - s->cur++; - - if (s->cur == s->len || !is_hex_digit(s->src[s->cur])) - return buf; - buf <<= 4; - buf |= hex_digit_to_int(s->src[s->cur]); - s->cur++; - - return (int) buf; -} - -static int parse_ipv6(Scanner *s, HTTP_IPv6 *ipv6) -{ - unsigned short head[8]; - unsigned short tail[8]; - int head_len = 0; - int tail_len = 0; - - if (s->len - s->cur > 1 - && s->src[s->cur+0] == ':' - && s->src[s->cur+1] == ':') - s->cur += 2; - else { - - for (;;) { - - int ret = parse_ipv6_comp(s); - if (ret < 0) return ret; - - head[head_len++] = (unsigned short) ret; - if (head_len == 8) break; - - if (s->cur == s->len || s->src[s->cur] != ':') - return -1; - s->cur++; - - if (s->cur < s->len && s->src[s->cur] == ':') { - s->cur++; - break; - } - } - } - - if (head_len < 8) { - while (s->cur < s->len && is_hex_digit(s->src[s->cur])) { - - int ret = parse_ipv6_comp(s); - if (ret < 0) return ret; - - tail[tail_len++] = (unsigned short) ret; - if (head_len + tail_len == 8) break; - - if (s->cur == s->len || s->src[s->cur] != ':') - break; - s->cur++; - } - } - - for (int i = 0; i < head_len; i++) - ipv6->data[i] = head[i]; - - for (int i = 0; i < 8 - head_len - tail_len; i++) - ipv6->data[head_len + i] = 0; - - for (int i = 0; i < tail_len; i++) - ipv6->data[8 - tail_len + i] = tail[i]; - - if (little_endian()) - for (int i = 0; i < 8; i++) - invert_bytes(&ipv6->data[i], 2); - - return 0; -} - -// From RFC 3986: -// reg-name = *( unreserved / pct-encoded / sub-delims ) -static int is_regname(char c) -{ - return is_unreserved(c) || is_sub_delim(c); -} - -static int parse_regname(Scanner *s, HTTP_String *regname) -{ - if (s->cur == s->len || !is_regname(s->src[s->cur])) - return -1; - int start = s->cur; - do - s->cur++; - while (s->cur < s->len && is_regname(s->src[s->cur])); - regname->ptr = s->src + start; - regname->len = s->cur - start; - return 0; -} - -static int parse_host(Scanner *s, HTTP_Host *host) -{ - int ret; - if (s->cur < s->len && s->src[s->cur] == '[') { - - s->cur++; - - int start = s->cur; - HTTP_IPv6 ipv6; - ret = parse_ipv6(s, &ipv6); - if (ret < 0) return ret; - - host->mode = HTTP_HOST_MODE_IPV6; - host->ipv6 = ipv6; - host->text = (HTTP_String) { s->src + start, s->cur - start }; - - if (s->cur == s->len || s->src[s->cur] != ']') - return -1; - s->cur++; - - } else { - - int start = s->cur; - HTTP_IPv4 ipv4; - ret = parse_ipv4(s, &ipv4); - if (ret >= 0) { - host->mode = HTTP_HOST_MODE_IPV4; - host->ipv4 = ipv4; - } else { - s->cur = start; - - HTTP_String regname; - ret = parse_regname(s, ®name); - if (ret < 0) return ret; - - host->mode = HTTP_HOST_MODE_NAME; - host->name = regname; - } - host->text = (HTTP_String) { s->src + start, s->cur - start }; - } - - return 0; -} - -// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) -static int is_scheme_head(char c) -{ - return is_alpha(c); -} - -static int is_scheme_body(char c) -{ - return is_alpha(c) - || is_digit(c) - || c == '+' - || c == '-' - || c == '.'; -} - -// userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) -static int is_userinfo(char c) -{ - return is_unreserved(c) || is_sub_delim(c) || c == ':'; // TODO: PCT encoded -} - -// authority = [ userinfo "@" ] host [ ":" port ] -static int parse_authority(Scanner *s, HTTP_Authority *authority) -{ - HTTP_String userinfo; - { - int start = s->cur; - - CONSUME_OPTIONAL_SEQUENCE(s, is_userinfo); - - if (s->cur < s->len && s->src[s->cur] == '@') { - userinfo = (HTTP_String) { - s->src + start, - s->cur - start - }; - s->cur++; - } else { - // Rollback - s->cur = start; - userinfo = (HTTP_String) {NULL, 0}; - } - } - - HTTP_Host host; - { - int ret = parse_host(s, &host); - if (ret < 0) - return ret; - } - - int port = 0; - if (s->cur < s->len && s->src[s->cur] == ':') { - s->cur++; - if (s->cur < s->len && is_digit(s->src[s->cur])) { - port = s->src[s->cur++] - '0'; - while (s->cur < s->len && is_digit(s->src[s->cur])) { - int x = s->src[s->cur++] - '0'; - if (port > (UINT16_MAX - x) / 10) - return -1; // ERROR: Port too big - port = port * 10 + x; - } - } - } - - authority->userinfo = userinfo; - authority->host = host; - authority->port = port; - return 0; -} - -static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment) -{ - HTTP_String scheme = {0}; - { - int start = s->cur; - if (s->cur == s->len || !is_scheme_head(s->src[s->cur])) - return -1; // ERROR: Missing scheme - do - s->cur++; - while (s->cur < s->len && is_scheme_body(s->src[s->cur])); - scheme = (HTTP_String) { - s->src + start, - s->cur - start, - }; - - if (s->cur == s->len || s->src[s->cur] != ':') - return -1; // ERROR: Missing ':' after scheme - s->cur++; - } - - int abempty = 0; - HTTP_Authority authority = {0}; - if (s->len - s->cur > 1 - && s->src[s->cur+0] == '/' - && s->src[s->cur+1] == '/') { - - s->cur += 2; - - int ret = parse_authority(s, &authority); - if (ret < 0) return ret; - - abempty = 1; - } - - HTTP_String path; - int ret = parse_path(s, &path, abempty); - if (ret < 0) return ret; - - HTTP_String query = {0}; - if (s->cur < s->len && s->src[s->cur] == '?') { - int start = s->cur; - do - s->cur++; - while (s->cur < s->len && is_query(s->src[s->cur])); - query = (HTTP_String) { - s->src + start, - s->cur - start, - }; - } - - HTTP_String fragment = {0}; - if (allow_fragment && s->cur < s->len && s->src[s->cur] == '#') { - int start = s->cur; - do - s->cur++; - while (s->cur < s->len && is_fragment(s->src[s->cur])); - fragment = (HTTP_String) { - s->src + start, - s->cur - start, - }; - } - - url->scheme = scheme; - url->authority = authority; - url->path = path; - url->query = query; - url->fragment = fragment; - - return 1; -} - -// authority-form = host ":" port -// host = IP-literal / IPv4address / reg-name -// IP-literal = "[" ( IPv6address / IPvFuture ) "]" -// reg-name = *( unreserved / pct-encoded / sub-delims ) -static int parse_authority_form(Scanner *s, HTTP_Host *host, int *port) -{ - int ret; - - ret = parse_host(s, host); - if (ret < 0) return ret; - - // Default port value - *port = 0; - - if (s->cur == s->len || s->src[s->cur] != ':') - return 0; // No port - s->cur++; - - if (s->cur == s->len || !is_digit(s->src[s->cur])) - return 0; // No port - - int buf = 0; - do { - int x = s->src[s->cur++] - '0'; - if (buf > (UINT16_MAX - x) / 10) - return -1; // ERROR - buf = buf * 10 + x; - } while (s->cur < s->len && is_digit(s->src[s->cur])); - - *port = buf; - return 0; -} - -static int parse_origin_form(Scanner *s, HTTP_String *path, HTTP_String *query) -{ - int ret, start; - - start = s->cur; - ret = consume_absolute_path(s); - if (ret < 0) return ret; - *path = (HTTP_String) { s->src + start, s->cur - start }; - - if (s->cur < s->len && s->src[s->cur] == '?') { - start = s->cur; - do - s->cur++; - while (s->cur < s->len && is_query(s->src[s->cur])); - *query = (HTTP_String) { s->src + start, s->cur - start }; - } else - *query = (HTTP_String) { NULL, 0 }; - - return 0; -} - -static int parse_asterisk_form(Scanner *s) -{ - if (s->len - s->cur < 2 - || s->src[s->cur+0] != '*' - || s->src[s->cur+1] != ' ') - return -1; - s->cur++; - return 0; -} - -static int parse_request_target(Scanner *s, HTTP_URL *url) -{ - int ret; - - memset(url, 0, sizeof(HTTP_URL)); - - // asterisk-form - ret = parse_asterisk_form(s); - if (ret >= 0) return ret; - - ret = parse_uri(s, url, 0); - if (ret >= 0) return ret; - - ret = parse_authority_form(s, &url->authority.host, &url->authority.port); - if (ret >= 0) return ret; - - ret = parse_origin_form(s, &url->path, &url->query); - if (ret >= 0) return ret; - - return -1; -} - -bool consume_str(Scanner *scan, HTTP_String token) -{ - HTTP_ASSERT(token.len > 0); - - if (token.len > scan->len - scan->cur) - return false; - - for (int i = 0; i < token.len; i++) - if (scan->src[scan->cur + i] != token.ptr[i]) - return false; - - scan->cur += token.len; - return true; -} - -static int is_header_body(char c) -{ - return is_vchar(c) || c == ' ' || c == '\t'; -} - -static int parse_headers(Scanner *s, HTTP_Header *headers, int max_headers) -{ - int num_headers = 0; - while (!consume_str(s, HTTP_STR("\r\n"))) { - - // RFC 9112: - // field-line = field-name ":" OWS field-value OWS - // - // RFC 9110: - // field-value = *field-content - // field-content = field-vchar - // [ 1*( SP / HTAB / field-vchar ) field-vchar ] - // field-vchar = VCHAR / obs-text - // obs-text = %x80-FF - - int start; - - if (s->cur == s->len || !is_tchar(s->src[s->cur])) - return -1; // ERROR - start = s->cur; - do - s->cur++; - while (s->cur < s->len && is_tchar(s->src[s->cur])); - HTTP_String name = { s->src + start, s->cur - start }; - - if (s->cur == s->len || s->src[s->cur] != ':') - return -1; // ERROR - s->cur++; - - start = s->cur; - CONSUME_OPTIONAL_SEQUENCE(s, is_header_body); - HTTP_String body = { s->src + start, s->cur - start }; - body = http_trim(body); - - if (num_headers < max_headers) - headers[num_headers++] = (HTTP_Header) { name, body }; - - if (!consume_str(s, HTTP_STR("\r\n"))) { - return -1; - } - } - - return num_headers; -} - -typedef enum { - TRANSFER_ENCODING_OPTION_CHUNKED, - TRANSFER_ENCODING_OPTION_COMPRESS, - TRANSFER_ENCODING_OPTION_DEFLATE, - TRANSFER_ENCODING_OPTION_GZIP, -} TransferEncodingOption; - -static bool is_space(char c) -{ - return c == ' ' || c == '\t'; -} - -static int -parse_transfer_encoding(HTTP_String src, TransferEncodingOption *dst, int max) -{ - Scanner s = { src.ptr, src.len, 0 }; - - int num = 0; - for (;;) { - - CONSUME_OPTIONAL_SEQUENCE(&s, is_space); - - TransferEncodingOption opt; - if (0) {} - else if (consume_str(&s, HTTP_STR("chunked"))) opt = TRANSFER_ENCODING_OPTION_CHUNKED; - else if (consume_str(&s, HTTP_STR("compress"))) opt = TRANSFER_ENCODING_OPTION_COMPRESS; - else if (consume_str(&s, HTTP_STR("deflate"))) opt = TRANSFER_ENCODING_OPTION_DEFLATE; - else if (consume_str(&s, HTTP_STR("gzip"))) opt = TRANSFER_ENCODING_OPTION_GZIP; - else return -1; // Invalid option - - if (num == max) - return -1; // Too many options - dst[num++] = opt; - - CONSUME_OPTIONAL_SEQUENCE(&s, is_space); - - if (s.cur == s.len) - break; - - if (s.src[s.cur] != ',') - return -1; // Missing comma separator - } - - return num; -} - -static int -parse_content_length(const char *src, int len, uint64_t *out) -{ - int cur = 0; - while (cur < len && (src[cur] == ' ' || src[cur] == '\t')) - cur++; - - if (cur == len || !is_digit(src[cur])) - return -1; - - uint64_t buf = 0; - do { - int d = src[cur++] - '0'; - if (buf > (UINT64_MAX - d) / 10) - return -1; - buf = buf * 10 + d; - } while (cur < len && is_digit(src[cur])); - - *out = buf; - return 0; -} - -static int parse_body(Scanner *s, - HTTP_Header *headers, int num_headers, - HTTP_String *body, bool body_expected) -{ - - // RFC 9112 section 6: - // The presence of a message body in a request is signaled by a Content-Length or - // Transfer-Encoding header field. Request message framing is independent of method - // semantics. - - int header_index = http_find_header(headers, num_headers, HTTP_STR("Transfer-Encoding")); - if (header_index != -1) { - - // RFC 9112 section 6.1: - // A server MAY reject a request that contains both Content-Length and Transfer-Encoding - // or process such a request in accordance with the Transfer-Encoding alone. Regardless, - // the server MUST close the connection after responding to such a request to avoid the - // potential attacks. - if (http_find_header(headers, num_headers, HTTP_STR("Content-Length")) != -1) - return -1; - - HTTP_String value = headers[header_index].value; - - // RFC 9112 section 6.1: - // If any transfer coding other than chunked is applied to a request's content, the - // sender MUST apply chunked as the final transfer coding to ensure that the message - // is properly framed. If any transfer coding other than chunked is applied to a - // response's content, the sender MUST either apply chunked as the final transfer - // coding or terminate the message by closing the connection. - - TransferEncodingOption opts[8]; - int num = parse_transfer_encoding(value, opts, HTTP_COUNT(opts)); - if (num != 1 || opts[0] != TRANSFER_ENCODING_OPTION_CHUNKED) - return -1; - - HTTP_String chunks_maybe[128]; - HTTP_String *chunks = chunks_maybe; - int num_chunks = 0; - int max_chunks = HTTP_COUNT(chunks_maybe); - - #define FREE_CHUNK_LIST \ - if (chunks != chunks_maybe) \ - free(chunks); - - char *content_start = s->src + s->cur; - - for (;;) { - - // RFC 9112 section 7.1: - // The chunked transfer coding wraps content in order to transfer it as a series of chunks, - // each with its own size indicator, followed by an OPTIONAL trailer section containing - // trailer fields. - - if (s->cur == s->len) { - FREE_CHUNK_LIST - return 0; // Incomplete request - } - - if (!is_hex_digit(s->src[s->cur])) { - FREE_CHUNK_LIST - return -1; - } - - int chunk_len = 0; - - do { - char c = s->src[s->cur++]; - int n = hex_digit_to_int(c); - if (chunk_len > (INT_MAX - n) / 16) { - FREE_CHUNK_LIST - return -1; // overflow - } - chunk_len = chunk_len * 16 + n; - } while (s->cur < s->len && is_hex_digit(s->src[s->cur])); - - if (s->cur == s->len) { - FREE_CHUNK_LIST - return 0; // Incomplete request - } - if (s->src[s->cur] != '\r') { - FREE_CHUNK_LIST - return -1; - } - s->cur++; - - if (s->cur == s->len) { - FREE_CHUNK_LIST - return 0; - } - if (s->src[s->cur] != '\n') { - FREE_CHUNK_LIST - return -1; - } - s->cur++; - - char *chunk_ptr = s->src + s->cur; - - if (chunk_len > s->len - s->cur) { - FREE_CHUNK_LIST - return 0; // Incomplete request - } - s->cur += chunk_len; - - if (s->cur == s->len) - return 0; // Incomplete request - if (s->src[s->cur] != '\r') { - FREE_CHUNK_LIST - return -1; - } - s->cur++; - - if (s->cur == s->len) { - FREE_CHUNK_LIST - return 0; // Incomplete request - } - if (s->src[s->cur] != '\n') { - FREE_CHUNK_LIST - return -1; - } - s->cur++; - - if (chunk_len == 0) - break; - - if (num_chunks == max_chunks) { - - max_chunks *= 2; - - HTTP_String *new_chunks = malloc(max_chunks * sizeof(HTTP_String)); - if (new_chunks == NULL) { - if (chunks != chunks_maybe) - free(chunks); - return -1; - } - - for (int i = 0; i < num_chunks; i++) - new_chunks[i] = chunks[i]; - - if (chunks != chunks_maybe) - free(chunks); - - chunks = new_chunks; - } - chunks[num_chunks++] = (HTTP_String) { chunk_ptr, chunk_len }; - } - - char *content_ptr = content_start; - for (int i = 0; i < num_chunks; i++) { - memmove(content_ptr, chunks[i].ptr, chunks[i].len); - content_ptr += chunks[i].len; - } - - *body = (HTTP_String) { - content_start, - content_ptr - content_start - }; - - if (chunks != chunks_maybe) - free(chunks); - - return 1; - } - - // RFC 9112 section 6.3: - // If a valid Content-Length header field is present without Transfer-Encoding, - // its decimal value defines the expected message body length in octets. - - header_index = http_find_header(headers, num_headers, HTTP_STR("Content-Length")); - if (header_index != -1) { - - // Have Content-Length - HTTP_String value = headers[header_index].value; - - uint64_t tmp; - if (parse_content_length(value.ptr, value.len, &tmp) < 0) - return -1; - if (tmp > INT_MAX) - return -1; - int len = (int) tmp; - - if (len > s->len - s->cur) - return 0; // Incomplete request - - *body = (HTTP_String) { s->src + s->cur, len }; - - s->cur += len; - return 1; - } - - // No Content-Length or Transfer-Encoding - if (body_expected) return -1; - - *body = (HTTP_String) { NULL, 0 }; - return 1; -} - -static int contains_head(char *src, int len) -{ - int cur = 0; - while (len - cur > 3) { - if (src[cur+0] == '\r' && - src[cur+1] == '\n' && - src[cur+2] == '\r' && - src[cur+3] == '\n') - return 1; - cur++; - } - return 0; -} - -static int parse_request(Scanner *s, HTTP_Request *req) -{ - if (!contains_head(s->src + s->cur, s->len - s->cur)) - return 0; - - req->secure = false; - - if (0) {} - else if (consume_str(s, HTTP_STR("GET "))) req->method = HTTP_METHOD_GET; - else if (consume_str(s, HTTP_STR("POST "))) req->method = HTTP_METHOD_POST; - else if (consume_str(s, HTTP_STR("PUT "))) req->method = HTTP_METHOD_PUT; - else if (consume_str(s, HTTP_STR("HEAD "))) req->method = HTTP_METHOD_HEAD; - else if (consume_str(s, HTTP_STR("DELETE "))) req->method = HTTP_METHOD_DELETE; - else if (consume_str(s, HTTP_STR("CONNECT "))) req->method = HTTP_METHOD_CONNECT; - else if (consume_str(s, HTTP_STR("OPTIONS "))) req->method = HTTP_METHOD_OPTIONS; - else if (consume_str(s, HTTP_STR("TRACE "))) req->method = HTTP_METHOD_TRACE; - else if (consume_str(s, HTTP_STR("PATCH "))) req->method = HTTP_METHOD_PATCH; - else return -1; - - { - Scanner s2 = *s; - int peek = s->cur; - while (peek < s->len && s->src[peek] != ' ') - peek++; - if (peek == s->len) - return -1; - s2.len = peek; - - int ret = parse_request_target(&s2, &req->url); - if (ret < 0) return ret; - - s->cur = s2.cur; - } - - if (consume_str(s, HTTP_STR(" HTTP/1.1\r\n"))) { - req->minor = 1; - } else if (consume_str(s, HTTP_STR(" HTTP/1.0\r\n")) || consume_str(s, HTTP_STR(" HTTP/1\r\n"))) { - req->minor = 0; - } else { - return -1; - } - - int num_headers = parse_headers(s, req->headers, HTTP_MAX_HEADERS); - if (num_headers < 0) - return num_headers; - req->num_headers = num_headers; - - bool body_expected = true; - if (req->method == HTTP_METHOD_GET || req->method == HTTP_METHOD_DELETE) // TODO: maybe other methods? - body_expected = false; - - return parse_body(s, req->headers, req->num_headers, &req->body, body_expected); -} - -int http_find_header(HTTP_Header *headers, int num_headers, HTTP_String name) -{ - for (int i = 0; i < num_headers; i++) - if (http_streqcase(name, headers[i].name)) - return i; - return -1; -} - -static int parse_response(Scanner *s, HTTP_Response *res) -{ - if (!contains_head(s->src + s->cur, s->len - s->cur)) - return 0; - - if (consume_str(s, HTTP_STR("HTTP/1.1 "))) { - res->minor = 1; - } else if (consume_str(s, HTTP_STR("HTTP/1.0 ")) || consume_str(s, HTTP_STR("HTTP/1 "))) { - res->minor = 0; - } else { - return -1; - } - - if (s->len - s->cur < 5 - || s->src[s->cur+0] != ' ' - || !is_digit(s->src[s->cur+1]) - || !is_digit(s->src[s->cur+2]) - || !is_digit(s->src[s->cur+3]) - || s->src[s->cur+4] != ' ') - return -1; - s->cur += 5; - - res->status = - (s->src[s->cur-2] - '0') * 1 + - (s->src[s->cur-3] - '0') * 10 + - (s->src[s->cur-4] - '0') * 100; - - while (s->cur < s->len && ( - s->src[s->cur] == '\t' || - s->src[s->cur] == ' ' || - is_vchar(s->src[s->cur]))) // TODO: obs-text - s->cur++; - - if (s->len - s->cur < 2 - || s->src[s->cur+0] != '\r' - || s->src[s->cur+1] != '\n') - return -1; - s->cur += 2; - - int num_headers = parse_headers(s, res->headers, HTTP_MAX_HEADERS); - if (num_headers < 0) - return num_headers; - res->num_headers = num_headers; - - bool body_expected = true; // TODO - - return parse_body(s, res->headers, res->num_headers, &res->body, body_expected); -} - -int http_parse_ipv4(char *src, int len, HTTP_IPv4 *ipv4) -{ - Scanner s = {src, len, 0}; - int ret = parse_ipv4(&s, ipv4); - if (ret < 0) return ret; - return s.cur; -} - -int http_parse_ipv6(char *src, int len, HTTP_IPv6 *ipv6) -{ - Scanner s = {src, len, 0}; - int ret = parse_ipv6(&s, ipv6); - if (ret < 0) return ret; - return s.cur; -} - -int http_parse_url(char *src, int len, HTTP_URL *url) -{ - Scanner s = {src, len, 0}; - int ret = parse_uri(&s, url, 1); - if (ret == 1) - return s.cur; - return ret; -} - -int http_parse_request(char *src, int len, HTTP_Request *req) -{ - Scanner s = {src, len, 0}; - int ret = parse_request(&s, req); - if (ret == 1) - return s.cur; - return ret; -} - -int http_parse_response(char *src, int len, HTTP_Response *res) -{ - Scanner s = {src, len, 0}; - int ret = parse_response(&s, res); - if (ret == 1) - return s.cur; - return ret; -} - -HTTP_String http_get_cookie(HTTP_Request *req, HTTP_String name) -{ - // TODO: best-effort implementation - - for (int i = 0; i < req->num_headers; i++) { - - if (!http_streqcase(req->headers[i].name, HTTP_STR("Cookie"))) - continue; - - char *src = req->headers[i].value.ptr; - int len = req->headers[i].value.len; - int cur = 0; - - // Cookie: name1=value1; name2=value2; name3=value3 - - for (;;) { - - while (cur < len && src[cur] == ' ') - cur++; - - int off = cur; - while (cur < len && src[cur] != '=') - cur++; - - HTTP_String cookie_name = { src + off, cur - off }; - - if (cur == len) - break; - cur++; - - off = cur; - while (cur < len && src[cur] != ';') - cur++; - - HTTP_String cookie_value = { src + off, cur - off }; - - if (http_streq(name, cookie_name)) - return cookie_value; - - if (cur == len) - break; - cur++; - } - } - - return HTTP_STR(""); -} - -HTTP_String http_get_param(HTTP_String body, HTTP_String str, char *mem, int cap) -{ - // This is just a best-effort implementation - - char *src = body.ptr; - int len = body.len; - int cur = 0; - - if (cur < len && src[cur] == '?') - cur++; - - while (cur < len) { - - HTTP_String name; - { - int off = cur; - while (cur < len && src[cur] != '=' && src[cur] != '&') - cur++; - name = (HTTP_String) { src + off, cur - off }; - } - - HTTP_String body = HTTP_STR(""); - if (cur < len) { - cur++; - if (src[cur-1] == '=') { - int off = cur; - while (cur < len && src[cur] != '&') - cur++; - body = (HTTP_String) { src + off, cur - off }; - - if (cur < len) - cur++; - } - } - - if (http_streq(str, name)) { - - bool percent_encoded = false; - for (int i = 0; i < body.len; i++) - if (body.ptr[i] == '+' || body.ptr[i] == '%') { - percent_encoded = true; - break; - } - - if (!percent_encoded) - return body; - - if (body.len > cap) - return (HTTP_String) { NULL, 0 }; - - HTTP_String decoded = { mem, 0 }; - for (int i = 0; i < body.len; i++) { - - char c = body.ptr[i]; - if (c == '+') - c = ' '; - else { - if (body.ptr[i] == '%') { - if (body.len - i < 3 - || !is_hex_digit(body.ptr[i+1]) - || !is_hex_digit(body.ptr[i+2])) - return (HTTP_String) { NULL, 0 }; - - int h = hex_digit_to_int(body.ptr[i+1]); - int l = hex_digit_to_int(body.ptr[i+2]); - c = (h << 4) | l; - - i += 2; - } - } - - decoded.ptr[decoded.len++] = c; - } - - return decoded; - } - } - - return HTTP_STR(""); -} - -static bool is_digit(char c) -{ - return c >= '0' && c <= '9'; -} - -int http_get_param_i(HTTP_String body, HTTP_String str) -{ - char buf[128]; - HTTP_String out = http_get_param(body, str, buf, SIZEOF(buf)); - if (out.len == 0 || !is_digit(out.ptr[0])) - return -1; - - int cur = 0; - int res = 0; - do { - int d = out.ptr[cur++] - '0'; - if (res > (INT_MAX - d) / 10) - return -1; - res = res * 10 + d; - } while (cur < out.len && is_digit(out.ptr[cur])); - - return res; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// 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. -// -// It features sticky errors, a zero-copy interface, -// and a safe mechanism to patch previously written -// bytes. -// -// Only up to 4GB of data can be stored at once. - -enum { - BYTE_QUEUE_ERROR = 1 << 0, - BYTE_QUEUE_READ = 1 << 1, - BYTE_QUEUE_WRITE = 1 << 2, -}; - -static void* -callback_malloc(HTTP_ByteQueue *queue, int len) -{ - return queue->memfunc(HTTP_MEMFUNC_MALLOC, NULL, len, queue->memfuncdata); -} - -static void -callback_free(HTTP_ByteQueue *queue, void *ptr, int len) -{ - queue->memfunc(HTTP_MEMFUNC_FREE, ptr, len, queue->memfuncdata); -} - -// Initialize the queue -static void -byte_queue_init(HTTP_ByteQueue *queue, unsigned int limit, HTTP_MemoryFunc memfunc, void *memfuncdata) -{ - queue->flags = 0; - queue->head = 0; - queue->size = 0; - queue->used = 0; - queue->curs = 0; - queue->limit = limit; - queue->data = NULL; - queue->read_target = NULL; - queue->memfunc = memfunc; - queue->memfuncdata = memfuncdata; -} - -// Deinitialize the queue -static void -byte_queue_free(HTTP_ByteQueue *queue) -{ - if (queue->read_target) { - if (queue->read_target != queue->data) - callback_free(queue, queue->read_target, queue->read_target_size); - queue->read_target = NULL; - queue->read_target_size = 0; - } - - callback_free(queue, queue->data, queue->size); - queue->data = NULL; -} - -static int -byte_queue_error(HTTP_ByteQueue *queue) -{ - return queue->flags & BYTE_QUEUE_ERROR; -} - -static int -byte_queue_empty(HTTP_ByteQueue *queue) -{ - return queue->used == 0; -} - -// Start a read operation on the queue. -// -// This function returnes the pointer to the memory region containing the bytes -// to read. Callers can't read more than [*len] bytes from it. To complete the -// read, the [byte_queue_read_ack] function must be called with the number of -// bytes that were acknowledged by the caller. -// -// Note: -// - You can't have more than one pending read. -static char* -byte_queue_read_buf(HTTP_ByteQueue *queue, int *len) -{ - if (queue->flags & BYTE_QUEUE_ERROR) { - *len = 0; - return NULL; - } - - HTTP_ASSERT((queue->flags & BYTE_QUEUE_READ) == 0); - queue->flags |= BYTE_QUEUE_READ; - queue->read_target = queue->data; - queue->read_target_size = queue->size; - - *len = queue->used; - if (queue->data == NULL) - return NULL; - return queue->data + queue->head; -} - -// Complete a previously started operation on the queue. -static void -byte_queue_read_ack(HTTP_ByteQueue *queue, int num) -{ - HTTP_ASSERT(num >= 0); - - if (queue->flags & BYTE_QUEUE_ERROR) - return; - - if ((queue->flags & BYTE_QUEUE_READ) == 0) - return; - - queue->flags &= ~BYTE_QUEUE_READ; - - HTTP_ASSERT((unsigned int) num <= queue->used); - queue->head += (unsigned int) num; - queue->used -= (unsigned int) num; - queue->curs += (unsigned int) num; - - if (queue->read_target) { - if (queue->read_target != queue->data) - callback_free(queue, queue->read_target, queue->read_target_size); - queue->read_target = NULL; - queue->read_target_size = 0; - } -} - -static char* -byte_queue_write_buf(HTTP_ByteQueue *queue, int *cap) -{ - if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL) { - *cap = 0; - return NULL; - } - - HTTP_ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0); - queue->flags |= BYTE_QUEUE_WRITE; - - unsigned int ucap = queue->size - (queue->head + queue->used); - if (ucap > INT_MAX) ucap = INT_MAX; - - *cap = (int) ucap; - return queue->data + (queue->head + queue->used); -} - -static void -byte_queue_write_ack(HTTP_ByteQueue *queue, int num) -{ - HTTP_ASSERT(num >= 0); - - if (queue->flags & BYTE_QUEUE_ERROR) - return; - - if ((queue->flags & BYTE_QUEUE_WRITE) == 0) - return; - - queue->flags &= ~BYTE_QUEUE_WRITE; - queue->used += (unsigned int) num; -} - -// Sets the minimum capacity for the next write operation -// and returns 1 if the content of the queue was moved, else -// 0 is returned. -// -// You must not call this function while a write is pending. -// In other words, you must do this: -// -// byte_queue_write_setmincap(queue, mincap); -// dst = byte_queue_write_buf(queue, &cap); -// ... -// byte_queue_write_ack(num); -// -// And NOT this: -// -// dst = byte_queue_write_buf(queue, &cap); -// byte_queue_write_setmincap(queue, mincap); <-- BAD -// ... -// byte_queue_write_ack(num); -// -static int -byte_queue_write_setmincap(HTTP_ByteQueue *queue, int mincap) -{ - HTTP_ASSERT(mincap >= 0); - unsigned int umincap = (unsigned int) mincap; - - // Sticky error - if (queue->flags & BYTE_QUEUE_ERROR) - return 0; - - // In general, the queue's contents look like this: - // - // size - // v - // [___xxxxxxxxxxxx________] - // ^ ^ ^ - // 0 head head + used - // - // This function needs to make sure that at least [mincap] - // bytes are available on the right side of the content. - // - // We have 3 cases: - // - // 1) If there is enough memory already, this function doesn't - // need to do anything. - // - // 2) If there isn't enough memory on the right but there is - // enough free memory if we cound the left unused region, - // then the content is moved back to the - // start of the buffer. - // - // 3) If there isn't enough memory considering both sides, this - // function needs to allocate a new buffer. - // - // If there are pending read or write operations, the application - // is holding pointers to the buffer, so we need to make sure - // to not invalidate them. The only real problem is pending reads - // since this function can only be called before starting a write - // opearation. - // - // To avoid invalidating the read pointer when we allocate a new - // buffer, we don't free the old buffer. Instead, we store the - // pointer in the "old" field so that the read ack function can - // free it. - // - // To avoid invalidating the pointer when we are moving back the - // content since there is enough memory at the start of the buffer, - // we just avoid that. Even if there is enough memory considering - // left and right free regions, we allocate a new buffer. - - HTTP_ASSERT((queue->flags & BYTE_QUEUE_WRITE) == 0); - - unsigned int total_free_space = queue->size - queue->used; - unsigned int free_space_after_data = queue->size - queue->used - queue->head; - - int moved = 0; - if (free_space_after_data < umincap) { - - if (total_free_space < umincap || (queue->read_target == queue->data)) { - // Resize required - - if (queue->used + umincap > queue->limit) { - queue->flags |= BYTE_QUEUE_ERROR; - return 0; - } - - unsigned int size; - if (queue->size > UINT32_MAX / 2) - size = UINT32_MAX; - else - size = 2 * queue->size; - - if (size < queue->used + umincap) - size = queue->used + umincap; - - if (size > queue->limit) - size = queue->limit; - - char *data = callback_malloc(queue, size); - if (!data) { - queue->flags |= BYTE_QUEUE_ERROR; - return 0; - } - - if (queue->used > 0) - memcpy(data, queue->data + queue->head, queue->used); - - if (queue->read_target != queue->data) - callback_free(queue, queue->data, queue->size); - - queue->data = data; - queue->head = 0; - queue->size = size; - - } else { - // Move required - memmove(queue->data, queue->data + queue->head, queue->used); - queue->head = 0; - } - - moved = 1; - } - - return moved; -} - -static HTTP_ByteQueueOffset -byte_queue_offset(HTTP_ByteQueue *queue) -{ - if (queue->flags & BYTE_QUEUE_ERROR) - return (HTTP_ByteQueueOffset) { 0 }; - return (HTTP_ByteQueueOffset) { queue->curs + queue->used }; -} - -static unsigned int -byte_queue_size_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off) -{ - return queue->curs + queue->used - off; -} - -static void -byte_queue_patch(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset off, - char *src, unsigned int len) -{ - if (queue->flags & BYTE_QUEUE_ERROR) - return; - - // Check that the offset is in range - HTTP_ASSERT(off >= queue->curs && off - queue->curs < queue->used); - - // Check that the length is in range - HTTP_ASSERT(len <= queue->used - (off - queue->curs)); - - // Perform the patch - char *dst = queue->data + queue->head + (off - queue->curs); - memcpy(dst, src, len); -} - -static void -byte_queue_remove_from_offset(HTTP_ByteQueue *queue, HTTP_ByteQueueOffset offset) -{ - if (queue->flags & BYTE_QUEUE_ERROR) - return; - - unsigned long long num = (queue->curs + queue->used) - offset; - HTTP_ASSERT(num <= queue->used); - - queue->used -= num; -} - -static void -byte_queue_write(HTTP_ByteQueue *queue, const char *str, int len) -{ - if (str == NULL) str = ""; - if (len < 0) len = strlen(str); - - int cap; - byte_queue_write_setmincap(queue, len); - char *dst = byte_queue_write_buf(queue, &cap); - if (dst) memcpy(dst, str, len); - byte_queue_write_ack(queue, len); -} - -static void -byte_queue_write_fmt2(HTTP_ByteQueue *queue, const char *fmt, va_list args) -{ - if (queue->flags & BYTE_QUEUE_ERROR) - return; - - va_list args2; - va_copy(args2, args); - - int cap; - byte_queue_write_setmincap(queue, 128); - char *dst = byte_queue_write_buf(queue, &cap); - - int len = vsnprintf(dst, cap, fmt, args); - if (len < 0) { - queue->flags |= BYTE_QUEUE_ERROR; - va_end(args2); - return; - } - - if (len > cap) { - byte_queue_write_ack(queue, 0); - byte_queue_write_setmincap(queue, len+1); - dst = byte_queue_write_buf(queue, &cap); - vsnprintf(dst, cap, fmt, args2); - } - - byte_queue_write_ack(queue, len); - - va_end(args2); -} - -static void -byte_queue_write_fmt(HTTP_ByteQueue *queue, const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - byte_queue_write_fmt2(queue, fmt, args); - va_end(args); -} - -#define TEN_SPACES " " - -void http_engine_init(HTTP_Engine *eng, int client, HTTP_MemoryFunc memfunc, void *memfuncdata) -{ - if (client) - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_URL; - else - eng->state = HTTP_ENGINE_STATE_SERVER_RECV_BUF; - - eng->closing = 0; - eng->numexch = 0; - - byte_queue_init(&eng->input, 1<<20, memfunc, memfuncdata); - byte_queue_init(&eng->output, 1<<20, memfunc, memfuncdata); -} - -void http_engine_free(HTTP_Engine *eng) -{ - byte_queue_free(&eng->input); - byte_queue_free(&eng->output); - eng->state = HTTP_ENGINE_STATE_NONE; -} - -void http_engine_close(HTTP_Engine *eng) -{ - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - else - eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; -} - -HTTP_EngineState http_engine_state(HTTP_Engine *eng) -{ - return eng->state; -} - -const char* http_engine_statestr(HTTP_EngineState state) { // TODO: remove - switch (state) { - case HTTP_ENGINE_STATE_NONE: return "NONE"; - case HTTP_ENGINE_STATE_CLIENT_PREP_URL: return "CLIENT_PREP_URL"; - case HTTP_ENGINE_STATE_CLIENT_PREP_HEADER: return "CLIENT_PREP_HEADER"; - case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF: return "CLIENT_PREP_BODY_BUF"; - case HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK: return "CLIENT_PREP_BODY_ACK"; - case HTTP_ENGINE_STATE_CLIENT_PREP_ERROR: return "CLIENT_PREP_ERROR"; - case HTTP_ENGINE_STATE_CLIENT_SEND_BUF: return "CLIENT_SEND_BUF"; - case HTTP_ENGINE_STATE_CLIENT_SEND_ACK: return "CLIENT_SEND_ACK"; - case HTTP_ENGINE_STATE_CLIENT_RECV_BUF: return "CLIENT_RECV_BUF"; - case HTTP_ENGINE_STATE_CLIENT_RECV_ACK: return "CLIENT_RECV_ACK"; - case HTTP_ENGINE_STATE_CLIENT_READY: return "CLIENT_READY"; - case HTTP_ENGINE_STATE_CLIENT_CLOSED: return "CLIENT_CLOSED"; - case HTTP_ENGINE_STATE_SERVER_RECV_BUF: return "SERVER_RECV_BUF"; - case HTTP_ENGINE_STATE_SERVER_RECV_ACK: return "SERVER_RECV_ACK"; - case HTTP_ENGINE_STATE_SERVER_PREP_STATUS: return "SERVER_PREP_STATUS"; - case HTTP_ENGINE_STATE_SERVER_PREP_HEADER: return "SERVER_PREP_HEADER"; - case HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF: return "SERVER_PREP_BODY_BUF"; - case HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK: return "SERVER_PREP_BODY_ACK"; - case HTTP_ENGINE_STATE_SERVER_PREP_ERROR: return "SERVER_PREP_ERROR"; - case HTTP_ENGINE_STATE_SERVER_SEND_BUF: return "SERVER_SEND_BUF"; - case HTTP_ENGINE_STATE_SERVER_SEND_ACK: return "SERVER_SEND_ACK"; - case HTTP_ENGINE_STATE_SERVER_CLOSED: return "SERVER_CLOSED"; - default: return "UNKNOWN"; - } -} - -char *http_engine_recvbuf(HTTP_Engine *eng, int *cap) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_RECV_BUF) == 0) { - *cap = 0; - return NULL; - } - - eng->state &= ~HTTP_ENGINE_STATEBIT_RECV_BUF; - eng->state |= HTTP_ENGINE_STATEBIT_RECV_ACK; - - byte_queue_write_setmincap(&eng->input, 1<<9); - if (byte_queue_error(&eng->input)) { - *cap = 0; - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - else - eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; - return NULL; - } - - return byte_queue_write_buf(&eng->input, cap); -} - -static int -should_keep_alive(HTTP_Engine *eng) -{ - HTTP_ASSERT(eng->state & HTTP_ENGINE_STATEBIT_PREP); - -#if 0 - // If the parent system doesn't want us to reuse - // the connection, we certainly can't keep alive. - if ((eng->state & TINYHTTP_STREAM_REUSE) == 0) - return 0; -#endif - - if (eng->numexch >= 100) // TODO: Make this a parameter - return 0; - - HTTP_Request *req = &eng->result.req; - - // If the client is using HTTP/1.0, we can't - // keep alive. - if (req->minor == 0) - return 0; - - // TODO: This assumes "Connection" can only hold a single token, - // but this is not true. - int i = http_find_header(req->headers, req->num_headers, HTTP_STR("Connection")); - if (i >= 0 && http_streqcase(req->headers[i].value, HTTP_STR("Close"))) - return 0; - - return 1; -} - -static void process_incoming_request(HTTP_Engine *eng) -{ - HTTP_ASSERT(eng->state == HTTP_ENGINE_STATE_SERVER_RECV_ACK - || eng->state == HTTP_ENGINE_STATE_SERVER_SEND_ACK - || eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF - || eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR); - - char *src; - int len; - src = byte_queue_read_buf(&eng->input, &len); - - int ret = http_parse_request(src, len, &eng->result.req); - - if (ret == 0) { - byte_queue_read_ack(&eng->input, 0); - eng->state = HTTP_ENGINE_STATE_SERVER_RECV_BUF; - return; - } - - if (ret < 0) { - byte_queue_read_ack(&eng->input, 0); - byte_queue_write(&eng->output, - "HTTP/1.1 400 Bad Request\r\n" - "Connection: Close\r\n" - "Content-Length: 0\r\n" - "\r\n", -1 - ); - if (byte_queue_error(&eng->output)) - eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; - else { - eng->closing = 1; - eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF; - } - return; - } - - HTTP_ASSERT(ret > 0); - - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_STATUS; - eng->reqsize = ret; - eng->keepalive = should_keep_alive(eng); - eng->response_offset = byte_queue_offset(&eng->output); -} - -void http_engine_recvack(HTTP_Engine *eng, int num) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_RECV_ACK) == 0) - return; - - byte_queue_write_ack(&eng->input, num); - - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) { - - char *src; - int len; - src = byte_queue_read_buf(&eng->input, &len); - - int ret = http_parse_response(src, len, &eng->result.res); - - if (ret == 0) { - byte_queue_read_ack(&eng->input, 0); - eng->state = HTTP_ENGINE_STATE_CLIENT_RECV_BUF; - return; - } - - if (ret < 0) { - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - return; - } - - HTTP_ASSERT(ret > 0); - - eng->state = HTTP_ENGINE_STATE_CLIENT_READY; - - } else { - process_incoming_request(eng); - } -} - -char *http_engine_sendbuf(HTTP_Engine *eng, int *len) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_SEND_BUF) == 0) { - *len = 0; - return NULL; - } - - eng->state &= ~HTTP_ENGINE_STATEBIT_SEND_BUF; - eng->state |= HTTP_ENGINE_STATEBIT_SEND_ACK; - - return byte_queue_read_buf(&eng->output, len); -} - -void http_engine_sendack(HTTP_Engine *eng, int num) -{ - if (eng->state != HTTP_ENGINE_STATE_SERVER_SEND_ACK && - eng->state != HTTP_ENGINE_STATE_CLIENT_SEND_ACK) - return; - - byte_queue_read_ack(&eng->output, num); - - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) { - if (byte_queue_empty(&eng->output)) - eng->state = HTTP_ENGINE_STATE_CLIENT_RECV_BUF; - else - eng->state = HTTP_ENGINE_STATE_CLIENT_SEND_BUF; - } else { - if (byte_queue_empty(&eng->output)) { - if (!eng->closing && eng->keepalive) - process_incoming_request(eng); - else - eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; - } else - eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF; - } -} - -HTTP_Request *http_engine_getreq(HTTP_Engine *eng) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_REQUEST) == 0) - return NULL; - return &eng->result.req; -} - -HTTP_Response *http_engine_getres(HTTP_Engine *eng) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_RESPONSE) == 0) - return NULL; - return &eng->result.res; -} - -void http_engine_url(HTTP_Engine *eng, HTTP_Method method, HTTP_String url, int minor) -{ - if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_URL) - return; - - eng->response_offset = byte_queue_offset(&eng->output); // TODO: rename response_offset to something that makes sense for clients - - HTTP_URL parsed_url; - int ret = http_parse_url(url.ptr, url.len, &parsed_url); - if (ret != url.len) { - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_ERROR; - return; - } - - HTTP_String method_and_space = HTTP_STR("???"); - switch (method) { - case HTTP_METHOD_GET : method_and_space = HTTP_STR("GET "); break; - case HTTP_METHOD_HEAD : method_and_space = HTTP_STR("HEAD "); break; - case HTTP_METHOD_POST : method_and_space = HTTP_STR("POST "); break; - case HTTP_METHOD_PUT : method_and_space = HTTP_STR("PUT "); break; - case HTTP_METHOD_DELETE : method_and_space = HTTP_STR("DELETE "); break; - case HTTP_METHOD_CONNECT: method_and_space = HTTP_STR("CONNECT "); break; - case HTTP_METHOD_OPTIONS: method_and_space = HTTP_STR("OPTIONS "); break; - case HTTP_METHOD_TRACE : method_and_space = HTTP_STR("TRACE "); break; - case HTTP_METHOD_PATCH : method_and_space = HTTP_STR("PATCH "); break; - } - - HTTP_String path = parsed_url.path; - if (path.len == 0) - path = HTTP_STR("/"); - - byte_queue_write(&eng->output, method_and_space.ptr, method_and_space.len); - byte_queue_write(&eng->output, path.ptr, path.len); - byte_queue_write(&eng->output, parsed_url.query.ptr, parsed_url.query.len); - byte_queue_write(&eng->output, minor ? " HTTP/1.1\r\nHost: " : " HTTP/1.0\r\nHost: ", -1); - byte_queue_write(&eng->output, parsed_url.authority.host.text.ptr, parsed_url.authority.host.text.len); - if (parsed_url.authority.port > 0) - byte_queue_write_fmt(&eng->output, "%d", parsed_url.authority.port); - byte_queue_write(&eng->output, "\r\n", 2); - - eng->keepalive = 1; // TODO - - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_HEADER; -} - - -static const char* -get_status_text(int code) -{ - switch(code) { - - case 100: return "Continue"; - case 101: return "Switching Protocols"; - case 102: return "Processing"; - - case 200: return "OK"; - case 201: return "Created"; - case 202: return "Accepted"; - case 203: return "Non-Authoritative Information"; - case 204: return "No Content"; - case 205: return "Reset Content"; - case 206: return "Partial Content"; - case 207: return "Multi-Status"; - case 208: return "Already Reported"; - - case 300: return "Multiple Choices"; - case 301: return "Moved Permanently"; - case 302: return "Found"; - case 303: return "See Other"; - case 304: return "Not Modified"; - case 305: return "Use Proxy"; - case 306: return "Switch Proxy"; - case 307: return "Temporary Redirect"; - case 308: return "Permanent Redirect"; - - case 400: return "Bad Request"; - case 401: return "Unauthorized"; - case 402: return "Payment Required"; - case 403: return "Forbidden"; - case 404: return "Not Found"; - case 405: return "Method Not Allowed"; - case 406: return "Not Acceptable"; - case 407: return "Proxy Authentication Required"; - case 408: return "Request Timeout"; - case 409: return "Conflict"; - case 410: return "Gone"; - case 411: return "Length Required"; - case 412: return "Precondition Failed"; - case 413: return "Request Entity Too Large"; - case 414: return "Request-URI Too Long"; - case 415: return "Unsupported Media Type"; - case 416: return "Requested Range Not Satisfiable"; - case 417: return "Expectation Failed"; - case 418: return "I'm a teapot"; - case 420: return "Enhance your calm"; - case 422: return "Unprocessable Entity"; - case 426: return "Upgrade Required"; - case 429: return "Too many requests"; - case 431: return "Request Header Fields Too Large"; - case 449: return "Retry With"; - case 451: return "Unavailable For Legal Reasons"; - - case 500: return "Internal Server Error"; - case 501: return "Not Implemented"; - case 502: return "Bad Gateway"; - case 503: return "Service Unavailable"; - case 504: return "Gateway Timeout"; - case 505: return "HTTP Version Not Supported"; - case 509: return "Bandwidth Limit Exceeded"; - } - return "???"; -} - -void http_engine_status(HTTP_Engine *eng, int status) -{ - if (eng->state != HTTP_ENGINE_STATE_SERVER_PREP_STATUS) - return; - - byte_queue_write_fmt(&eng->output, - "HTTP/1.1 %d %s\r\n", - status, get_status_text(status)); - - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_HEADER; -} - -void http_engine_header(HTTP_Engine *eng, HTTP_String str) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_PREP_HEADER) == 0) - return; - - // TODO: Check that the header is valid - - byte_queue_write(&eng->output, str.ptr, str.len); - byte_queue_write(&eng->output, "\r\n", 2); -} - -void http_engine_header_fmt2(HTTP_Engine *eng, const char *fmt, va_list args) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_PREP_HEADER) == 0) - return; - - // TODO: Check that the header is valid - - byte_queue_write_fmt2(&eng->output, fmt, args); - byte_queue_write(&eng->output, "\r\n", 2); -} - -void http_engine_header_fmt(HTTP_Engine *eng, const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - http_engine_header_fmt2(eng, fmt, args); - va_end(args); -} - -static void -complete_message_head(HTTP_Engine *eng) -{ - if (eng->keepalive) byte_queue_write(&eng->output, "Connection: Keep-Alive\r\n", -1); - else byte_queue_write(&eng->output, "Connection: Close\r\n", -1); - - byte_queue_write(&eng->output, "Content-Length: ", -1); - eng->content_length_value_offset = byte_queue_offset(&eng->output); - byte_queue_write(&eng->output, TEN_SPACES "\r\n", -1); - - byte_queue_write(&eng->output, "\r\n", -1); - eng->content_length_offset = byte_queue_offset(&eng->output); -} - -static void complete_message_body(HTTP_Engine *eng) -{ - unsigned int content_length = byte_queue_size_from_offset(&eng->output, eng->content_length_offset); - - if (content_length > UINT32_MAX) { - // TODO - } - - char tmp[10]; - - tmp[0] = '0' + content_length / 1000000000; content_length %= 1000000000; - tmp[1] = '0' + content_length / 100000000; content_length %= 100000000; - tmp[2] = '0' + content_length / 10000000; content_length %= 10000000; - tmp[3] = '0' + content_length / 1000000; content_length %= 1000000; - tmp[4] = '0' + content_length / 100000; content_length %= 100000; - tmp[5] = '0' + content_length / 10000; content_length %= 10000; - tmp[6] = '0' + content_length / 1000; content_length %= 1000; - tmp[7] = '0' + content_length / 100; content_length %= 100; - tmp[8] = '0' + content_length / 10; content_length %= 10; - tmp[9] = '0' + content_length; - - int i = 0; - while (i < 9 && tmp[i] == '0') - i++; - - byte_queue_patch(&eng->output, eng->content_length_value_offset, tmp + i, 10 - i); -} - -void http_engine_body(HTTP_Engine *eng, HTTP_String str) -{ - http_engine_bodycap(eng, str.len); - int cap; - char *buf = http_engine_bodybuf(eng, &cap); - if (buf) { - memcpy(buf, str.ptr, str.len); - http_engine_bodyack(eng, str.len); - } -} - -static void ensure_body_entered(HTTP_Engine *eng) -{ - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) { - - if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_HEADER) { - complete_message_head(eng); - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF; - } - - } else { - - if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_HEADER) { - complete_message_head(eng); - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF; - } - } -} - -void http_engine_bodycap(HTTP_Engine *eng, int mincap) -{ - ensure_body_entered(eng); - if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF && - eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) - return; - - byte_queue_write_setmincap(&eng->output, mincap); -} - -char *http_engine_bodybuf(HTTP_Engine *eng, int *cap) -{ - ensure_body_entered(eng); - if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF && - eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) { - *cap = 0; - return NULL; - } - - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK; - else - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK; - - return byte_queue_write_buf(&eng->output, cap); -} - -void http_engine_bodyack(HTTP_Engine *eng, int num) -{ - if (eng->state != HTTP_ENGINE_STATE_CLIENT_PREP_BODY_ACK && - eng->state != HTTP_ENGINE_STATE_SERVER_PREP_BODY_ACK) - return; - - byte_queue_write_ack(&eng->output, num); - - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF; - else - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF; -} - -void http_engine_done(HTTP_Engine *eng) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_PREP) == 0) - return; - - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) { - - if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_URL) { - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - return; - } - - if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_HEADER) { - complete_message_head(eng); - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF; - } - - if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_BODY_BUF) - complete_message_body(eng); - - if (eng->state == HTTP_ENGINE_STATE_CLIENT_PREP_ERROR) { - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - return; - } - - if (byte_queue_error(&eng->output)) { - eng->state = HTTP_ENGINE_STATE_CLIENT_CLOSED; - return; - } - - eng->state = HTTP_ENGINE_STATE_CLIENT_SEND_BUF; - - } else { - - if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_HEADER) { - complete_message_head(eng); - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF; - } - - if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_BODY_BUF) - complete_message_body(eng); - - if (eng->state == HTTP_ENGINE_STATE_SERVER_PREP_ERROR) { - byte_queue_remove_from_offset(&eng->output, eng->response_offset); - byte_queue_write(&eng->output, - "HTTP/1.1 500 Internal Server Error\r\n" - "Content-Length: 0\r\n" - "Connection: Close\r\n" - "\r\n", - -1 - ); - } - - if (byte_queue_error(&eng->output)) { - eng->state = HTTP_ENGINE_STATE_SERVER_CLOSED; - return; - } - - byte_queue_read_ack(&eng->input, eng->reqsize); - eng->state = HTTP_ENGINE_STATE_SERVER_SEND_BUF; - } -} - -void http_engine_undo(HTTP_Engine *eng) -{ - if ((eng->state & HTTP_ENGINE_STATEBIT_PREP) == 0) - return; - - byte_queue_write_ack(&eng->output, 0); - byte_queue_remove_from_offset(&eng->output, eng->response_offset); - - if (eng->state & HTTP_ENGINE_STATEBIT_CLIENT) - eng->state = HTTP_ENGINE_STATE_CLIENT_PREP_URL; - else - eng->state = HTTP_ENGINE_STATE_SERVER_PREP_STATUS; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// src/cert.c -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/cert.c" -#include -#include -#include - -#ifdef HTTPS_ENABLED -#include -#include -#include -#include -#include -#include -#endif - -#ifndef HTTP_AMALGAMATION -#include "cert.h" -#endif - -#ifdef HTTPS_ENABLED - -static EVP_PKEY *generate_rsa_key_pair(int key_bits) -{ - EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); - if (!ctx) - return NULL; - - if (EVP_PKEY_keygen_init(ctx) <= 0) { - EVP_PKEY_CTX_free(ctx); - return NULL; - } - - if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, key_bits) <= 0) { - EVP_PKEY_CTX_free(ctx); - return NULL; - } - - EVP_PKEY *pkey = NULL; - if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { - EVP_PKEY_CTX_free(ctx); - return NULL; - } - - EVP_PKEY_CTX_free(ctx); - return pkey; -} - -static X509 *create_certificate(EVP_PKEY *pkey, HTTP_String C, HTTP_String O, HTTP_String CN, int days) -{ - X509 *x509 = X509_new(); - if (!x509) - return NULL; - - // Set version (version 3) - X509_set_version(x509, 2); - - // Set serial number - ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); - - // Set validity period - X509_gmtime_adj(X509_get_notBefore(x509), 0); - X509_gmtime_adj(X509_get_notAfter(x509), 31536000L * days); // days * seconds_per_year - - // Set public key - X509_set_pubkey(x509, pkey); - - // Set subject name - X509_NAME *name = X509_get_subject_name(x509); - X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char*) C.ptr, C.len, -1, 0); - X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char*) O.ptr, O.len, -1, 0); - X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*) CN.ptr, CN.len, -1, 0); - - // Set issuer name (same as subject for self-signed) - X509_set_issuer_name(x509, name); - - if (!X509_sign(x509, pkey, EVP_sha256())) { - X509_free(x509); - return NULL; - } - - return x509; -} - -static int save_private_key(EVP_PKEY *pkey, HTTP_String file) -{ - char copy[1<<10]; - if (file.len >= (int) sizeof(copy)) - return -1; - memcpy(copy, file.ptr, file.len); - copy[file.len] = '\0'; - - FILE *fp = fopen(copy, "wb"); - if (!fp) - return -1; - - // Write private key in PEM format - if (!PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL)) { - fclose(fp); - return -1; - } - - fclose(fp); - return 0; -} - -static int save_certificate(X509 *x509, HTTP_String file) -{ - char copy[1<<10]; - if (file.len >= (int) sizeof(copy)) - return -1; - memcpy(copy, file.ptr, file.len); - copy[file.len] = '\0'; - - FILE *fp = fopen(copy, "wb"); - if (!fp) - return -1; - - // Write certificate in PEM format - if (!PEM_write_X509(fp, x509)) { - fclose(fp); - return -1; - } - - fclose(fp); - return 0; -} - -int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, - HTTP_String cert_file, HTTP_String key_file) -{ - EVP_PKEY *pkey = generate_rsa_key_pair(2048); - if (pkey == NULL) - return -1; - - X509 *x509 = create_certificate(pkey, C, O, CN, 1); - if (x509 == NULL) { - EVP_PKEY_free(pkey); - return -1; - } - - if (save_private_key(pkey, key_file) < 0) { - X509_free(x509); - EVP_PKEY_free(pkey); - return -1; - } - - if (save_certificate(x509, cert_file) < 0) { - X509_free(x509); - EVP_PKEY_free(pkey); - return -1; - } - - X509_free(x509); - EVP_PKEY_free(pkey); - return 0; -} - -#else - -int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, - HTTP_String cert_file, HTTP_String key_file) -{ - (void) C; - (void) O; - (void) CN; - (void) cert_file; - (void) key_file; - return -1; -} - -#endif - -//////////////////////////////////////////////////////////////////////////////////////// -// src/sec.c -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/sec.c" -#ifndef HTTP_AMALGAMATION -#include "sec.h" -#endif - -#ifndef HTTPS_ENABLED - -void secure_context_global_init(void) -{ -} - -void secure_context_global_free(void) -{ -} - -int secure_context_init_as_client(SecureContext *sec) -{ - (void) sec; - return 0; -} - -int secure_context_init_as_server(SecureContext *sec, - HTTP_String cert_file, HTTP_String key_file) -{ - (void) sec; - (void) cert_file; - (void) key_file; - return 0; -} - -int secure_context_add_cert(SecureContext *sec, - HTTP_String domain, HTTP_String cert_file, - HTTP_String key_file) -{ - (void) sec; - (void) domain; - (void) cert_file; - (void) key_file; - return -1; -} - -void secure_context_free(SecureContext *sec) -{ - (void) sec; -} - -#else - -void secure_context_global_init(void) -{ - SSL_library_init(); - SSL_load_error_strings(); - OpenSSL_add_all_algorithms(); -} - -void secure_context_global_free(void) -{ - EVP_cleanup(); -} - -int secure_context_init_as_client(SecureContext *sec) -{ - SSL_CTX *ctx = SSL_CTX_new(TLS_client_method()); - if (!ctx) - return -1; - - SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); - - SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL); - - if (SSL_CTX_set_default_verify_paths(ctx) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - sec->is_server = false; - sec->ctx = ctx; - sec->num_certs = 0; - return 0; -} - -static int servername_callback(SSL *ssl, int *ad, void *arg) -{ - SecureContext *sec = arg; - - (void) ad; // TODO: use this? - - const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); - if (servername == NULL) - return SSL_TLSEXT_ERR_NOACK; - - for (int i = 0; i < sec->num_certs; i++) { - CertData *cert = &sec->certs[i]; - if (!strcmp(cert->domain, servername)) { - SSL_set_SSL_CTX(ssl, cert->ctx); - return SSL_TLSEXT_ERR_OK; - } - } - - return SSL_TLSEXT_ERR_NOACK; -} - -int secure_context_init_as_server(SecureContext *sec, - HTTP_String cert_file, HTTP_String key_file) -{ - SSL_CTX *ctx = SSL_CTX_new(TLS_server_method()); - if (!ctx) - return -1; - - SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); - - char cert_buffer[1024]; - if (cert_file.len >= (int) sizeof(cert_buffer)) { - SSL_CTX_free(ctx); - return -1; - } - memcpy(cert_buffer, cert_file.ptr, cert_file.len); - cert_buffer[cert_file.len] = '\0'; - - // Copy private key file path to static buffer - char key_buffer[1024]; - if (key_file.len >= (int) sizeof(key_buffer)) { - SSL_CTX_free(ctx); - return -1; - } - memcpy(key_buffer, key_file.ptr, key_file.len); - key_buffer[key_file.len] = '\0'; - - // Load certificate and private key - if (SSL_CTX_use_certificate_file(ctx, cert_buffer, SSL_FILETYPE_PEM) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - if (SSL_CTX_use_PrivateKey_file(ctx, key_buffer, SSL_FILETYPE_PEM) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - // Verify that the private key matches the certificate - if (SSL_CTX_check_private_key(ctx) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - SSL_CTX_set_tlsext_servername_callback(ctx, servername_callback); - SSL_CTX_set_tlsext_servername_arg(ctx, sec); - - sec->is_server = true; - sec->ctx = ctx; - sec->num_certs = 0; - return 0; -} - -void secure_context_free(SecureContext *sec) -{ - SSL_CTX_free(sec->ctx); - for (int i = 0; i < sec->num_certs; i++) - SSL_CTX_free(sec->certs[i].ctx); -} - -int secure_context_add_cert(SecureContext *sec, - HTTP_String domain, HTTP_String cert_file, - HTTP_String key_file) -{ - if (!sec->is_server) - return -1; - - if (sec->num_certs == MAX_CERTS) - return -1; - - SSL_CTX *ctx = SSL_CTX_new(TLS_server_method()); - if (!ctx) - return -1; - - SSL_CTX_set_min_proto_version(ctx, TLS1_2_VERSION); - - char cert_buffer[1024]; - if (cert_file.len >= (int) sizeof(cert_buffer)) { - SSL_CTX_free(ctx); - return -1; - } - memcpy(cert_buffer, cert_file.ptr, cert_file.len); - cert_buffer[cert_file.len] = '\0'; - - char key_buffer[1024]; - if (key_file.len >= (int) sizeof(key_buffer)) { - SSL_CTX_free(ctx); - return -1; - } - memcpy(key_buffer, key_file.ptr, key_file.len); - key_buffer[key_file.len] = '\0'; - - if (SSL_CTX_use_certificate_file(ctx, cert_buffer, SSL_FILETYPE_PEM) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - if (SSL_CTX_use_PrivateKey_file(ctx, key_buffer, SSL_FILETYPE_PEM) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - if (SSL_CTX_check_private_key(ctx) != 1) { - SSL_CTX_free(ctx); - return -1; - } - - CertData *cert = &sec->certs[sec->num_certs]; - if (domain.len >= (int) sizeof(cert->domain)) { - SSL_CTX_free(ctx); - return -1; - } - memcpy(cert->domain, domain.ptr, domain.len); - cert->domain[domain.len] = '\0'; - cert->ctx = ctx; - sec->num_certs++; - return 0; -} - -#endif - -//////////////////////////////////////////////////////////////////////////////////////// -// src/socket_raw.c -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/socket_raw.c" -#include - -#ifdef _WIN32 -#include -#endif - -#ifdef __linux__ -#include -#include -#include -#endif - -#ifndef HTTP_AMALGAMATION -#include "socket_raw.h" -#endif - -int socket_raw_global_init(void) -{ -#ifdef _WIN32 - WSADATA wsaData; - int result = WSAStartup(MAKEWORD(2, 2), &wsaData); - if (result != 0) - return 1; -#endif - return 0; -} - -void socket_raw_global_free(void) -{ -#ifdef _WIN32 - WSACleanup(); -#endif -} - -int set_socket_blocking(RAW_SOCKET sock, bool value) -{ -#ifdef _WIN32 - u_long mode = !value; - if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR) - return -1; -#endif - -#ifdef __linux__ - int flags = fcntl(sock, F_GETFL, 0); - if (flags < 0) - return -1; - if (value) flags &= ~O_NONBLOCK; - else flags |= O_NONBLOCK; - if (fcntl(sock, F_SETFL, flags) < 0) - return -1; -#endif - - return 0; -} - -RAW_SOCKET listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int backlog) -{ - RAW_SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); - if (sock == BAD_SOCKET) - return BAD_SOCKET; - - if (set_socket_blocking(sock, false) < 0) { - CLOSE_SOCKET(sock); - return BAD_SOCKET; - } - - if (reuse_addr) { - int one = 1; - setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*) &one, sizeof(one)); - } - - struct in_addr addr_buf; - if (addr.len == 0) - addr_buf.s_addr = htonl(INADDR_ANY); - else { - - char copy[100]; - if (addr.len >= (int) sizeof(copy)) { - CLOSE_SOCKET(sock); - return BAD_SOCKET; - } - memcpy(copy, addr.ptr, addr.len); - copy[addr.len] = '\0'; - - if (inet_pton(AF_INET, copy, &addr_buf) < 0) { - CLOSE_SOCKET(sock); - return BAD_SOCKET; - } - } - - struct sockaddr_in bind_buf; - bind_buf.sin_family = AF_INET; - bind_buf.sin_addr = addr_buf; - bind_buf.sin_port = htons(port); - if (bind(sock, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) { // TODO: how does bind fail on windows? - CLOSE_SOCKET(sock); - return BAD_SOCKET; - } - - if (listen(sock, backlog) < 0) { // TODO: how does listen fail on windows? - CLOSE_SOCKET(sock); - return BAD_SOCKET; - } - - return sock; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// src/socket.c -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/socket.c" -#include // snprintf -#include -#include -#include - -#ifdef _WIN32 -#include -#endif - -#ifdef __linux__ -#include -#include -#include -#include -#endif - -#ifdef HTTPS_ENABLED -#include -#include -#endif - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#include "socket.h" -#endif - -typedef struct { - bool is_ipv4; - union { - HTTP_IPv4 ipv4; - HTTP_IPv6 ipv6; - }; -} PendingConnectAddr; - -struct PendingConnect { - uint16_t port; - int cursor; - int num_addrs; - int max_addrs; - PendingConnectAddr *addrs; - char* hostname; // null-terminated - int hostname_len; -}; - -static PendingConnect* -pending_connect_init(HTTP_String hostname, uint16_t port, int max_addrs) -{ - PendingConnect *pending_connect = malloc(sizeof(PendingConnect) + max_addrs * sizeof(PendingConnectAddr) + hostname.len + 1); - if (pending_connect == NULL) - return NULL; - pending_connect->port = port; - pending_connect->cursor = 0; - pending_connect->num_addrs = 0; - pending_connect->max_addrs = max_addrs; - pending_connect->addrs = (PendingConnectAddr*) (pending_connect + 1); - pending_connect->hostname = (char*) (pending_connect->addrs + max_addrs); - memcpy(pending_connect->hostname, hostname.ptr, hostname.len); - pending_connect->hostname[hostname.len] = '\0'; - pending_connect->hostname_len = hostname.len; - return pending_connect; -} - -static void -pending_connect_free(PendingConnect *pending_connect) -{ - free(pending_connect); -} - -static void -pending_connect_add_ipv4(PendingConnect *pending_connect, HTTP_IPv4 ipv4) -{ - if (pending_connect->num_addrs == pending_connect->max_addrs) - return; - pending_connect->addrs[pending_connect->num_addrs++] = (PendingConnectAddr) { .is_ipv4=true, .ipv4=ipv4 }; -} - -static void -pending_connect_add_ipv6(PendingConnect *pending_connect, HTTP_IPv6 ipv6) -{ - if (pending_connect->num_addrs == pending_connect->max_addrs) - return; - pending_connect->addrs[pending_connect->num_addrs++] = (PendingConnectAddr) { .is_ipv4=false, .ipv6=ipv6 }; -} - -static int -next_connect_addr(PendingConnect *pending_connect, PendingConnectAddr *addr) -{ - if (pending_connect->cursor == pending_connect->num_addrs) - return -1; - *addr = pending_connect->addrs[pending_connect->cursor++]; - return 0; -} - -// Initializes a FREE socket with the information required to -// connect to specified host name. The resulting socket state -// is DIED if an error occurred or PENDING. -void socket_connect(Socket *sock, SecureContext *sec, - HTTP_String hostname, uint16_t port, void *user_data) -{ - PendingConnect *pending_connect; - - int max_addrs = 30; - pending_connect = pending_connect_init(hostname, port, max_addrs); - if (pending_connect == NULL) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return; - } - - char portstr[16]; - int len = snprintf(portstr, sizeof(portstr), "%u", port); - if (len < 0 || len >= (int) sizeof(portstr)) { - pending_connect_free(pending_connect); - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return; - } - - // DNS query - struct addrinfo hints = {0}; - hints.ai_family = AF_UNSPEC; - hints.ai_socktype = SOCK_STREAM; - - struct addrinfo *res = NULL; - int ret = getaddrinfo(pending_connect->hostname, portstr, &hints, &res); - if (ret != 0) { - pending_connect_free(pending_connect); - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return; - } - - for (struct addrinfo *rp = res; rp; rp = rp->ai_next) { - if (rp->ai_family == AF_INET) { - HTTP_IPv4 *ipv4 = (void*) &((struct sockaddr_in*)rp->ai_addr)->sin_addr; - pending_connect_add_ipv4(pending_connect, *ipv4); - } else if (rp->ai_family == AF_INET6) { - HTTP_IPv6 *ipv6 = (void*) &((struct sockaddr_in6*)rp->ai_addr)->sin6_addr; - pending_connect_add_ipv6(pending_connect, *ipv6); - } - } - - freeaddrinfo(res); - - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - - sock->raw = BAD_SOCKET; - sock->user_data = user_data; - sock->pending_connect = pending_connect; - sock->sec = sec; - -#ifdef HTTPS_ENABLED - sock->ssl = NULL; -#endif - - socket_update(sock); -} - -// Just like socket_connect, but the raw IPv4 address is specified -void socket_connect_ipv4(Socket *sock, SecureContext *sec, - HTTP_IPv4 addr, uint16_t port, void *user_data) -{ - PendingConnect *pending_connect; - - pending_connect = pending_connect_init(HTTP_STR(""), port, 1); - if (pending_connect == NULL) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return; - } - - pending_connect_add_ipv4(pending_connect, addr); - - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - - sock->raw = BAD_SOCKET; - sock->user_data = user_data; - sock->pending_connect = pending_connect; - sock->sec = sec; - -#ifdef HTTPS_ENABLED - sock->ssl = NULL; -#endif - - socket_update(sock); -} - -// Just like socket_connect, but the raw IPv6 address is specified -void socket_connect_ipv6(Socket *sock, SecureContext *sec, - HTTP_IPv6 addr, uint16_t port, void *user_data) -{ - PendingConnect *pending_connect; - - pending_connect = pending_connect_init(HTTP_STR(""), port, 1); - if (pending_connect == NULL) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return; - } - - pending_connect_add_ipv6(pending_connect, addr); - - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - - sock->raw = BAD_SOCKET; - sock->user_data = user_data; - sock->pending_connect = pending_connect; - sock->sec = sec; - -#ifdef HTTPS_ENABLED - sock->ssl = NULL; -#endif - - socket_update(sock); -} - -void socket_accept(Socket *sock, SecureContext *sec, RAW_SOCKET raw) -{ - sock->state = SOCKET_STATE_ACCEPTED; - sock->raw = raw; - sock->events = 0; - sock->user_data = NULL; - sock->pending_connect = NULL; - sock->sec = sec; - -#ifdef HTTPS_ENABLED - sock->ssl = NULL; -#endif - - if (set_socket_blocking(raw, false) < 0) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return; - } - - socket_update(sock); -} - -void socket_close(Socket *sock) -{ - // TODO: maybe we don't want to always set to SHUTDOWN. What if the socket is DIED for instance? - sock->state = SOCKET_STATE_SHUTDOWN; - sock->events = 0; - socket_update(sock); -} - -bool socket_ready(Socket *sock) -{ - return sock->state == SOCKET_STATE_ESTABLISHED_READY; -} - -bool socket_died(Socket *sock) -{ - return sock->state == SOCKET_STATE_DIED; -} - -// TODO: when is the pending_connect data freed? - -static bool connect_pending(void) -{ -#ifdef _WIN32 - return WSAGetLastError() == WSAEWOULDBLOCK; -#else - return errno == EINPROGRESS; -#endif -} - -static bool -connect_failed_because_or_peer_2(int err) -{ -#ifdef _WIN32 - return err == WSAECONNREFUSED - || err == WSAETIMEDOUT - || err == WSAENETUNREACH - || err == WSAEHOSTUNREACH; -#else - return err == ECONNREFUSED - || err == ETIMEDOUT - || err == ENETUNREACH - || err == EHOSTUNREACH; -#endif -} - -static bool -connect_failed_because_or_peer(void) -{ -#ifdef _WIN32 - int err = WSAGetLastError(); -#else - int err = errno; -#endif - return connect_failed_because_or_peer_2(err); -} - -// Processes the socket until it's either ready, died, or would block -void socket_update(Socket *sock) -{ - sock->events = 0; - - bool again; - do { - - again = false; - - switch (sock->state) { - case SOCKET_STATE_PENDING: - { - // In this state we need to pop an address from the pending connect - // data and try connect to it. This state is reached when a socket - // is initialized using one of the socket_connect functions or by - // failing to connect before the established state is reached. - - // If this isn't the first connection attempt we may have old - // descriptors that need freeing before trying again. - { -#ifdef HTTPS_ENABLED - if (sock->ssl) { - SSL_free(sock->ssl); - sock->ssl = NULL; - } -#endif - if (sock->raw != BAD_SOCKET) - CLOSE_SOCKET(sock->raw); - } - - // Pop the next address from the pending connect data - PendingConnectAddr addr; - if (next_connect_addr(sock->pending_connect, &addr) < 0) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - uint16_t port = sock->pending_connect->port; - - // Create a kernel socket object - int family = addr.is_ipv4 ? AF_INET : AF_INET6; - RAW_SOCKET raw = socket(family, SOCK_STREAM, 0); - if (raw == BAD_SOCKET) { - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - again = true; - break; - } - - // Configure it - if (set_socket_blocking(raw, false) < 0) { - CLOSE_SOCKET(raw); - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - - // Now perform the connect - - struct sockaddr_in connect_buf_4; - struct sockaddr_in6 connect_buf_6; - struct sockaddr* connect_buf; - int connect_buf_len; - - if (addr.is_ipv4) { - - connect_buf = (struct sockaddr*) &connect_buf_4; - connect_buf_len = sizeof(connect_buf_4); - - connect_buf_4.sin_family = AF_INET; - connect_buf_4.sin_port = htons(port); - memcpy(&connect_buf_4.sin_addr, &addr.ipv4, sizeof(HTTP_IPv4)); - - } else { - - connect_buf = (struct sockaddr*) &connect_buf_6; - connect_buf_len = sizeof(connect_buf_6); - - connect_buf_6.sin6_family = AF_INET6; - connect_buf_6.sin6_port = htons(port); - memcpy(&connect_buf_6.sin6_addr, &addr.ipv6, sizeof(HTTP_IPv6)); - } - - int ret = connect(raw, connect_buf, connect_buf_len); - - // We divide the connect() results in four categories: - // - // 1) The connect resolved immediately. I'm not sure how this can happen, - // but we may as well handle it. This allows us to skip a step. - // - // 2) The connect operation is pending. This is what we expect most of the time. - // - // 3) The connect operation failed because the target address wasn't good - // for some reason. It make sense to try connecting to a different address - // - // 4) The connect operation failed for unknown reasons. There isn't much we - // can do at this point. - - if (ret == 0) { - // Connected immediately - sock->raw = raw; - sock->state = SOCKET_STATE_CONNECTED; - sock->events = 0; - again = true; - break; - } - - if (connect_pending()) { // TODO: I'm pretty sure all the error numbers need to be changed for windows - // Connection pending - sock->raw = raw; - sock->state = SOCKET_STATE_CONNECTING; - sock->events = POLLOUT; - break; - } - - // Connect failed - - // If remote peer not working, try next address - if (connect_failed_because_or_peer()) { - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - again = true; - } else { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - } - } - break; - - case SOCKET_STATE_CONNECTING: - { - // We reach this point when a connect() operation on the - // socket started and then the descriptor was marked as - // ready for output. This means the operation is complete. - - int err = 0; - socklen_t len = sizeof(err); - - if (getsockopt(sock->raw, SOL_SOCKET, SO_ERROR, (void*) &err, &len) < 0 || err != 0) { - - // If remote peer not working, try next address - if (connect_failed_because_or_peer_2(err)) { - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - again = true; - break; - } - - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - - // Connect succeeded - sock->state = SOCKET_STATE_CONNECTED; - sock->events = 0; - again = true; - } - break; - - case SOCKET_STATE_CONNECTED: - { - if (!socket_secure(sock)) { - - pending_connect_free(sock->pending_connect); - sock->pending_connect = NULL; - - sock->events = 0; - sock->state = SOCKET_STATE_ESTABLISHED_READY; - - } else { -#ifdef HTTPS_ENABLED - // Start SSL handshake - - if (sock->ssl == NULL) { - sock->ssl = SSL_new(sock->sec->ctx); - if (sock->ssl == NULL) { - ERR_print_errors_fp(stderr); // TODO: remove - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - - if (SSL_set_fd(sock->ssl, sock->raw) != 1) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - - char *hostname = NULL; - if (sock->pending_connect->hostname[0]) - hostname = sock->pending_connect->hostname; - - if (hostname) - SSL_set_tlsext_host_name(sock->ssl, hostname); - } - - int ret = SSL_connect(sock->ssl); - if (ret == 1) { - // Handshake done - - pending_connect_free(sock->pending_connect); - sock->pending_connect = NULL; - - sock->state = SOCKET_STATE_ESTABLISHED_READY; - sock->events = 0; - break; - } - - int err = SSL_get_error(sock->ssl, ret); - if (err == SSL_ERROR_WANT_READ) { - sock->events = POLLIN; - break; - } - - if (err == SSL_ERROR_WANT_WRITE) { - sock->events = POLLOUT; - break; - } - - sock->state = SOCKET_STATE_PENDING; - sock->events = 0; - again = true; -#else - assert(0); -#endif - } - } - break; - - case SOCKET_STATE_ACCEPTED: - { - if (!socket_secure(sock)) { - sock->state = SOCKET_STATE_ESTABLISHED_READY; - sock->events = 0; - } else { -#ifdef HTTPS_ENABLED - // Start server-side SSL handshake - if (!sock->ssl) { - - sock->ssl = SSL_new(sock->sec->ctx); - if (sock->ssl == NULL) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - - if (SSL_set_fd(sock->ssl, sock->raw) != 1) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - } - - int ret = SSL_accept(sock->ssl); - if (ret == 1) { - // Handshake done - sock->state = SOCKET_STATE_ESTABLISHED_READY; - sock->events = 0; - break; - } - - int err = SSL_get_error(sock->ssl, ret); - if (err == SSL_ERROR_WANT_READ) { - sock->events = POLLIN; - break; - } - - if (err == SSL_ERROR_WANT_WRITE) { - sock->events = POLLOUT; - break; - } - - // Server socket error - close the connection - sock->state = SOCKET_STATE_DIED; - sock->events = 0; -#else - assert(0); -#endif - } - } - break; - - case SOCKET_STATE_ESTABLISHED_WAIT: - { - sock->state = SOCKET_STATE_ESTABLISHED_READY; - sock->events = 0; - } - break; - - case SOCKET_STATE_SHUTDOWN: - { - if (!socket_secure(sock)) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - } else { -#ifdef HTTPS_ENABLED - int ret = SSL_shutdown(sock->ssl); - if (ret == 1) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - break; - } - - int err = SSL_get_error(sock->ssl, ret); - if (err == SSL_ERROR_WANT_READ) { - sock->events = POLLIN; - break; - } - - if (err == SSL_ERROR_WANT_WRITE) { - sock->events = POLLOUT; - break; - } - - sock->state = SOCKET_STATE_DIED; - sock->events = 0; -#else - assert(0); -#endif - } - } - break; - - default: - // Do nothing - break; - } - - } while (again); -} - -static bool would_block(void) -{ -#ifdef _WIN32 - int err = WSAGetLastError(); - return err == WSAEWOULDBLOCK; -#else - return errno == EAGAIN || errno == EWOULDBLOCK; -#endif -} - -static bool interrupted(void) -{ -#ifdef _WIN32 - return false; -#else - return errno == EINTR; -#endif -} - -int socket_read(Socket *sock, char *dst, int max) -{ - // If not ESTABLISHED, set state to DIED and return - if (sock->state != SOCKET_STATE_ESTABLISHED_READY) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return 0; - } - - if (!socket_secure(sock)) { - int ret = recv(sock->raw, dst, max, 0); - if (ret == 0) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - } else { - if (ret < 0) { - if (would_block()) { - sock->state = SOCKET_STATE_ESTABLISHED_WAIT; - sock->events = POLLIN; - } else { - if (!interrupted()) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - } - } - ret = 0; - } - } - return ret; - } else { -#ifdef HTTPS_ENABLED - int ret = SSL_read(sock->ssl, dst, max); - if (ret <= 0) { - int err = SSL_get_error(sock->ssl, ret); - if (err == SSL_ERROR_WANT_READ) { - sock->state = SOCKET_STATE_ESTABLISHED_WAIT; - sock->events = POLLIN; - } else if (err == SSL_ERROR_WANT_WRITE) { - sock->state = SOCKET_STATE_ESTABLISHED_WAIT; - sock->events = POLLOUT; - } else { - fprintf(stderr, "OpenSSL error in socket_read: "); - ERR_print_errors_fp(stderr); - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - } - ret = 0; - } - return ret; -#else - assert(0); - return -1; -#endif - } -} - -int socket_write(Socket *sock, char *src, int len) -{ - // If not ESTABLISHED, set state to DIED and return - if (sock->state != SOCKET_STATE_ESTABLISHED_READY) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - return 0; - } - - if (!socket_secure(sock)) { - int ret = send(sock->raw, src, len, 0); - if (ret < 0) { - if (would_block()) { - sock->state = SOCKET_STATE_ESTABLISHED_WAIT; - sock->events = POLLOUT; - } else { - if (!interrupted()) { - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - } - } - ret = 0; - } - return ret; - } else { -#ifdef HTTPS_ENABLED - int ret = SSL_write(sock->ssl, src, len); - if (ret <= 0) { - int err = SSL_get_error(sock->ssl, ret); - if (err == SSL_ERROR_WANT_READ) { - sock->state = SOCKET_STATE_ESTABLISHED_WAIT; - sock->events = POLLIN; - } else if (err == SSL_ERROR_WANT_WRITE) { - sock->state = SOCKET_STATE_ESTABLISHED_WAIT; - sock->events = POLLOUT; - } else { - fprintf(stderr, "OpenSSL error in socket_write: "); - ERR_print_errors_fp(stderr); - sock->state = SOCKET_STATE_DIED; - sock->events = 0; - } - ret = 0; - } - return ret; -#else - assert(0); -#endif - } -} - -bool socket_secure(Socket *sock) -{ -#ifdef HTTPS_ENABLED - return sock->sec != NULL; -#else - (void) sock; - return false; -#endif -} - -void socket_free(Socket *sock) -{ - if (sock->pending_connect != NULL) - pending_connect_free(sock->pending_connect); - - if (sock->raw != BAD_SOCKET) - CLOSE_SOCKET(sock->raw); - -#ifdef HTTPS_ENABLED - if (sock->ssl) - SSL_free(sock->ssl); -#endif - - sock->state = SOCKET_STATE_FREE; -} - -void socket_set_user_data(Socket *sock, void *user_data) -{ - sock->user_data = user_data; -} - -void *socket_get_user_data(Socket *sock) -{ - return sock->user_data; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// src/socket_pool.c -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/socket_pool.c" -#include -#include - -#ifdef __linux__ -#include -#include -#endif - -#ifndef HTTP_AMALGAMATION -#include "socket_pool.h" -#endif - -#define SOCKET_HARD_LIMIT (1<<10) -#define MAX_CERTS 10 - -struct SocketPool { - - SecureContext sec; - - RAW_SOCKET listen_sock; - RAW_SOCKET secure_sock; - - int num_socks; - int max_socks; - Socket socks[]; -}; - -int socket_pool_global_init(void) -{ - int ret = socket_raw_global_init(); - if (ret < 0) - return -1; - - secure_context_global_init(); - return 0; -} - -void socket_pool_global_free(void) -{ - secure_context_global_free(); - socket_raw_global_free(); -} - -SocketPool *socket_pool_init(HTTP_String addr, - uint16_t port, uint16_t secure_port, int max_socks, - bool reuse_addr, int backlog, HTTP_String cert_file, - HTTP_String key_file) -{ - if (max_socks > SOCKET_HARD_LIMIT) - return NULL; - - SocketPool *pool = malloc(sizeof(SocketPool) + max_socks * sizeof(Socket)); - if (pool == NULL) - return NULL; - - pool->num_socks = 0; - pool->max_socks = max_socks; - - for (int i = 0; i < pool->max_socks; i++) - pool->socks[i].state = SOCKET_STATE_FREE; - - if (port == 0) - pool->listen_sock = BAD_SOCKET; - else { - pool->listen_sock = listen_socket(addr, port, reuse_addr, backlog); - if (pool->listen_sock == BAD_SOCKET) { - free(pool); - return NULL; - } - } - - if (secure_port == 0) - pool->secure_sock = BAD_SOCKET; - else { -#ifndef HTTPS_ENABLED - (void) cert_file; - (void) key_file; - if (pool->listen_sock != BAD_SOCKET) - CLOSE_SOCKET(pool->listen_sock); - free(pool); - return NULL; -#else - if (secure_context_init_as_server(&pool->sec, cert_file, key_file) < 0) { - if (pool->listen_sock != BAD_SOCKET) - CLOSE_SOCKET(pool->listen_sock); - free(pool); - return NULL; - } - - pool->secure_sock = listen_socket(addr, secure_port, reuse_addr, backlog); - if (pool->secure_sock == BAD_SOCKET) { - if (pool->listen_sock != BAD_SOCKET) CLOSE_SOCKET(pool->listen_sock); - free(pool); - return NULL; - } -#endif - } - -#ifdef HTTPS_ENABLED - if (port == 0 && secure_port == 0) { - if (secure_context_init_as_client(&pool->sec) < 0) { - if (pool->listen_sock != BAD_SOCKET) CLOSE_SOCKET(pool->listen_sock); - if (pool->secure_sock != BAD_SOCKET) CLOSE_SOCKET(pool->secure_sock); - free(pool); - return NULL; - } - } -#endif - - for (int i = 0; i < max_socks; i++) - pool->socks[i].state = SOCKET_STATE_FREE; - - return pool; -} - -void socket_pool_free(SocketPool *pool) -{ - for (int i = 0, j = 0; j < pool->num_socks; i++) { - - Socket *sock = &pool->socks[i]; - - if (sock->state == SOCKET_STATE_FREE) - continue; - j++; - - socket_free(sock); - } - - secure_context_free(&pool->sec); - - if (pool->secure_sock != BAD_SOCKET) CLOSE_SOCKET(pool->secure_sock); - if (pool->listen_sock != BAD_SOCKET) CLOSE_SOCKET(pool->listen_sock); -} - -int socket_pool_add_cert(SocketPool *pool, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) -{ - return secure_context_add_cert(&pool->sec, domain, cert_file, key_file); -} - -void socket_pool_set_user_data(SocketPool *pool, SocketHandle handle, void *user_data) -{ - Socket *sock = &pool->socks[handle]; - socket_set_user_data(sock, user_data); -} - -void socket_pool_close(SocketPool *pool, SocketHandle handle) -{ - Socket *sock = &pool->socks[handle]; - socket_close(sock); -} - -static Socket *find_free_socket(SocketPool *pool) -{ - if (pool->num_socks == pool->max_socks) - return NULL; - - int i = 0; - while (pool->socks[i].state != SOCKET_STATE_FREE) - i++; - - return &pool->socks[i]; -} - -int socket_pool_connect(SocketPool *pool, bool secure, - HTTP_String addr, uint16_t port, void *user_data) -{ - Socket *sock = find_free_socket(pool); - if (sock == NULL) - return -1; - - socket_connect(sock, secure ? &pool->sec : NULL, addr, port, user_data); - - if (socket_died(sock)) { - socket_free(sock); - return -1; - } - - pool->num_socks++; - return 0; -} - -int socket_pool_connect_ipv4(SocketPool *pool, bool secure, - HTTP_IPv4 addr, uint16_t port, void *user_data) -{ - Socket *sock = find_free_socket(pool); - if (sock == NULL) - return -1; - - socket_connect_ipv4(sock, secure ? &pool->sec : NULL, addr, port, user_data); - - if (socket_died(sock)) { - socket_free(sock); - return -1; - } - - pool->num_socks++; - return 0; -} - -int socket_pool_connect_ipv6(SocketPool *pool, bool secure, - HTTP_IPv6 addr, uint16_t port, void *user_data) -{ - Socket *sock = find_free_socket(pool); - if (sock == NULL) - return -1; - - socket_connect_ipv6(sock, secure ? &pool->sec : NULL, addr, port, user_data); - - if (socket_died(sock)) { - socket_free(sock); - return -1; - } - - pool->num_socks++; - return 0; -} - -#include // TODO: remove - -SocketEvent socket_pool_wait(SocketPool *pool) -{ - for (;;) { - - // First, iterate over all sockets to find one that - // died or is ready. - - for (int i = 0, j = 0; j < pool->num_socks; i++) { - - Socket *sock = &pool->socks[i]; - - if (sock->state == SOCKET_STATE_FREE) - continue; - j++; - - if (socket_died(sock)) { - void *user_data = socket_get_user_data(sock); - socket_free(sock); - pool->num_socks--; - return (SocketEvent) { SOCKET_EVENT_DIED, -1, user_data }; - } - - if (socket_ready(sock)) - return (SocketEvent) { SOCKET_EVENT_READY, i, socket_get_user_data(sock) }; - - assert(sock->events); - } - - // If we reached this point, we either have no sockets - // or all sockets need to wait for some event. Waiting - // when no sockets are available is only allowed when - // the pool is in server mode. - - int indices[SOCKET_HARD_LIMIT+2]; - struct pollfd polled[SOCKET_HARD_LIMIT+2]; - int num_polled = 0; - - if (pool->num_socks < pool->max_socks) { - - if (pool->listen_sock != BAD_SOCKET) { - indices[num_polled] = -1; - polled[num_polled].fd = pool->listen_sock; - polled[num_polled].events = POLLIN; - polled[num_polled].revents = 0; - num_polled++; - } - - if (pool->secure_sock != BAD_SOCKET) { - indices[num_polled] = -1; - polled[num_polled].fd = pool->secure_sock; - polled[num_polled].events = POLLIN; - polled[num_polled].revents = 0; - num_polled++; - } - } - - for (int i = 0, j = 0; j < pool->num_socks; i++) { - - Socket *sock = &pool->socks[i]; - - if (sock->state == SOCKET_STATE_FREE) - continue; - j++; - - indices[num_polled] = i; - polled[num_polled].fd = sock->raw; - polled[num_polled].events = sock->events; - polled[num_polled].revents = 0; - num_polled++; - } - - if (num_polled == 0) - return (SocketEvent) { SOCKET_EVENT_ERROR, -1, NULL }; - - int ret = POLL(polled, num_polled, -1); - if (ret < 0) { - - if (errno == EINTR) - return (SocketEvent) { SOCKET_EVENT_SIGNAL, -1, NULL }; - - return (SocketEvent) { SOCKET_EVENT_ERROR, -1, NULL }; - } - - for (int i = 0; i < num_polled; i++) { - - Socket *sock; - - if (polled[i].fd == pool->listen_sock || polled[i].fd == pool->secure_sock) { - - bool secure = false; - if (polled[i].fd == pool->secure_sock) - secure = true; - - Socket *sock = find_free_socket(pool); - if (sock == NULL) - continue; - - RAW_SOCKET raw = accept(polled[i].fd, NULL, NULL); - if (raw == BAD_SOCKET) - continue; - - socket_accept(sock, secure ? &pool->sec : NULL, raw); - - if (socket_died(sock)) { - socket_free(sock); - continue; - } - - pool->num_socks++; - - } else { - int j = indices[i]; - sock = &pool->socks[j]; - - if (polled[i].revents) - socket_update(sock); - } - } - } - - // This branch is unreachable - return (SocketEvent) { SOCKET_EVENT_ERROR, -1, NULL }; -} - -int socket_pool_read(SocketPool *pool, SocketHandle handle, char *dst, int len) -{ - return socket_read(&pool->socks[handle], dst, len); -} - -int socket_pool_write(SocketPool *pool, SocketHandle handle, char *src, int len) -{ - return socket_write(&pool->socks[handle], src, len); -} - -bool socket_pool_secure(SocketPool *pool, SocketHandle handle) -{ - return socket_secure(&pool->socks[handle]); -} - -//////////////////////////////////////////////////////////////////////////////////////// -// src/client.c -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/client.c" -#include -#include -#include -#include - -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#define POLL WSAPoll -#endif - -#ifdef __linux__ -#include -#define POLL poll -#endif - -#ifndef HTTP_AMALGAMATION -#include "client.h" -#include "engine.h" -#include "socket_pool.h" -#endif - -#define CLIENT_MAX_CONNS 256 - -typedef enum { - CLIENT_CONNECTION_FREE, - CLIENT_CONNECTION_INIT, - CLIENT_CONNECTION_INIT_ERROR, - CLIENT_CONNECTION_WAIT, - CLIENT_CONNECTION_DONE, -} ClientConnectionState; - -typedef struct { - ClientConnectionState state; - uint16_t gen; - SocketHandle sock; - HTTP_Engine eng; - bool trace; - void* user_data; -} ClientConnection; - -struct HTTP_Client { - - SocketPool *socket_pool; - - int num_conns; - ClientConnection conns[CLIENT_MAX_CONNS]; - - int ready_head; - int ready_count; - int ready[CLIENT_MAX_CONNS]; -}; - -int http_global_init(void) -{ - int ret = socket_pool_global_init(); - if (ret < 0) - return -1; - return 0; -} - -void http_global_free(void) -{ - socket_pool_global_free(); -} - -// Rename the memory function -static void* client_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) { - (void)data; - switch (tag) { - case HTTP_MEMFUNC_MALLOC: - return malloc(len); - case HTTP_MEMFUNC_FREE: - free(ptr); - return NULL; - } - return NULL; -} - -HTTP_Client *http_client_init(void) -{ - HTTP_Client *client = malloc(sizeof(HTTP_Client)); - if (client == NULL) - return NULL; - - int max_socks = 100; - SocketPool *socket_pool = socket_pool_init(HTTP_STR(""), 0, 0, max_socks, false, 0, HTTP_STR(""), HTTP_STR("")); - if (socket_pool == NULL) { - free(client); - return NULL; - } - client->socket_pool = socket_pool; - - for (int i = 0; i < CLIENT_MAX_CONNS; i++) { - client->conns[i].state = CLIENT_CONNECTION_FREE; - client->conns[i].gen = 1; - } - - client->num_conns = 0; - client->ready_head = 0; - client->ready_count = 0; - - return client; -} - -void http_client_free(HTTP_Client *client) -{ - for (int i = 0, j = 0; j < client->num_conns; i++) { - - if (client->conns[i].state == CLIENT_CONNECTION_FREE) - continue; - j++; - - // TODO - } - - socket_pool_free(client->socket_pool); - free(client); -} - -int http_client_get_builder(HTTP_Client *client, HTTP_RequestBuilder *builder) -{ - if (client->num_conns == CLIENT_MAX_CONNS) - return -1; - - int i = 0; - while (client->conns[i].state != CLIENT_CONNECTION_FREE) - i++; - - client->conns[i].sock = -1; - client->conns[i].user_data = NULL; - client->conns[i].trace = false; - client->conns[i].state = CLIENT_CONNECTION_INIT; - http_engine_init(&client->conns[i].eng, 1, client_memfunc, NULL); - - client->num_conns++; - - *builder = (HTTP_RequestBuilder) { client, i, client->conns[i].gen }; - return 0; -} - -int http_client_wait(HTTP_Client *client, HTTP_Response **result, void **user_data) -{ - while (client->ready_count == 0) { - - SocketEvent event = socket_pool_wait(client->socket_pool); - switch (event.type) { - - case SOCKET_EVENT_DIED: - { - ClientConnection *conn = event.user_data; - conn->state = CLIENT_CONNECTION_DONE; - - int tail = (client->ready_head + client->ready_count) % CLIENT_MAX_CONNS; - client->ready[tail] = conn - client->conns; - client->ready_count++; - } - break; - - case SOCKET_EVENT_READY: - { - ClientConnection *conn = event.user_data; - - if (conn->sock == -1) - conn->sock = event.handle; - - HTTP_EngineState engine_state; - engine_state = http_engine_state(&conn->eng); - - if (engine_state == HTTP_ENGINE_STATE_CLIENT_RECV_BUF) { - int len; - char *buf; - buf = http_engine_recvbuf(&conn->eng, &len); - if (buf) { - int ret = socket_pool_read(client->socket_pool, conn->sock, buf, len); - if (conn->trace) - print_bytes(HTTP_STR(">> "), (HTTP_String) { buf, ret }); - http_engine_recvack(&conn->eng, ret); - } - } else if (engine_state == HTTP_ENGINE_STATE_CLIENT_SEND_BUF) { - int len; - char *buf; - buf = http_engine_sendbuf(&conn->eng, &len); - if (buf) { - int ret = socket_pool_write(client->socket_pool, conn->sock, buf, len); - if (conn->trace) - print_bytes(HTTP_STR("<< "), (HTTP_String) { buf, ret }); - http_engine_sendack(&conn->eng, ret); - } - } - - engine_state = http_engine_state(&conn->eng); - - if (engine_state == HTTP_ENGINE_STATE_CLIENT_CLOSED || - engine_state == HTTP_ENGINE_STATE_CLIENT_READY) - socket_pool_close(client->socket_pool, conn->sock); - } - break; - - case SOCKET_EVENT_ERROR: - return -1; - - case SOCKET_EVENT_SIGNAL: - return 1; - } - } - - int index = client->ready[client->ready_head]; - client->ready_head = (client->ready_head + 1) % CLIENT_MAX_CONNS; - client->ready_count--; - - ClientConnection *conn = &client->conns[index]; - - HTTP_Response *result2 = http_engine_getres(&conn->eng); - - if (result) - *result = result2; - - if (user_data) - *user_data = conn->user_data; - - if (result2 == NULL) { - http_engine_free(&conn->eng); - conn->state = CLIENT_CONNECTION_FREE; - client->num_conns--; - } else { - result2->context = client; - } - - return 0; -} - -static ClientConnection *client_builder_to_conn(HTTP_RequestBuilder handle) -{ - if (handle.data0 == NULL) - return NULL; - - HTTP_Client *client = handle.data0; - - if (handle.data1 >= CLIENT_MAX_CONNS) - return NULL; - - ClientConnection *conn = &client->conns[handle.data1]; - - if (handle.data2 != conn->gen) - return NULL; - - return conn; -} - -void http_request_builder_user_data(HTTP_RequestBuilder builder, void *user_data) -{ - ClientConnection *conn = client_builder_to_conn(builder); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT) - return; - - conn->user_data = user_data; -} - -void http_request_builder_trace(HTTP_RequestBuilder builder, bool trace) -{ - ClientConnection *conn = client_builder_to_conn(builder); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT) - return; - - conn->trace = trace; -} - -void http_request_builder_line(HTTP_RequestBuilder builder, HTTP_Method method, HTTP_String url) -{ - ClientConnection *conn = client_builder_to_conn(builder); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT) - return; - - HTTP_Client *client = builder.data0; - - HTTP_URL parsed_url; - int ret = http_parse_url(url.ptr, url.len, &parsed_url); - if (ret != url.len) { - conn->state = CLIENT_CONNECTION_INIT_ERROR; - return; - } - - bool secure = false; - if (http_streq(parsed_url.scheme, HTTP_STR("https"))) { - secure = true; - } else if (!http_streq(parsed_url.scheme, HTTP_STR("http"))) { - conn->state = CLIENT_CONNECTION_INIT_ERROR; - return; - } - - int port = parsed_url.authority.port; - if (port == 0) { - if (secure) - port = 443; - else - port = 80; - } - - switch (parsed_url.authority.host.mode) { - case HTTP_HOST_MODE_IPV4: ret = socket_pool_connect_ipv4(client->socket_pool, secure, parsed_url.authority.host.ipv4, port, conn); break; - case HTTP_HOST_MODE_IPV6: ret = socket_pool_connect_ipv6(client->socket_pool, secure, parsed_url.authority.host.ipv6, port, conn); break; - case HTTP_HOST_MODE_NAME: ret = socket_pool_connect (client->socket_pool, secure, parsed_url.authority.host.name, port, conn); break; - case HTTP_HOST_MODE_VOID: ret = -1; return; - } - - if (ret < 0) { - conn->state = CLIENT_CONNECTION_INIT_ERROR; - return; - } - - http_engine_url(&conn->eng, method, url, 1); -} - -void http_request_builder_header(HTTP_RequestBuilder handle, HTTP_String str) -{ - ClientConnection *conn = client_builder_to_conn(handle); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT) - return; - - http_engine_header(&conn->eng, str); -} - -void http_request_builder_body(HTTP_RequestBuilder handle, HTTP_String str) -{ - ClientConnection *conn = client_builder_to_conn(handle); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT) - return; - - http_engine_body(&conn->eng, str); -} - -void http_request_builder_submit(HTTP_RequestBuilder handle) -{ - HTTP_Client *client = handle.data0; - ClientConnection *conn = client_builder_to_conn(handle); - if (conn == NULL) - return; - if (conn->state != CLIENT_CONNECTION_INIT && - conn->state != CLIENT_CONNECTION_INIT_ERROR) - return; - - // TODO: invalidate the handle - - if (conn->state == CLIENT_CONNECTION_INIT_ERROR) { - - conn->state = CLIENT_CONNECTION_DONE; - - int tail = (client->ready_head + client->ready_count) % CLIENT_MAX_CONNS; - client->ready[tail] = conn - client->conns; - client->ready_count++; - - } else { - http_engine_done(&conn->eng); - conn->state = CLIENT_CONNECTION_WAIT; - } -} - -void http_response_free(HTTP_Response *res) -{ - if (res == NULL) - return; - - HTTP_Client *client = res->context; - - ClientConnection *conn = NULL; - for (int i = 0, j = 0; j < client->num_conns; i++) { - - if (client->conns[i].state == CLIENT_CONNECTION_FREE) - continue; - j++; - - if (client->conns[i].state != CLIENT_CONNECTION_DONE) - continue; - - if (http_engine_getres(&client->conns[i].eng) == res) { - conn = &client->conns[i]; - break; - } - } - - HTTP_ASSERT(conn); - - http_engine_free(&conn->eng); - conn->state = CLIENT_CONNECTION_FREE; - client->num_conns--; -} - -static HTTP_Client *default_client___; // TODO: deinitialize the default client when http_global_free is called - -static HTTP_Client *get_default_client(void) -{ - if (default_client___ == NULL) - default_client___ = http_client_init(); - return default_client___; -} - -HTTP_Response *http_get(HTTP_String url, HTTP_String *headers, int num_headers) -{ - HTTP_Client *client = get_default_client(); - if (client == NULL) - return NULL; - - HTTP_RequestBuilder builder; - int ret = http_client_get_builder(client, &builder); - if (ret < 0) - return NULL; - http_request_builder_line(builder, HTTP_METHOD_GET, url); - for (int i = 0; i < num_headers; i++) - http_request_builder_header(builder, headers[i]); - http_request_builder_submit(builder); - - HTTP_Response *res; - ret = http_client_wait(client, &res, NULL); // TODO: it's assumed there is only one request pending - if (ret < 0) - return NULL; - - return res; -} - -HTTP_Response *http_post(HTTP_String url, HTTP_String *headers, int num_headers, HTTP_String body) -{ - HTTP_Client *client = get_default_client(); - if (client == NULL) - return NULL; - - HTTP_RequestBuilder builder; - int ret = http_client_get_builder(client, &builder); - if (ret < 0) - return NULL; - http_request_builder_line(builder, HTTP_METHOD_POST, url); - for (int i = 0; i < num_headers; i++) - http_request_builder_header(builder, headers[i]); - http_request_builder_body(builder, body); - http_request_builder_submit(builder); - - HTTP_Response *res; - ret = http_client_wait(client, &res, NULL); // TODO: it's assumed there is only one request pending - if (ret < 0) - return NULL; - - return res; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// src/server.c -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/server.c" -#include -#include -#include -#include - -#ifndef HTTP_AMALGAMATION -#include "engine.h" -#include "server.h" -#include "socket_pool.h" -#endif - -#define MAX_CONNS (1<<10) - -typedef struct { - bool used; - uint16_t gen; - HTTP_Engine engine; - SocketHandle sock; -} Connection; - -struct HTTP_Server { - - SocketPool *socket_pool; - - int num_conns; - Connection conns[MAX_CONNS]; - - int ready_head; - int ready_count; - int ready[MAX_CONNS]; -}; - -HTTP_Server *http_server_init(HTTP_String addr, uint16_t port) -{ - return http_server_init_ex(addr, port, 0, HTTP_STR(""), HTTP_STR("")); -} - -HTTP_Server *http_server_init_ex(HTTP_String addr, uint16_t port, - uint16_t secure_port, HTTP_String cert_file, HTTP_String key_file) -{ - HTTP_Server *server = malloc(sizeof(HTTP_Server)); - if (server == NULL) - return NULL; - - int backlog = 32; - bool reuse_addr = true; - SocketPool *socket_pool = socket_pool_init(addr, port, secure_port, MAX_CONNS, reuse_addr, backlog, cert_file, key_file); - if (socket_pool == NULL) { - free(server); - return NULL; - } - - server->socket_pool = socket_pool; - server->num_conns = 0; - server->ready_head = 0; - server->ready_count = 0; - - for (int i = 0; i < MAX_CONNS; i++) { - server->conns[i].used = false; - server->conns[i].gen = 1; - } - - return server; -} - -void http_server_free(HTTP_Server *server) -{ - for (int i = 0, j = 0; j < server->num_conns; i++) { - - if (!server->conns[i].used) - continue; - j++; - - // TODO - } - - socket_pool_free(server->socket_pool); - free(server); -} - -int http_server_add_website(HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) -{ - return socket_pool_add_cert(server->socket_pool, domain, cert_file, key_file); -} - -static void* server_memfunc(HTTP_MemoryFuncTag tag, void *ptr, int len, void *data) { - (void)data; - switch (tag) { - case HTTP_MEMFUNC_MALLOC: - return malloc(len); - case HTTP_MEMFUNC_FREE: - free(ptr); - return NULL; - } - return NULL; -} - -int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseBuilder *builder) -{ - while (server->ready_count == 0) { - - SocketEvent event = socket_pool_wait(server->socket_pool); - switch (event.type) { - - case SOCKET_EVENT_DIED: - { - Connection *conn = event.user_data; - HTTP_ASSERT(conn); - - http_engine_free(&conn->engine); - conn->used = false; - conn->gen++; - server->num_conns--; - } - break; - - case SOCKET_EVENT_READY: - { - Connection *conn = event.user_data; - if (conn == NULL) { - - // Connection was just accepted - - if (server->num_conns == MAX_CONNS) { - socket_pool_close(server->socket_pool, event.handle); - break; - } - - int i = 0; - while (server->conns[i].used) - i++; - - conn = &server->conns[i]; - conn->used = true; - conn->sock = event.handle; - http_engine_init(&conn->engine, 0, server_memfunc, NULL); - socket_pool_set_user_data(server->socket_pool, event.handle, conn); - server->num_conns++; - } - - switch (http_engine_state(&conn->engine)) { - - int len; - char *buf; - - case HTTP_ENGINE_STATE_SERVER_RECV_BUF: - buf = http_engine_recvbuf(&conn->engine, &len); - if (buf) { - int ret = socket_pool_read(server->socket_pool, conn->sock, buf, len); - http_engine_recvack(&conn->engine, ret); - } - break; - - case HTTP_ENGINE_STATE_SERVER_SEND_BUF: - buf = http_engine_sendbuf(&conn->engine, &len); - if (buf) { - int ret = socket_pool_write(server->socket_pool, conn->sock, buf, len); - http_engine_sendack(&conn->engine, ret); - } - break; - - default: - break; - } - - switch (http_engine_state(&conn->engine)) { - - int tail; - - case HTTP_ENGINE_STATE_SERVER_PREP_STATUS: - tail = (server->ready_head + server->ready_count) % MAX_CONNS; - server->ready[tail] = conn - server->conns; - server->ready_count++; - break; - - case HTTP_ENGINE_STATE_SERVER_CLOSED: - socket_pool_close(server->socket_pool, conn->sock); - break; - - default: - break; - } - } - break; - - case SOCKET_EVENT_ERROR: - return -1; - - case SOCKET_EVENT_SIGNAL: - return 1; - } - } - - int index = server->ready[server->ready_head]; - server->ready_head = (server->ready_head + 1) % MAX_CONNS; - server->ready_count--; - - *req = http_engine_getreq(&server->conns[index].engine); - (*req)->secure = socket_pool_secure(server->socket_pool, server->conns[index].sock); - - *builder = (HTTP_ResponseBuilder) { server, index, server->conns[index].gen }; - return 0; -} - -static Connection* -server_builder_to_conn(HTTP_ResponseBuilder builder) -{ - HTTP_Server *server = builder.data0; - if (builder.data1 >= MAX_CONNS) - return NULL; - - Connection *conn = &server->conns[builder.data1]; - if (conn->gen != builder.data2) - return NULL; - - return conn; -} - -void http_response_builder_status(HTTP_ResponseBuilder res, int status) -{ - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; - - http_engine_status(&conn->engine, status); -} - -void http_response_builder_header(HTTP_ResponseBuilder res, HTTP_String str) -{ - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; - - http_engine_header(&conn->engine, str); -} - -void http_response_builder_body(HTTP_ResponseBuilder res, HTTP_String str) -{ - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; - - http_engine_body(&conn->engine, str); -} - -void http_response_builder_bodycap(HTTP_ResponseBuilder res, int mincap) -{ - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; - - http_engine_bodycap(&conn->engine, mincap); -} - -char *http_response_builder_bodybuf(HTTP_ResponseBuilder res, int *cap) -{ - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) { - *cap = 0; - return NULL; - } - - return http_engine_bodybuf(&conn->engine, cap); -} - -void http_response_builder_bodyack(HTTP_ResponseBuilder res, int num) -{ - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; - - http_engine_bodyack(&conn->engine, num); -} - -void http_response_builder_undo(HTTP_ResponseBuilder res) -{ - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; - - http_engine_undo(&conn->engine); -} - -void http_response_builder_done(HTTP_ResponseBuilder res) -{ - HTTP_Server *server = res.data0; - Connection *conn = server_builder_to_conn(res); - if (conn == NULL) - return; - - http_engine_done(&conn->engine); - - conn->gen++; - if (conn->gen == 0 || conn->gen == UINT16_MAX) - conn->gen = 1; - - HTTP_EngineState state = http_engine_state(&conn->engine); - - if (state == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) { - int tail = (server->ready_head + server->ready_count) % MAX_CONNS; - server->ready[tail] = res.data1; - server->ready_count++; - } - - if (state == HTTP_ENGINE_STATE_SERVER_CLOSED) - socket_pool_close(server->socket_pool, conn->sock); -} - -//////////////////////////////////////////////////////////////////////////////////////// -// 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, -} RouteType; - -typedef struct { - RouteType type; - HTTP_String endpoint; - HTTP_String path; - HTTP_RouterFunc func; - void *ptr; -} Route; - -struct HTTP_Router { - int num_routes; - int max_routes; - Route routes[]; -}; - -HTTP_Router *http_router_init(void) -{ - int max_routes = 32; - HTTP_Router *router = malloc(sizeof(HTTP_Router) + max_routes * sizeof(Route)); - if (router == NULL) - return NULL; - router->max_routes = max_routes; - router->num_routes = 0; - return router; -} - -void http_router_free(HTTP_Router *router) -{ - free(router); -} - -void http_router_dir(HTTP_Router *router, HTTP_String endpoint, HTTP_String path) -{ - if (router->num_routes == router->max_routes) - abort(); - Route *route = &router->routes[router->num_routes++]; - route->type = ROUTE_STATIC_DIR; - route->endpoint = endpoint; - route->path = path; -} - -void http_router_func(HTTP_Router *router, HTTP_Method method, - HTTP_String endpoint, HTTP_RouterFunc func, void *ptr) -{ - if (router->num_routes == router->max_routes) - abort(); - Route *route = &router->routes[router->num_routes++]; - (void) method; // TODO: Don't ignore the method - route->type = ROUTE_DYNAMIC; - route->endpoint = endpoint; - route->func = func; - route->ptr = ptr; -} - -static int valid_component_char(char c) -{ - return is_alpha(c) || is_digit(c) || c == '-' || c == '_' || c == '.'; // TODO -} - -static int parse_and_sanitize_path(HTTP_String path, HTTP_String *comps, int max_comps) -{ - // We treat relative and absolute paths the same - if (path.len > 0 && path.ptr[0] == '/') { - path.ptr++; - path.len--; - if (path.len == 0) - return 0; - } - - int num = 0; - int cur = 0; - for (;;) { - if (cur == path.len || !valid_component_char(path.ptr[cur])) - return -1; // Empty component - int start = cur; - do - cur++; - while (cur < path.len && valid_component_char(path.ptr[cur])); - HTTP_String comp = { path.ptr + start, cur - start }; - - if (http_streq(comp, HTTP_STR(".."))) { - if (num == 0) - return -1; - num--; - } else if (!http_streq(comp, HTTP_STR("."))) { - if (num == max_comps) - return -1; - comps[num++] = comp; - } - - if (cur < path.len) { - if (path.ptr[cur] != '/') - return -1; - cur++; - } - - if (cur == path.len) - break; - } - - return num; -} - -static int -serialize_parsed_path(HTTP_String *comps, int num_comps, char *dst, int max) -{ - int len = 0; - for (int i = 0; i < num_comps; i++) - len += comps[i].len + 1; - - if (len >= max) - return -1; - - int copied = 0; - for (int i = 0; i < num_comps; i++) { - - if (i > 0) - dst[copied++] = '/'; - - memcpy(dst + copied, - comps[i].ptr, - comps[i].len); - - copied += comps[i].len; - } - - dst[copied] = '\0'; - return copied; -} - -#define MAX_COMPS 32 - -static int sanitize_path(HTTP_String path, char *dst, int max) -{ - HTTP_String comps[MAX_COMPS]; - int num_comps = parse_and_sanitize_path(path, comps, MAX_COMPS); - if (num_comps < 0) return -1; - - return serialize_parsed_path(comps, num_comps, dst, max); -} - -static int swap_parents(HTTP_String original_parent_path, HTTP_String new_parent_path, HTTP_String path, char *mem, int max) -{ - int num_original_parent_path_comps; - HTTP_String original_parent_path_comps[MAX_COMPS]; - - int num_new_parent_path_comps; - HTTP_String new_parent_path_comps[MAX_COMPS]; - - int num_path_comps; - HTTP_String path_comps[MAX_COMPS]; - - num_original_parent_path_comps = parse_and_sanitize_path(original_parent_path, original_parent_path_comps, MAX_COMPS); - num_new_parent_path_comps = parse_and_sanitize_path(new_parent_path, new_parent_path_comps, MAX_COMPS); - num_path_comps = parse_and_sanitize_path(path, path_comps, MAX_COMPS); - if (num_original_parent_path_comps < 0 || num_new_parent_path_comps < 0 || num_path_comps < 0) - return -1; - - int match = 1; - if (num_path_comps < num_original_parent_path_comps) - match = 0; - else { - for (int i = 0; i < num_original_parent_path_comps; i++) - if (!http_streq(original_parent_path_comps[i], path_comps[i])) { - match = 0; - break; - } - } - if (!match) - return 0; - - int num_result_comps = num_new_parent_path_comps + num_path_comps - num_original_parent_path_comps; - if (num_result_comps < 0 || num_result_comps > MAX_COMPS) - return -1; - - HTTP_String result_comps[MAX_COMPS]; - for (int i = 0; i < num_new_parent_path_comps; i++) - result_comps[i] = new_parent_path_comps[i]; - - for (int i = 0; i < num_path_comps; i++) - result_comps[num_new_parent_path_comps + i] = path_comps[num_original_parent_path_comps + i]; - - return serialize_parsed_path(result_comps, num_result_comps, mem, max); -} - -#if _WIN32 -typedef HANDLE File; -#else -typedef int File; -#endif - -static int file_open(const char *path, File *handle, int *size) -{ -#ifdef _WIN32 - *handle = CreateFileA( - path, - GENERIC_READ, - FILE_SHARE_READ, - NULL, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, - NULL - ); - if (*handle == INVALID_HANDLE_VALUE) { - DWORD error = GetLastError(); - if (error == ERROR_FILE_NOT_FOUND) - return 1; - if (error == ERROR_ACCESS_DENIED) - return 1; - return -1; - } - LARGE_INTEGER fileSize; - if (!GetFileSizeEx(*handle, &fileSize)) { - CloseHandle(*handle); - return -1; - } - if (fileSize.QuadPart > INT_MAX) { - CloseHandle(*handle); - return -1; - } - *size = (int) fileSize.QuadPart; - return 0; -#else - *handle = open(path, O_RDONLY); - if (*handle < 0) { - if (errno == ENOENT) - return 1; - return -1; - } - struct stat info; - if (fstat(*handle, &info) < 0) { - close(*handle); - return -1; - } - if (S_ISDIR(info.st_mode)) { - close(*handle); - return 1; - } - if (info.st_size > INT_MAX) { - close(*handle); - return -1; - } - *size = (int) info.st_size; - return 0; -#endif -} - -static void file_close(File file) -{ -#ifdef _WIN32 - CloseHandle(file); -#else - close(file); -#endif -} - -static int file_read(File file, char *dst, int max) -{ -#ifdef _WIN32 - DWORD num; - BOOL ok = ReadFile(file, dst, max, &num, NULL); - if (!ok) - return -1; - return (int) num; -#else - return read(file, dst, max); -#endif -} - -static int serve_file_or_index(HTTP_ResponseBuilder res, HTTP_String base_endpoint, HTTP_String base_path, HTTP_String endpoint) -{ - char mem[1<<12]; - int ret = swap_parents(base_endpoint, base_path, endpoint, mem, sizeof(mem)); - if (ret <= 0) - return ret; - HTTP_String path = {mem, ret}; // Note that this is zero terminated - - int size; - File file; - ret = file_open(path.ptr, &file, &size); - if (ret == -1) { - http_response_builder_status(res, 500); - http_response_builder_done(res); - return 1; - } - if (ret == 1) { - - // File missing - - char index[] = "index.html"; - if (path.len + sizeof(index) + 1 > sizeof(mem)) { - http_response_builder_status(res, 500); - http_response_builder_done(res); - return 1; - } - path.ptr[path.len++] = '/'; - memcpy(path.ptr + path.len, index, sizeof(index)); - path.len += sizeof(index)-1; - - ret = file_open(path.ptr, &file, &size); - if (ret == -1) { - http_response_builder_status(res, 500); - http_response_builder_done(res); - return 1; - } - if (ret == 1) - return 0; // File missing - } - HTTP_ASSERT(ret == 0); - - int cap; - char *dst; - http_response_builder_status(res, 200); - http_response_builder_bodycap(res, size); - dst = http_response_builder_bodybuf(res, &cap); - if (dst) { - int copied = 0; - while (copied < size) { - int ret = file_read(file, dst + copied, size - copied); - if (ret < 0) goto err; - if (ret == 0) break; - copied += ret; - } - if (copied < size) goto err; - http_response_builder_bodyack(res, size); - } - http_response_builder_done(res); - file_close(file); - return 1; -err: - http_response_builder_bodyack(res, 0); - http_response_builder_undo(res); - http_response_builder_status(res, 500); - http_response_builder_done(res); - file_close(file); - return 1; -} - -static int serve_dynamic_route(Route *route, HTTP_Request *req, HTTP_ResponseBuilder res) -{ - char path_mem[1<<12]; - int path_len = sanitize_path(req->url.path, path_mem, (int) sizeof(path_mem)); - if (path_len < 0) { - http_response_builder_status(res, 400); - http_response_builder_body(res, HTTP_STR("Invalid path")); - http_response_builder_done(res); - return 1; - } - HTTP_String path = {path_mem, path_len}; - - if (!http_streq(path, route->endpoint)) - return 0; - - route->func(req, res, route->ptr); - return 1; -} - -void http_router_resolve(HTTP_Router *router, HTTP_Request *req, HTTP_ResponseBuilder res) -{ - for (int i = 0; i < router->num_routes; i++) { - Route *route = &router->routes[i]; - switch (route->type) { - case ROUTE_STATIC_DIR: - if (serve_file_or_index(res, - route->endpoint, - route->path, - req->url.path)) - return; - break; - - case ROUTE_DYNAMIC: - if (serve_dynamic_route(route, req, res)) - return; - break; - - default: - http_response_builder_status(res, 500); - http_response_builder_done(res); - return; - } - } - http_response_builder_status(res, 404); - http_response_builder_done(res); -} - -int http_serve(char *addr, int port, HTTP_Router *router) -{ - int ret; - - HTTP_Server *server = http_server_init_ex((HTTP_String) { addr, strlen(addr) }, port, 0, (HTTP_String) {}, (HTTP_String) {}); - if (server == NULL) { - http_router_free(router); - return -1; - } - - for (;;) { - HTTP_Request *req; - HTTP_ResponseBuilder res; - ret = http_server_wait(server, &req, &res); - if (ret < 0) { - http_server_free(server); - http_router_free(router); - return -1; - } - if (ret == 0) - continue; - http_router_resolve(router, req, res); - } - - http_server_free(server); - http_router_free(router); - return 0; -} - -//////////////////////////////////////////////////////////////////////////////////////// -// 3p/crypt_blowfish.h +// src/crypt_blowfish.h //////////////////////////////////////////////////////////////////////////////////////// /* @@ -6135,7 +33,7 @@ extern char *_crypt_gensalt_blowfish_rn(const char *prefix, #endif //////////////////////////////////////////////////////////////////////////////////////// -// 3p/crypt_blowfish.c +// src/crypt_blowfish.c //////////////////////////////////////////////////////////////////////////////////////// /* @@ -7048,5947 +946,11 @@ char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, return output; } -//////////////////////////////////////////////////////////////////////////////////////// -// 3p/wl.h -//////////////////////////////////////////////////////////////////////////////////////// - -#include -#include - -typedef struct WL_Runtime WL_Runtime; -typedef struct WL_Compiler WL_Compiler; - -typedef struct { - char *ptr; - int len; -} WL_String; - -typedef struct { - char *ptr; - int len; - int cur; -} WL_Arena; - -typedef struct { - char *ptr; - int len; -} WL_Program; - -typedef enum { - WL_ADD_ERROR, - WL_ADD_AGAIN, - WL_ADD_LINK, -} WL_AddResultType; - -typedef struct { - WL_AddResultType type; - WL_String path; -} WL_AddResult; - -typedef enum { - WL_EVAL_NONE, - WL_EVAL_DONE, - WL_EVAL_ERROR, - WL_EVAL_OUTPUT, - WL_EVAL_SYSVAR, - WL_EVAL_SYSCALL, -} WL_EvalResultType; - -typedef struct { - WL_EvalResultType type; - WL_String str; -} WL_EvalResult; - -WL_Compiler* wl_compiler_init (WL_Arena *arena); -WL_AddResult wl_compiler_add (WL_Compiler *compiler, WL_String content); -int wl_compiler_link (WL_Compiler *compiler, WL_Program *program); -WL_String wl_compiler_error (WL_Compiler *compiler); -int wl_dump_ast (WL_Compiler *compiler, char *dst, int cap); -void wl_dump_program (WL_Program program); - -WL_Runtime* wl_runtime_init (WL_Arena *arena, WL_Program program); -WL_EvalResult wl_runtime_eval (WL_Runtime *rt); -WL_String wl_runtime_error (WL_Runtime *rt); -void wl_runtime_dump (WL_Runtime *rt); - -bool wl_streq (WL_String a, char *b, int blen); -int wl_arg_count (WL_Runtime *rt); -bool wl_arg_none (WL_Runtime *rt, int idx); -bool wl_arg_bool (WL_Runtime *rt, int idx, bool *x); -bool wl_arg_s64 (WL_Runtime *rt, int idx, int64_t *x); -bool wl_arg_f64 (WL_Runtime *rt, int idx, double *x); -bool wl_arg_str (WL_Runtime *rt, int idx, WL_String *x); -bool wl_arg_array (WL_Runtime *rt, int idx); -bool wl_arg_map (WL_Runtime *rt, int idx); -bool wl_peek_none (WL_Runtime *rt, int off); -bool wl_peek_bool (WL_Runtime *rt, int off, bool *x); -bool wl_peek_s64 (WL_Runtime *rt, int off, int64_t *x); -bool wl_peek_f64 (WL_Runtime *rt, int off, double *x); -bool wl_peek_str (WL_Runtime *rt, int off, WL_String *x); -bool wl_pop_any (WL_Runtime *rt); -bool wl_pop_none (WL_Runtime *rt); -bool wl_pop_bool (WL_Runtime *rt, bool *x); -bool wl_pop_s64 (WL_Runtime *rt, int64_t *x); -bool wl_pop_f64 (WL_Runtime *rt, double *x); -bool wl_pop_str (WL_Runtime *rt, WL_String *x); -void wl_push_none (WL_Runtime *rt); -void wl_push_true (WL_Runtime *rt); -void wl_push_false (WL_Runtime *rt); -void wl_push_s64 (WL_Runtime *rt, int64_t x); -void wl_push_f64 (WL_Runtime *rt, double x); -void wl_push_str (WL_Runtime *rt, WL_String x); -void wl_push_array (WL_Runtime *rt, int cap); -void wl_push_map (WL_Runtime *rt, int cap); -void wl_push_arg (WL_Runtime *rt, int idx); -void wl_insert (WL_Runtime *rt); -void wl_append (WL_Runtime *rt); - -//////////////////////////////////////////////////////////////////////////////////////// -// 3p/wl.c -//////////////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include -#include - -#ifndef WL_NOINCLUDE -#include "wl.h" -#endif // WL_NOINCLUDE - -///////////////////////////////////////////////////////////////////////// -// BASIC -///////////////////////////////////////////////////////////////////////// - -typedef struct { - char *ptr; - int len; -} String; - -typedef struct { - char *buf; - int cap; - bool yes; -} Error; - -#define S(X) (String) { (X), SIZEOF(X)-1 } - -#ifdef _WIN32 -#define LLD "lld" -#define LLU "llu" -#else -#define LLD "ld" -#define LLU "lu" -#endif - -#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) -#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) -#define SIZEOF(X) (int) sizeof(X) -#define ALIGNOF(X) (int) _Alignof(X) -#define COUNT(X) (int) (sizeof(X)/sizeof((X)[0])) - -#ifndef NDEBUG -#define UNREACHABLE __builtin_trap() -#define ASSERT(X) if (!(X)) __builtin_trap(); -#else -#define UNREACHABLE {} -#define ASSERT(X) {} -#endif - -static bool is_space(char c) -{ - return c == ' ' || c == '\t' || c == '\r' || c == '\n'; -} - -static bool is_digit(char c) -{ - return c >= '0' && c <= '9'; -} - -static bool is_alpha(char c) -{ - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); -} - -static bool is_printable(char c) -{ - return c >= ' ' && c <= '~'; -} - -static bool is_hex_digit(char c) -{ - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); -} - -#if 0 -static char to_lower(char c) -{ - if (c >= 'A' && c <= 'Z') - return c - 'A' + 'a'; - return c; -} -#endif - -static int hex_digit_to_int(char c) -{ - if (c >= 'a' && c <= 'f') - return c - 'a' + 10; - - if (c >= 'A' && c <= 'F') - return c - 'A' + 10; - - return c - '0'; -} - -static bool streq(String a, String b) -{ - if (a.len != b.len) - return false; - for (int i = 0; i < a.len; i++) - if (a.ptr[i] != b.ptr[i]) - return false; - return true; -} - -#if 0 -static bool streqcase(String a, String b) -{ - if (a.len != b.len) - return false; - for (int i = 0; i < a.len; i++) - if (to_lower(a.ptr[i]) != to_lower(b.ptr[i])) - return false; - return true; -} -#endif - -#define REPORT(err, fmt, ...) report((err), __FILE__, __LINE__, fmt, ## __VA_ARGS__) -static void report(Error *err, char *file, int line, char *fmt, ...) -{ - if (err->yes) return; - - if (err->cap > 0) { - - va_list args; - va_start(args, fmt); - int len = vsnprintf(err->buf, err->cap, fmt, args); - va_end(args); - ASSERT(len >= 0); - - if (err->cap > len) { - int ret = snprintf(err->buf + len, err->cap - len, - " (reported at %s:%d)", file, line); - ASSERT(ret >= 0); - len += ret; - } - - if (len > err->cap) - len = err->cap-1; - err->buf[len] = '\0'; - } - - err->yes = true; -} - -///////////////////////////////////////////////////////////////////////// -// ARENA -///////////////////////////////////////////////////////////////////////// - -static void *alloc(WL_Arena *a, int len, int align) -{ - int pad = -(intptr_t) (a->ptr + a->cur) & (align-1); - if (a->len - a->cur < len + pad) - return NULL; - void *ret = a->ptr + a->cur + pad; - a->cur += pad + len; - return ret; -} - -static bool grow_alloc(WL_Arena *a, char *p, int new_len) -{ - int new_cur = (p - a->ptr) + new_len; - if (new_cur > a->len) - return false; - a->cur = new_cur; - return true; -} - -#if 0 -static String copystr(String s, WL_Arena *a) -{ - char *p = alloc(a, s.len, 1); - if (p == NULL) - return (String) { NULL, 0 }; - memcpy(p, s.ptr, s.len); - return (String) { p, s.len }; -} -#endif - -///////////////////////////////////////////////////////////////////////// -// WRITER -///////////////////////////////////////////////////////////////////////// - -typedef struct { - char *dst; - int cap; - int len; -} Writer; - -static void write_raw_mem(Writer *w, void *ptr, int len) -{ - if (w->cap > w->len) { - int cpy = MIN(w->cap - w->len, len); - if (ptr && w->dst) - memcpy(w->dst + w->len, ptr, cpy); - } - w->len += len; -} - -static void write_raw_u8 (Writer *w, uint8_t x) { write_raw_mem(w, &x, SIZEOF(x)); } -//static void write_raw_u16(Writer *w, uint16_t x) { write_raw_mem(w, &x, SIZEOF(x)); } -static void write_raw_u32(Writer *w, uint32_t x) { write_raw_mem(w, &x, SIZEOF(x)); } -//static void write_raw_u64(Writer *w, uint64_t x) { write_raw_mem(w, &x, SIZEOF(x)); } -//static void write_raw_s8 (Writer *w, int8_t x) { write_raw_mem(w, &x, SIZEOF(x)); } -//static void write_raw_s16(Writer *w, int16_t x) { write_raw_mem(w, &x, SIZEOF(x)); } -//static void write_raw_s32(Writer *w, int32_t x) { write_raw_mem(w, &x, SIZEOF(x)); } -static void write_raw_s64(Writer *w, int64_t x) { write_raw_mem(w, &x, SIZEOF(x)); } -//static void write_raw_f32(Writer *w, float x) { write_raw_mem(w, &x, SIZEOF(x)); } -static void write_raw_f64(Writer *w, double x) { write_raw_mem(w, &x, SIZEOF(x)); } - -static void write_text(Writer *w, String str) -{ - write_raw_mem(w, str.ptr, str.len); -} - -static void write_text_s64(Writer *w, int64_t n) -{ - int len; - if (w->len < w->cap) - len = snprintf(w->dst + w->len, w->cap - w->len, "%" LLD, n); - else - len = snprintf(NULL, 0, "%" LLD, n); - ASSERT(len >= 0); - w->len += len; -} - -static void write_text_f64(Writer *w, double n) -{ - int len; - if (w->len < w->cap) - len = snprintf(w->dst + w->len, w->cap - w->len, "%2.2f", n); - else - len = snprintf(NULL, 0, "%2.2f", n); - ASSERT(len >= 0); - w->len += len; -} - -static void patch_mem(Writer *w, void *src, int off, int len) -{ - ASSERT(off + len <= w->len); - if (off < w->cap) { - int cpy = MIN(w->cap - off, len); - memcpy(w->dst + off, src, cpy); - } -} - -///////////////////////////////////////////////////////////////////////// -// PARSER -///////////////////////////////////////////////////////////////////////// - -typedef struct { - char *src; - int len; - int cur; -} Scanner; - -typedef enum { - TOKEN_END, - TOKEN_ERROR, - TOKEN_IDENT, - TOKEN_KWORD_IF, - TOKEN_KWORD_ELSE, - TOKEN_KWORD_WHILE, - TOKEN_KWORD_FOR, - TOKEN_KWORD_IN, - TOKEN_KWORD_PROCEDURE, - TOKEN_KWORD_LET, - TOKEN_KWORD_NONE, - TOKEN_KWORD_TRUE, - TOKEN_KWORD_FALSE, - TOKEN_KWORD_INCLUDE, - TOKEN_KWORD_LEN, - TOKEN_KWORD_ESCAPE, - TOKEN_VALUE_FLOAT, - TOKEN_VALUE_INT, - TOKEN_VALUE_STR, - TOKEN_OPER_EQL, - TOKEN_OPER_NQL, - TOKEN_OPER_LSS, - TOKEN_OPER_GRT, - TOKEN_OPER_ADD, - TOKEN_OPER_SUB, - TOKEN_OPER_MUL, - TOKEN_OPER_DIV, - TOKEN_OPER_MOD, - TOKEN_OPER_ASS, - TOKEN_OPER_SHOVEL, - TOKEN_PAREN_OPEN, - TOKEN_PAREN_CLOSE, - TOKEN_BRACKET_OPEN, - TOKEN_BRACKET_CLOSE, - TOKEN_CURLY_OPEN, - TOKEN_CURLY_CLOSE, - TOKEN_DOT, - TOKEN_COMMA, - TOKEN_COLON, - TOKEN_DOLLAR, - TOKEN_NEWLINE, -} TokType; - -typedef struct { - TokType type; - int64_t ival; - double fval; - String sval; -} Token; - -typedef enum { - NODE_PROCEDURE_DECL, - NODE_PROCEDURE_ARG, - NODE_PROCEDURE_CALL, - NODE_VAR_DECL, - NODE_COMPOUND, - NODE_GLOBAL, - NODE_IFELSE, - NODE_FOR, - NODE_WHILE, - NODE_INCLUDE, - NODE_SELECT, - NODE_NESTED, - NODE_OPER_ESCAPE, - NODE_OPER_LEN, - NODE_OPER_POS, - NODE_OPER_NEG, - NODE_OPER_ASS, - NODE_OPER_EQL, - NODE_OPER_NQL, - NODE_OPER_LSS, - NODE_OPER_GRT, - NODE_OPER_ADD, - NODE_OPER_SUB, - NODE_OPER_MUL, - NODE_OPER_DIV, - NODE_OPER_MOD, - NODE_OPER_SHOVEL, - NODE_VALUE_INT, - NODE_VALUE_FLOAT, - NODE_VALUE_STR, - NODE_VALUE_NONE, - NODE_VALUE_TRUE, - NODE_VALUE_FALSE, - NODE_VALUE_VAR, - NODE_VALUE_SYSVAR, - NODE_VALUE_HTML, - NODE_VALUE_ARRAY, - NODE_VALUE_MAP, -} NodeType; - -typedef struct Node Node; -struct Node { - NodeType type; - Node *next; - - Node *key; - - Node *left; - Node *right; - - Node *child; - - uint64_t ival; - double fval; - String sval; - - String html_tag; - Node* html_attr; - Node* html_child; - bool html_body; - - Node *if_cond; - Node *if_branch1; - Node *if_branch2; - - Node *while_cond; - Node *while_body; - - String for_var1; - String for_var2; - Node* for_set; - - String proc_name; - Node* proc_args; - Node* proc_body; - - String var_name; - Node* var_value; - - String include_path; - Node* include_next; - Node* include_root; -}; - -typedef struct { - Node *node; - Node *includes; - int errlen; -} ParseResult; - -typedef struct { - Scanner s; - WL_Arena* arena; - char* errbuf; - int errmax; - int errlen; - Node* include_head; - Node** include_tail; -} Parser; - -static bool consume_str(Scanner *s, String x) -{ - if (x.len == 0) - return false; - - if (x.len > s->len - s->cur) - return false; - - for (int i = 0; i < x.len; i++) - if (s->src[s->cur+i] != x.ptr[i]) - return false; - - s->cur += x.len; - return true; -} - -#if 0 -static void write_token(Writer *w, Token token) -{ - switch (token.type) { - - default : write_text(w, S("???")); break; - case TOKEN_END : write_text(w, S("")); break; - case TOKEN_ERROR : write_text(w, S("")); break; - case TOKEN_IDENT : write_text(w, token.sval); break; - case TOKEN_KWORD_IF : write_text(w, S("if")); break; - case TOKEN_KWORD_ELSE : write_text(w, S("else")); break; - case TOKEN_KWORD_WHILE : write_text(w, S("while")); break; - case TOKEN_KWORD_FOR : write_text(w, S("for")); break; - case TOKEN_KWORD_IN : write_text(w, S("in")); break; - case TOKEN_KWORD_PROCEDURE: write_text(w, S("procedure")); break; - case TOKEN_KWORD_LET : write_text(w, S("let")); break; - case TOKEN_KWORD_NONE : write_text(w, S("none")); break; - case TOKEN_KWORD_TRUE : write_text(w, S("true")); break; - case TOKEN_KWORD_FALSE : write_text(w, S("false")); break; - case TOKEN_KWORD_INCLUDE: write_text(w, S("include")); break; - case TOKEN_KWORD_LEN : write_text(w, S("len")); break; - case TOKEN_KWORD_ESCAPE: write_text(w, S("escape")); break; - case TOKEN_VALUE_FLOAT : write_text_f64(w, token.fval); break; - case TOKEN_VALUE_INT : write_text_s64(w, token.ival); break; - case TOKEN_OPER_ASS : write_text(w, S("=")); break; - case TOKEN_OPER_EQL : write_text(w, S("==")); break; - case TOKEN_OPER_NQL : write_text(w, S("!=")); break; - case TOKEN_OPER_LSS : write_text(w, S("<")); break; - case TOKEN_OPER_GRT : write_text(w, S(">")); break; - case TOKEN_OPER_ADD : write_text(w, S("+")); break; - case TOKEN_OPER_SUB : write_text(w, S("-")); break; - case TOKEN_OPER_MUL : write_text(w, S("*")); break; - case TOKEN_OPER_DIV : write_text(w, S("/")); break; - case TOKEN_OPER_MOD : write_text(w, S("%")); break; - case TOKEN_OPER_SHOVEL : write_text(w, S("<<")); break; - case TOKEN_PAREN_OPEN : write_text(w, S("(")); break; - case TOKEN_PAREN_CLOSE : write_text(w, S(")")); break; - case TOKEN_BRACKET_OPEN : write_text(w, S("[")); break; - case TOKEN_BRACKET_CLOSE: write_text(w, S("]")); break; - case TOKEN_CURLY_OPEN : write_text(w, S("{")); break; - case TOKEN_CURLY_CLOSE : write_text(w, S("}")); break; - case TOKEN_DOT : write_text(w, S(".")); break; - case TOKEN_COMMA : write_text(w, S(",")); break; - case TOKEN_COLON : write_text(w, S(":")); break; - case TOKEN_DOLLAR : write_text(w, S("$")); break; - case TOKEN_NEWLINE : write_text(w, S("\\n")); break; - - case TOKEN_VALUE_STR: - write_text(w, S("\"")); - write_text(w, token.sval); // TODO: Escape - write_text(w, S("\"")); - break; - - } -} -#endif - -static void parser_report(Parser *p, char *fmt, ...) -{ - if (p->errmax == 0 || p->errlen > 0) - return; - - int line = 1; - int cur = 0; - while (cur < p->s.cur) { - if (p->s.src[cur] == '\n') - line++; - cur++; - } - - int len = snprintf(p->errbuf, p->errmax, "Error (line %d): ", line); - ASSERT(len >= 0); - - va_list args; - va_start(args, fmt); - int ret = vsnprintf(p->errbuf + len, p->errmax - len, fmt, args); - va_end(args); - ASSERT(ret >= 0); - len += ret; - - p->errlen = len; -} - -static Node *alloc_node(Parser *p) -{ - Node *n = alloc(p->arena, sizeof(Node), _Alignof(Node)); - if (n == NULL) { - parser_report(p, "Out of memory"); - return NULL; - } - - return n; -} - -static Token next_token(Parser *p) -{ - for (;;) { - while (p->s.cur < p->s.len && is_space(p->s.src[p->s.cur])) - p->s.cur++; - - if (!consume_str(&p->s, S(""))) - break; - p->s.cur++; - } - } - - if (p->s.cur == p->s.len) - return (Token) { .type=TOKEN_END }; - char c = p->s.src[p->s.cur]; - - if (is_alpha(c) || c == '_') { - - int start = p->s.cur; - do - p->s.cur++; - while (p->s.cur < p->s.len && (is_alpha(p->s.src[p->s.cur]) || is_digit(p->s.src[p->s.cur]) || p->s.src[p->s.cur] == '_')); - - String kword = { - p->s.src + start, - p->s.cur - start - }; - - if (streq(kword, S("if"))) return (Token) { .type=TOKEN_KWORD_IF }; - if (streq(kword, S("else"))) return (Token) { .type=TOKEN_KWORD_ELSE }; - if (streq(kword, S("while"))) return (Token) { .type=TOKEN_KWORD_WHILE }; - if (streq(kword, S("for"))) return (Token) { .type=TOKEN_KWORD_FOR }; - if (streq(kword, S("in"))) return (Token) { .type=TOKEN_KWORD_IN }; - if (streq(kword, S("procedure"))) return (Token) { .type=TOKEN_KWORD_PROCEDURE }; - if (streq(kword, S("let"))) return (Token) { .type=TOKEN_KWORD_LET }; - if (streq(kword, S("none"))) return (Token) { .type=TOKEN_KWORD_NONE }; - if (streq(kword, S("true"))) return (Token) { .type=TOKEN_KWORD_TRUE }; - if (streq(kword, S("false"))) return (Token) { .type=TOKEN_KWORD_FALSE }; - if (streq(kword, S("include"))) return (Token) { .type=TOKEN_KWORD_INCLUDE }; - if (streq(kword, S("len"))) return (Token) { .type=TOKEN_KWORD_LEN }; - if (streq(kword, S("escape"))) return (Token) { .type=TOKEN_KWORD_ESCAPE }; - - return (Token) { .type=TOKEN_IDENT, .sval=kword }; - } - - if (is_digit(c)) { - - int peek = p->s.cur; - do - peek++; - while (peek < p->s.len && is_digit(p->s.src[peek])); - - if (p->s.len - peek > 1 && p->s.src[peek] == '.' && is_digit(p->s.src[peek+1])) { - - double buf = 0; - do { - int d = p->s.src[p->s.cur++] - '0'; - buf = buf * 10 + d; - } while (p->s.cur < p->s.len && p->s.src[p->s.cur] != '.'); - - p->s.cur++; - - double q = 1; - do { - int d = p->s.src[p->s.cur++] - '0'; - q /= 10; - buf += q * d; - } while (p->s.cur < p->s.len && is_digit(p->s.src[p->s.cur])); - - return (Token) { .type=TOKEN_VALUE_FLOAT, .fval=buf }; - - } else { - - uint64_t buf = 0; - do { - int d = p->s.src[p->s.cur++] - '0'; - if (buf > (UINT64_MAX - d) / 10) { - parser_report(p, "Integer literal overflow"); - return (Token) { .type=TOKEN_ERROR }; - } - buf = buf * 10 + d; - } while (p->s.cur < p->s.len && is_digit(p->s.src[p->s.cur])); - - return (Token) { .type=TOKEN_VALUE_INT, .ival=buf }; - } - } - - if (c == '\'' || c == '"') { - - char f = c; - p->s.cur++; - - char *buf = NULL; - int len = 0; - - for (;;) { - - int substr_off = p->s.cur; - - while (p->s.cur < p->s.len && is_printable(p->s.src[p->s.cur]) && p->s.src[p->s.cur] != f && p->s.src[p->s.cur] != '\\') - p->s.cur++; - - int substr_len = p->s.cur - substr_off; - - if (buf == NULL) - buf = alloc(p->arena, substr_len+1, 1); - else - if (!grow_alloc(p->arena, buf, len + substr_len+1)) - buf = NULL; - - if (buf == NULL) { - parser_report(p, "Out of memory"); - return (Token) { .type=TOKEN_ERROR }; - } - - if (substr_len > 0) { - memcpy( - buf + len, - p->s.src + substr_off, - p->s.cur - substr_off - ); - len += substr_len; - } - - if (p->s.cur == p->s.len) { - parser_report(p, "String literal wasn't closed"); - return (Token) { .type=TOKEN_ERROR }; - } - - if (!is_printable(p->s.src[p->s.cur])) { - parser_report(p, "Invalid byte in string literal"); - return (Token) { .type=TOKEN_ERROR }; - } - - if (p->s.src[p->s.cur] == f) - break; - - p->s.cur++; - if (p->s.cur == p->s.len) { - parser_report(p, "Missing special character after escape character \\"); - return (Token) { .type=TOKEN_ERROR }; - } - - switch (p->s.src[p->s.cur]) { - case 'n': buf[len++] = '\n'; break; - case 't': buf[len++] = '\t'; break; - case 'r': buf[len++] = '\r'; break; - case '"': buf[len++] = '"'; break; - case '\'': buf[len++] = '\''; break; - case '\\': buf[len++] = '\\'; break; - - case 'x': - { - if (p->s.len - p->s.cur < 3 - || !is_hex_digit(p->s.src[p->s.cur+1]) - || !is_hex_digit(p->s.src[p->s.cur+2])) - return (Token) { .type=TOKEN_ERROR }; - buf[len++] - = (hex_digit_to_int(p->s.src[p->s.cur+1]) << 4) - | (hex_digit_to_int(p->s.src[p->s.cur+2]) << 0); - p->s.cur += 2; - } - break; - - default: - parser_report(p, "Invalid character after escape character \\"); - return (Token) { .type=TOKEN_ERROR }; - } - - p->s.cur++; - } - - p->s.cur++; - return (Token) { .type=TOKEN_VALUE_STR, .sval=(String) { .ptr=buf, .len=len } }; - } - - if (consume_str(&p->s, S("<<"))) return (Token) { .type=TOKEN_OPER_SHOVEL }; - if (consume_str(&p->s, S("=="))) return (Token) { .type=TOKEN_OPER_EQL }; - if (consume_str(&p->s, S("!="))) return (Token) { .type=TOKEN_OPER_NQL }; - if (consume_str(&p->s, S("<"))) return (Token) { .type=TOKEN_OPER_LSS }; - if (consume_str(&p->s, S(">"))) return (Token) { .type=TOKEN_OPER_GRT }; - if (consume_str(&p->s, S("+"))) return (Token) { .type=TOKEN_OPER_ADD }; - if (consume_str(&p->s, S("-"))) return (Token) { .type=TOKEN_OPER_SUB }; - if (consume_str(&p->s, S("*"))) return (Token) { .type=TOKEN_OPER_MUL }; - if (consume_str(&p->s, S("/"))) return (Token) { .type=TOKEN_OPER_DIV }; - if (consume_str(&p->s, S("%"))) return (Token) { .type=TOKEN_OPER_MOD }; - if (consume_str(&p->s, S("="))) return (Token) { .type=TOKEN_OPER_ASS }; - - if (consume_str(&p->s, S("("))) return (Token) { .type=TOKEN_PAREN_OPEN }; - if (consume_str(&p->s, S(")"))) return (Token) { .type=TOKEN_PAREN_CLOSE }; - if (consume_str(&p->s, S("["))) return (Token) { .type=TOKEN_BRACKET_OPEN }; - if (consume_str(&p->s, S("]"))) return (Token) { .type=TOKEN_BRACKET_CLOSE }; - if (consume_str(&p->s, S("{"))) return (Token) { .type=TOKEN_CURLY_OPEN }; - if (consume_str(&p->s, S("}"))) return (Token) { .type=TOKEN_CURLY_CLOSE }; - if (consume_str(&p->s, S("."))) return (Token) { .type=TOKEN_DOT }; - if (consume_str(&p->s, S(","))) return (Token) { .type=TOKEN_COMMA }; - if (consume_str(&p->s, S(":"))) return (Token) { .type=TOKEN_COLON }; - if (consume_str(&p->s, S("$"))) return (Token) { .type=TOKEN_DOLLAR }; - - parser_report(p, "Invalid character '%c'", c); - return (Token) { .type=TOKEN_ERROR }; -} - -static Token next_token_or_newline(Parser *p) -{ - int peek = p->s.cur; - while (peek < p->s.len && is_space(p->s.src[peek]) && p->s.src[peek] != '\n') - peek++; - - if (peek < p->s.len && p->s.src[peek] == '\n') { - p->s.cur = peek+1; - return (Token) { .type=TOKEN_NEWLINE }; - } - - return next_token(p); -} - -enum { - IGNORE_GRT = 1 << 0, - IGNORE_LSS = 1 << 1, - IGNORE_DIV = 1 << 2, -}; - -static Node *parse_stmt(Parser *p, int opflags); -static Node *parse_expr(Parser *p, int opflags); - -static Node *parse_html(Parser *p) -{ - // NOTE: The first < was already consumed - - Token t = next_token(p); - if (t.type != TOKEN_IDENT) { - parser_report(p, "HTML tag doesn't start with a name"); - return NULL; - } - String tagname = t.sval; - - Node *attr_head; - Node **attr_tail = &attr_head; - - bool no_body = false; - Scanner *s = &p->s; - for (;;) { - - int off = s->cur; - - bool quotes = false; - while (s->cur < s->len && s->src[s->cur] != '\\' && (quotes || (s->src[s->cur] != '/' && s->src[s->cur] != '>'))) { - if (s->src[s->cur] == '"') - quotes = !quotes; - s->cur++; - } - - if (s->cur > off) { - - Node *child = alloc_node(p); - if (child == NULL) - return NULL; - - child->type = NODE_VALUE_STR; - child->sval = (String) { p->s.src + off, p->s.cur - off }; - - *attr_tail = child; - attr_tail = &child->next; - } - - if (s->cur == s->len) { - ASSERT(0); // TODO - } - s->cur++; - - if (s->src[s->cur-1] == '>') - break; - - if (s->src[s->cur-1] == '/') { - while (s->cur < s->len && is_space(s->src[s->cur])) - s->cur++; - if (s->cur == s->len || s->src[s->cur] != '>') { - ASSERT(0); // TODO - } - s->cur++; - no_body = true; - break; - } - - ASSERT(s->src[s->cur-1] == '\\'); - - Node *child = parse_stmt(p, IGNORE_GRT | IGNORE_DIV); - if (child == NULL) - return NULL; - - *attr_tail = child; - attr_tail = &child->next; - } - - *attr_tail = NULL; - - Node *child_head; - Node **child_tail = &child_head; - - if (no_body == false) - for (;;) { - - int off = s->cur; - - while (s->cur < s->len && s->src[s->cur] != '\\' && s->src[s->cur] != '<') - s->cur++; - - if (s->cur > off) { - - Node *child = alloc_node(p); - if (child == NULL) - return NULL; - - child->type = NODE_VALUE_STR; - child->sval = (String) { p->s.src + off, p->s.cur - off }; - - *child_tail = child; - child_tail = &child->next; - } - - if (s->cur == s->len) { - ASSERT(0); // TODO - } - s->cur++; - - if (s->src[s->cur-1] == '<') { - - Scanner saved = *s; - t = next_token(p); - if (t.type == TOKEN_OPER_DIV) { - - t = next_token(p); - if (t.type != TOKEN_IDENT) { - ASSERT(0); // TODO - } - String closing_tagname = t.sval; - - if (!streq(closing_tagname, tagname)) { - ASSERT(0); // TODO - } - - t = next_token(p); - if (t.type != TOKEN_OPER_GRT) { - ASSERT(0); - } - - break; - } - - *s = saved; - - Node *child = parse_html(p); - if (child == NULL) - return NULL; - - *child_tail = child; - child_tail = &child->next; - - } else { - - Node *child = parse_stmt(p, IGNORE_LSS); - if (child == NULL) - return NULL; - - *child_tail = child; - child_tail = &child->next; - } - } - - *child_tail = NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_VALUE_HTML; - parent->html_tag = tagname; - parent->html_attr = attr_head; - parent->html_child = child_head; - parent->html_body = !no_body; - - return parent; -} - -static Node *parse_array(Parser *p) -{ - // Left bracket already consumed - - Node *head; - Node **tail = &head; - - Scanner saved = p->s; - Token t = next_token(p); - if (t.type != TOKEN_BRACKET_CLOSE) { - - p->s = saved; - - for (;;) { - - Node *child = parse_expr(p, 0); - if (child == NULL) - return NULL; - - *tail = child; - tail = &child->next; - - saved = p->s; - t = next_token(p); - if (t.type == TOKEN_COMMA) { - saved = p->s; - t = next_token(p); - } - - if (t.type == TOKEN_BRACKET_CLOSE) - break; - - p->s = saved; - } - } - - *tail = NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_VALUE_ARRAY; - parent->child = head; - - return parent; -} - -static Node *parse_map(Parser *p) -{ - // Left bracket already consumed - - Node *head; - Node **tail = &head; - - Scanner saved = p->s; - Token t = next_token(p); - if (t.type != TOKEN_CURLY_CLOSE) { - - p->s = saved; - - for (;;) { - - Node *key; - - saved = p->s; - t = next_token(p); - if (t.type == TOKEN_IDENT) { - - key = alloc_node(p); - if (key == NULL) - return NULL; - - key->type = NODE_VALUE_STR; - key->sval = t.sval; - - } else { - - p->s = saved; - key = parse_expr(p, 0); - if (key == NULL) - return NULL; - } - - t = next_token(p); - if (t.type != TOKEN_COLON) { - parser_report(p, "Missing ':' after key inside map literal"); - return NULL; - } - - Node *child = parse_expr(p, 0); - if (child == NULL) - return NULL; - child->key = key; - - *tail = child; - tail = &child->next; - - saved = p->s; - t = next_token(p); - if (t.type == TOKEN_COMMA) { - saved = p->s; - t = next_token(p); - } - - if (t.type == TOKEN_CURLY_CLOSE) - break; - - p->s = saved; - } - } - - *tail = NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_VALUE_MAP; - parent->child = head; - - return parent; -} - -static int precedence(Token t, int flags) -{ - switch (t.type) { - - case TOKEN_OPER_ASS: - return 1; - - case TOKEN_OPER_SHOVEL: - if (flags & IGNORE_LSS) - return -1; - return 1; - - case TOKEN_OPER_EQL: - case TOKEN_OPER_NQL: - return 2; - - case TOKEN_OPER_LSS: - if (flags & IGNORE_LSS) - return -1; - return 2; - - case TOKEN_OPER_GRT: - if (flags & IGNORE_GRT) - return -1; - return 2; - - case TOKEN_OPER_ADD: - case TOKEN_OPER_SUB: - return 3; - - case TOKEN_OPER_MUL: - case TOKEN_OPER_MOD: - return 4; - - case TOKEN_OPER_DIV: - if (flags & IGNORE_DIV) - return -1; - return 4; - - default: - break; - } - - return -1; -} - -static bool right_associative(Token t) -{ - return t.type == TOKEN_OPER_ASS; -} - -static Node *parse_atom(Parser *p) -{ - Token t = next_token(p); - - Node *ret; - switch (t.type) { - case TOKEN_OPER_ADD: - { - Node *child = parse_atom(p); - if (child == NULL) - return NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_OPER_POS; - parent->left = child; - - ret = parent; - } - break; - - case TOKEN_OPER_SUB: - { - Node *child = parse_atom(p); - if (child == NULL) - return NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_OPER_NEG; - parent->left = child; - - ret = parent; - } - break; - - case TOKEN_KWORD_LEN: - { - Node *child = parse_atom(p); - if (child == NULL) - return NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_OPER_LEN; - parent->left = child; - - ret = parent; - } - break; - - case TOKEN_KWORD_ESCAPE: - { - Node *child = parse_atom(p); - if (child == NULL) - return NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_OPER_ESCAPE; - parent->left = child; - - ret = parent; - } - break; - - case TOKEN_IDENT: - { - Node *node = alloc_node(p); - if (node == NULL) - return NULL; - - node->type = NODE_VALUE_VAR; - node->sval = t.sval; - - ret = node; - } - break; - - case TOKEN_VALUE_INT: - { - Node *node = alloc_node(p); - if (node == NULL) - return NULL; - - node->type = NODE_VALUE_INT; - node->ival = t.ival; - - ret = node; - } - break; - - case TOKEN_VALUE_FLOAT: - { - Node *node = alloc_node(p); - if (node == NULL) - return NULL; - - node->type = NODE_VALUE_FLOAT; - node->fval = t.fval; - - ret = node; - } - break; - - case TOKEN_VALUE_STR: - { - Node *node = alloc_node(p); - if (node == NULL) - return NULL; - - node->type = NODE_VALUE_STR; - node->sval = t.sval; - - ret = node; - } - break; - - case TOKEN_KWORD_NONE: - { - Node *node = alloc_node(p); - if (node == NULL) - return NULL; - - node->type = NODE_VALUE_NONE; - node->sval = t.sval; - - ret = node; - } - break; - - case TOKEN_KWORD_TRUE: - { - Node *node = alloc_node(p); - if (node == NULL) - return NULL; - - node->type = NODE_VALUE_TRUE; - node->sval = t.sval; - - ret = node; - } - break; - case TOKEN_KWORD_FALSE: - { - Node *node = alloc_node(p); - if (node == NULL) - return NULL; - - node->type = NODE_VALUE_FALSE; - node->sval = t.sval; - - ret = node; - } - break; - - case TOKEN_OPER_LSS: - { - Node *node = parse_html(p); - if (node == NULL) - return NULL; - - ret = node; - } - break; - - case TOKEN_PAREN_OPEN: - { - Node *node = parse_expr(p, 0); - if (node == NULL) - return NULL; - - Token t = next_token(p); - if (t.type != TOKEN_PAREN_CLOSE) { - parser_report(p, "Missing ')' after expression"); - return NULL; - } - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_NESTED; - parent->left = node; - - ret = parent; - } - break; - - case TOKEN_BRACKET_OPEN: - { - Node *node = parse_array(p); - if (node == NULL) - return NULL; - - ret = node; - } - break; - - case TOKEN_CURLY_OPEN: - { - Node *node = parse_map(p); - if (node == NULL) - return NULL; - - ret = node; - } - break; - - case TOKEN_DOLLAR: - { - t = next_token(p); - if (t.type != TOKEN_IDENT) { - parser_report(p, "Missing identifier after '$'"); - return NULL; - } - - Node *node = alloc_node(p); - if (node == NULL) - return NULL; - - node->type = NODE_VALUE_SYSVAR; - node->sval = t.sval; - - ret = node; - } - break; - - default: - { - parser_report(p, "Invalid token inside expression"); - } - return NULL; - } - - for (;;) { - Scanner saved = p->s; - t = next_token(p); - if (t.type == TOKEN_DOT) { - - t = next_token(p); - if (t.type != TOKEN_IDENT) { - parser_report(p, "Invalid token after '.' where an identifier was expected"); - return NULL; - } - - Node *child = alloc_node(p); - if (child == NULL) - return NULL; - - child->type = NODE_VALUE_STR; - child->sval = t.sval; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_SELECT; - parent->left = ret; - parent->right = child; - - ret = parent; - - } else if (t.type == TOKEN_BRACKET_OPEN) { - - Node *child = parse_expr(p, 0); - if (child == NULL) - return NULL; - - t = next_token(p); - if (t.type != TOKEN_BRACKET_CLOSE) { - parser_report(p, "Missing token ']'"); - return NULL; - } - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_SELECT; - parent->left = ret; - parent->right = child; - - ret = parent; - - } else if (t.type == TOKEN_PAREN_OPEN && (ret->type == NODE_VALUE_VAR || ret->type == NODE_VALUE_SYSVAR)) { - - Node *arg_head; - Node **arg_tail = &arg_head; - - Scanner saved = p->s; - t = next_token(p); - if (t.type != TOKEN_PAREN_CLOSE) { - - p->s = saved; - - for (;;) { - - Node *argval = parse_expr(p, 0); - if (argval == NULL) - return NULL; - - *arg_tail = argval; - arg_tail = &argval->next; - - t = next_token(p); - if (t.type == TOKEN_PAREN_CLOSE) - break; - - if (t.type != TOKEN_COMMA) { - parser_report(p, "Expected ',' after argument in procedure call"); - return NULL; - } - } - } - - *arg_tail = NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_PROCEDURE_CALL; - parent->left = ret; - parent->right = arg_head; - - ret = parent; - - } else { - p->s = saved; - break; - } - } - - return ret; -} - -static Node *parse_expr_inner(Parser *p, Node *left, int min_prec, int flags) -{ - for (;;) { - - Scanner saved = p->s; - Token t1 = next_token_or_newline(p); - if (precedence(t1, flags) < min_prec) { - p->s = saved; - break; - } - - Node *right = parse_atom(p); - if (right == NULL) - return NULL; - - for (;;) { - - saved = p->s; - Token t2 = next_token_or_newline(p); - int p1 = precedence(t1, flags); - int p2 = precedence(t2, flags); - p->s = saved; - - if (p2 < 0) - break; - - if (p2 <= p1 && (p1 != p2 || !right_associative(t2))) - break; - - right = parse_expr_inner(p, right, p1 + (p2 > p1), flags); - if (right == NULL) - return NULL; - } - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->left = left; - parent->right = right; - - switch (t1.type) { - case TOKEN_OPER_ASS: parent->type = NODE_OPER_ASS; break; - case TOKEN_OPER_EQL: parent->type = NODE_OPER_EQL; break; - case TOKEN_OPER_NQL: parent->type = NODE_OPER_NQL; break; - case TOKEN_OPER_LSS: parent->type = NODE_OPER_LSS; break; - case TOKEN_OPER_GRT: parent->type = NODE_OPER_GRT; break; - case TOKEN_OPER_ADD: parent->type = NODE_OPER_ADD; break; - case TOKEN_OPER_SUB: parent->type = NODE_OPER_SUB; break; - case TOKEN_OPER_MUL: parent->type = NODE_OPER_MUL; break; - case TOKEN_OPER_DIV: parent->type = NODE_OPER_DIV; break; - case TOKEN_OPER_MOD: parent->type = NODE_OPER_MOD; break; - case TOKEN_OPER_SHOVEL: parent->type = NODE_OPER_SHOVEL; break; - default: - parser_report(p, "Operator not implemented"); - return NULL; - } - - left = parent; - } - - return left; -} - -static Node *parse_expr(Parser *p, int flags) -{ - Node *left = parse_atom(p); - if (left == NULL) - return NULL; - - return parse_expr_inner(p, left, 0, flags); -} - -static Node *parse_expr_stmt(Parser *p, int opflags) -{ - Node *e = parse_expr(p, opflags); - if (e == NULL) - return NULL; - - return e; -} - -static Node *parse_ifelse_stmt(Parser *p, int opflags) -{ - Token t = next_token(p); - if (t.type != TOKEN_KWORD_IF) { - parser_report(p, "Missing 'if' keyword before if statement"); - return NULL; - } - - Node *cond = parse_expr(p, 0); - if (cond == NULL) - return NULL; - - t = next_token(p); - if (t.type != TOKEN_COLON) { - parser_report(p, "Missing ':' after if condition"); - return NULL; - } - - Node *if_stmt = parse_stmt(p, opflags); - if (if_stmt == NULL) - return NULL; - - Scanner saved = p->s; - t = next_token(p); - - Node *else_stmt = NULL; - if (t.type == TOKEN_KWORD_ELSE) { - - else_stmt = parse_stmt(p, opflags); - if (else_stmt == NULL) - return NULL; - - } else { - p->s = saved; - } - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_IFELSE; - parent->if_cond = cond; - parent->if_branch1 = if_stmt; - parent->if_branch2 = else_stmt; - - return parent; -} - -static Node *parse_for_stmt(Parser *p, int opflags) -{ - Token t = next_token(p); - if (t.type != TOKEN_KWORD_FOR) { - parser_report(p, "Missing 'for' keyword at the start of a for statement"); - return NULL; - } - - t = next_token(p); - if (t.type != TOKEN_IDENT) { - parser_report(p, "Missing iteraion variable name in for statement"); - return NULL; - } - String var1 = t.sval; - - t = next_token(p); - - String var2 = S(""); - if (t.type == TOKEN_COMMA) { - - t = next_token(p); - if (t.type != TOKEN_IDENT) { - parser_report(p, "Missing iteration variable name after ',' in for statement"); - return NULL; - } - var2 = t.sval; - - t = next_token(p); - } - - if (t.type != TOKEN_KWORD_IN) { - parser_report(p, "Missing 'in' keyword after iteration variable name in for statement"); - return NULL; - } - - Node *set = parse_expr(p, 0); - if (set == NULL) - return NULL; - - t = next_token(p); - if (t.type != TOKEN_COLON) { - parser_report(p, "Missing ':' after for statement set expression"); - return NULL; - } - - Node *body = parse_stmt(p, opflags); - if (body == NULL) - return NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_FOR; - parent->left = body; - parent->for_var1 = var1; - parent->for_var2 = var2; - parent->for_set = set; - - return parent; -} - -static Node *parse_while_stmt(Parser *p, int opflags) -{ - Token t = next_token(p); - if (t.type != TOKEN_KWORD_WHILE) { - parser_report(p, "Missing keyword 'while' at the start of a while statement"); - return NULL; - } - - Node *cond = parse_expr(p, 0); - if (cond == NULL) - return NULL; - - t = next_token(p); - if (t.type != TOKEN_COLON) { - parser_report(p, "Missing token ':' after while statement condition"); - return NULL; - } - - Node *stmt = parse_stmt(p, opflags); - if (stmt == NULL) - return NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_WHILE; - parent->left = stmt; - parent->while_cond = cond; - parent->while_body = stmt; - - return parent; -} - -static Node *parse_compound_stmt(Parser *p, bool global) -{ - if (!global) { - Token t = next_token(p); - if (t.type != TOKEN_CURLY_OPEN) { - parser_report(p, "Missing '{' at the start of a compound statement"); - return NULL; - } - } - - Node *head; - Node **tail = &head; - - for (;;) { - - Scanner saved = p->s; - Token t = next_token(p); - if (!global) { - if (t.type == TOKEN_CURLY_CLOSE) - break; - } else { - if (t.type == TOKEN_END) - break; - } - p->s = saved; - - Node *node = parse_stmt(p, 0); - if (node == NULL) - return NULL; - - *tail = node; - tail = &node->next; - } - - *tail = NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = global ? NODE_GLOBAL : NODE_COMPOUND; - parent->left = head; - - return parent; -} - -static Node *parse_proc_decl(Parser *p, int opflags) -{ - Token t = next_token(p); - if (t.type != TOKEN_KWORD_PROCEDURE) { - parser_report(p, "Missing keyword 'procedure' at the start of a procedure declaration"); - return NULL; - } - - t = next_token(p); - if (t.type != TOKEN_IDENT) { - parser_report(p, "Missing procedure name after 'procedure' keyword"); - return NULL; - } - String name = t.sval; - - t = next_token(p); - if (t.type != TOKEN_PAREN_OPEN) { - parser_report(p, "Missing '(' after procedure name in declaration"); - return NULL; - } - - Node *arg_head; - Node **arg_tail = &arg_head; - - Scanner saved = p->s; - t = next_token(p); - if (t.type != TOKEN_PAREN_CLOSE) { - p->s = saved; - - for (;;) { - - t = next_token(p); - if (t.type != TOKEN_IDENT) { - parser_report(p, "Missing argument name in procedure declaration"); - return NULL; - } - String argname = t.sval; - - Node *node = alloc_node(p); - if (node == NULL) - return NULL; - - node->type = NODE_PROCEDURE_ARG; - node->sval = argname; - - *arg_tail = node; - arg_tail = &node->next; - - Scanner saved = p->s; - t = next_token(p); - if (t.type == TOKEN_COMMA) { - saved = p->s; - t = next_token(p); - } - - if (t.type == TOKEN_PAREN_CLOSE) - break; - p->s = saved; - } - } - - *arg_tail = NULL; - - Node *body = parse_stmt(p, opflags); - if (body == NULL) - return NULL; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_PROCEDURE_DECL; - parent->proc_name = name; - parent->proc_args = arg_head; - parent->proc_body = body; - - return parent; -} - -static Node *parse_var_decl(Parser *p, int opflags) -{ - Token t = next_token(p); - if (t.type != TOKEN_KWORD_LET) { - parser_report(p, "Missing keyword 'let' at the start of a variable declaration"); - return NULL; - } - - t = next_token(p); - if (t.type != TOKEN_IDENT) { - parser_report(p, "Missing variable name after 'let' keyword"); - return NULL; - } - String name = t.sval; - - Scanner saved = p->s; - t = next_token(p); - - Node *value; - if (t.type == TOKEN_OPER_ASS) { - - value = parse_expr(p, opflags); - if (value == NULL) - return NULL; - - } else { - p->s = saved; - value = NULL; - } - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_VAR_DECL; - parent->var_name = name; - parent->var_value = value; - - return parent; -} - -static Node *parse_include_stmt(Parser *p) -{ - Token t = next_token(p); - if (t.type != TOKEN_KWORD_INCLUDE) { - parser_report(p, "Missing keyword 'include' at the start of an include statement"); - return NULL; - } - - t = next_token(p); - if (t.type != TOKEN_VALUE_STR) { - parser_report(p, "Missing file path string after 'include' keyword"); - return NULL; - } - String path = t.sval; - - Node *parent = alloc_node(p); - if (parent == NULL) - return NULL; - - parent->type = NODE_INCLUDE; - parent->include_path = path; - parent->include_root = NULL; - - *p->include_tail = parent; - p->include_tail = &parent->include_next; - - return parent; -} - -static Node *parse_stmt(Parser *p, int opflags) -{ - Scanner saved = p->s; - Token t = next_token(p); - p->s = saved; - - switch (t.type) { - - case TOKEN_KWORD_INCLUDE: - return parse_include_stmt(p); - - case TOKEN_KWORD_PROCEDURE: - return parse_proc_decl(p, opflags); - - case TOKEN_KWORD_LET: - return parse_var_decl(p, opflags); - - case TOKEN_KWORD_IF: - return parse_ifelse_stmt(p, opflags); - - case TOKEN_KWORD_WHILE: - return parse_while_stmt(p, opflags); - - case TOKEN_KWORD_FOR: - return parse_for_stmt(p, opflags); - - case TOKEN_CURLY_OPEN: - return parse_compound_stmt(p, false); - - default: - break; - } - - return parse_expr_stmt(p, opflags); -} - -static void write_node(Writer *w, Node *node) -{ - switch (node->type) { - - case NODE_VALUE_NONE : write_text(w, S("none")); break; - case NODE_VALUE_TRUE : write_text(w, S("true")); break; - case NODE_VALUE_FALSE: write_text(w, S("false")); break; - - case NODE_NESTED: - write_text(w, S("(nested ")); - write_node(w, node->left); - write_text(w, S(")")); - break; - - case NODE_COMPOUND: - { - write_text(w, S("(compound ")); - Node *cur = node->left; - while (cur) { - write_node(w, cur); - cur = cur->next; - if (cur) - write_text(w, S(" ")); - } - write_text(w, S(")")); - } - break; - - case NODE_GLOBAL: - { - write_text(w, S("(global ")); - Node *cur = node->left; - while (cur) { - write_node(w, cur); - cur = cur->next; - if (cur) - write_text(w, S(" ")); - } - write_text(w, S(")")); - } - break; - - case NODE_OPER_LEN: - write_text(w, S("(len ")); - write_node(w, node->left); - write_text(w, S(")")); - break; - - case NODE_OPER_ESCAPE: - write_text(w, S("(escape ")); - write_node(w, node->left); - write_text(w, S(")")); - break; - - case NODE_OPER_POS: - write_text(w, S("(+")); - write_node(w, node->left); - write_text(w, S(")")); - break; - - case NODE_OPER_NEG: - write_text(w, S("(")); - write_text(w, S("-")); - write_node(w, node->left); - write_text(w, S(")")); - break; - - case NODE_OPER_ASS: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("=")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_EQL: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("==")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_NQL: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("!=")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_LSS: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("<")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_GRT: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S(">")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_ADD: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("+")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_SUB: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("-")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_MUL: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("*")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_DIV: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("/")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_MOD: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("%%")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_OPER_SHOVEL: - write_text(w, S("(")); - write_node(w, node->left); - write_text(w, S("<<")); - write_node(w, node->right); - write_text(w, S(")")); - break; - - case NODE_VALUE_INT: - write_text_s64(w, node->ival); - break; - - case NODE_VALUE_FLOAT: - write_text_f64(w, node->fval); - break; - - case NODE_VALUE_STR: - write_text(w, S("\"")); - write_text(w, node->sval); - write_text(w, S("\"")); - break; - - case NODE_VALUE_VAR: - write_text(w, node->sval); - break; - - case NODE_VALUE_SYSVAR: - write_text(w, S("$")); - write_text(w, node->sval); - break; - - case NODE_IFELSE: - write_text(w, S("(if ")); - write_node(w, node->if_cond); - write_text(w, S(" ")); - write_node(w, node->if_branch1); - if (node->if_branch2) { - write_text(w, S(" else ")); - write_node(w, node->if_branch2); - } - write_text(w, S(")")); - break; - - case NODE_WHILE: - write_text(w, S("(while ")); - write_node(w, node->while_cond); - write_text(w, S(" ")); - write_node(w, node->while_body); - write_text(w, S(")")); - break; - - case NODE_VALUE_HTML: - { - write_text(w, S("(html ")); - write_text(w, node->html_tag); - - Node *child = node->html_child; - while (child) { - write_text(w, S(" ")); - write_node(w, child); - child = child->next; - } - - write_text(w, S(")")); - } - break; - - case NODE_FOR: - write_text(w, S("(for ")); - write_text(w, node->for_var1); - if (node->for_var2.len > 0) { - write_text(w, S(", ")); - write_text(w, node->for_var2); - } - write_text(w, S(" in ")); - write_node(w, node->for_set); - write_text(w, S(": ")); - write_node(w, node->left); - break; - - case NODE_SELECT: - write_node(w, node->left); - write_text(w, S("[")); - write_node(w, node->right); - write_text(w, S("]")); - break; - - case NODE_VALUE_ARRAY: - { - write_text(w, S("[")); - Node *child = node->child; - while (child) { - write_node(w, child); - write_text(w, S(", ")); - child = child->next; - } - write_text(w, S("]")); - } - break; - - case NODE_VALUE_MAP: - { - write_text(w, S("{")); - Node *child = node->child; - while (child) { - write_node(w, child->key); - write_text(w, S(": ")); - write_node(w, child); - write_text(w, S(", ")); - child = child->next; - } - write_text(w, S("}")); - } - break; - - case NODE_PROCEDURE_DECL: - { - write_text(w, S("(proc ")); - write_text(w, node->proc_name); - write_text(w, S("(")); - Node *arg = node->proc_args; - while (arg) { - write_node(w, arg); - arg = arg->next; - if (arg) - write_text(w, S(", ")); - } - write_text(w, S(")")); - write_node(w, node->proc_body); - } - break; - - case NODE_PROCEDURE_ARG: - write_text(w, node->sval); - break; - - case NODE_PROCEDURE_CALL: - { - write_node(w, node->left); - write_text(w, S("(")); - Node *arg = node->right; - while (arg) { - write_node(w, arg); - arg = arg->next; - if (arg) - write_text(w, S(", ")); - } - write_text(w, S(")")); - } - break; - - case NODE_VAR_DECL: - write_text(w, S("(let ")); - write_text(w, node->var_name); - if (node->var_value) { - write_text(w, S(" = ")); - write_node(w, node->var_value); - } - write_text(w, S(")")); - break; - - case NODE_INCLUDE: - write_text(w, S("include \"")); - write_text(w, node->include_path); - write_text(w, S("\"")); - break; - } -} - -static ParseResult parse(String src, WL_Arena *arena, char *errbuf, int errmax) -{ - Parser p = { - .s={ src.ptr, src.len, 0 }, - .arena=arena, - .errbuf=errbuf, - .errmax=errmax, - .errlen=0, - }; - - p.include_tail = &p.include_head; - - Node *node = parse_compound_stmt(&p, true); - if (node == NULL) - return (ParseResult) { .node=NULL, .includes=NULL, .errlen=p.errlen }; - - *p.include_tail = NULL; - return (ParseResult) { .node=node, .includes=p.include_head, .errlen=-1 }; -} - -///////////////////////////////////////////////////////////////////////// -// CODEGEN -///////////////////////////////////////////////////////////////////////// - -enum { - OPCODE_NOPE, - OPCODE_JUMP, - OPCODE_JIFP, - OPCODE_OUTPUT, - OPCODE_SYSVAR, - OPCODE_SYSCALL, - OPCODE_CALL, - OPCODE_RET, - OPCODE_GROUP, - OPCODE_ESCAPE, - OPCODE_PACK, - OPCODE_GPOP, - OPCODE_FOR, - OPCODE_EXIT, - OPCODE_VARS, - OPCODE_POP, - OPCODE_SETV, - OPCODE_PUSHV, - OPCODE_PUSHI, - OPCODE_PUSHF, - OPCODE_PUSHS, - OPCODE_PUSHA, - OPCODE_PUSHM, - OPCODE_PUSHN, - OPCODE_PUSHT, - OPCODE_PUSHFL, - OPCODE_LEN, - OPCODE_NEG, - OPCODE_EQL, - OPCODE_NQL, - OPCODE_LSS, - OPCODE_GRT, - OPCODE_ADD, - OPCODE_SUB, - OPCODE_MUL, - OPCODE_DIV, - OPCODE_MOD, - OPCODE_APPEND, - OPCODE_INSERT1, - OPCODE_INSERT2, - OPCODE_SELECT, -}; - -typedef struct UnpatchedCall UnpatchedCall; -struct UnpatchedCall { - UnpatchedCall *next; - String name; - int off; -}; - -typedef enum { - SYMBOL_VARIABLE, - SYMBOL_PROCEDURE, -} SymbolType; - -typedef struct { - SymbolType type; - String name; - bool cnst; - int off; -} Symbol; - -typedef enum { - SCOPE_IF, - SCOPE_ELSE, - SCOPE_FOR, - SCOPE_WHILE, - SCOPE_PROC, - SCOPE_COMPOUND, - SCOPE_GLOBAL, - SCOPE_ASSIGNMENT, -} ScopeType; - -typedef struct { - ScopeType type; - int idx_syms; - int max_vars; - UnpatchedCall *calls; -} Scope; - -#define MAX_SYMBOLS 1024 -#define MAX_SCOPES 128 -#define MAX_UNPATCHED_CALLS 32 - -typedef struct { - - Writer code; - Writer data; - - int num_scopes; - Scope scopes[MAX_SCOPES]; - - int num_syms; - Symbol syms[MAX_SYMBOLS]; - - UnpatchedCall *free_list_calls; - UnpatchedCall calls[MAX_UNPATCHED_CALLS]; - - bool err; - char *errmsg; - int errcap; - - int data_off; - -} Codegen; - -static void cg_report(Codegen *cg, char *fmt, ...) -{ - if (cg->err) return; - - va_list args; - va_start(args, fmt); - int len = vsnprintf(cg->errmsg, cg->errcap, fmt, args); - va_end(args); - - if (len > cg->errcap) - len = cg->errcap-1; - - cg->errmsg[len] = '\0'; - cg->err = true; -} - -static int cg_write_u8(Codegen *cg, uint8_t x) -{ - if (cg->err) return -1; - - int off = cg->code.len; - write_raw_u8(&cg->code, x); - return off; -} - -static int cg_write_u32(Codegen *cg, uint32_t x) -{ - if (cg->err) return -1; - - int off = cg->code.len; - write_raw_u32(&cg->code, x); - return off; -} - -static int cg_write_s64(Codegen *cg, int64_t x) -{ - if (cg->err) return -1; - - int off = cg->code.len; - write_raw_s64(&cg->code, x); - return off; -} - -static int cg_write_f64(Codegen *cg, double x) -{ - if (cg->err) return -1; - - int off = cg->code.len; - write_raw_f64(&cg->code, x); - return off; -} - -static void cg_write_str(Codegen *cg, String x) -{ - if (cg->err) return; - - int off = cg->data.len; - write_text(&cg->data, x); - write_raw_u32(&cg->code, off); - write_raw_u32(&cg->code, x.len); -} - -static void cg_patch_u8(Codegen *cg, int off, uint8_t x) -{ - if (cg->err) return; - - patch_mem(&cg->code, &x, off, SIZEOF(x)); -} - -static void cg_patch_u32(Codegen *cg, int off, uint32_t x) -{ - if (cg->err) return; - - patch_mem(&cg->code, &x, off, SIZEOF(x)); -} - -static uint32_t cg_current_offset(Codegen *cg) -{ - return cg->code.len; -} - -int count_nodes(Node *head) -{ - int n = 0; - Node *node = head; - while (node) { - n++; - node = node->next; - } - return n; -} - -static Scope *parent_scope(Codegen *cg) -{ - ASSERT(cg->num_scopes > 0); - - int parent = cg->num_scopes-1; - while (cg->scopes[parent].type != SCOPE_PROC && cg->scopes[parent].type != SCOPE_GLOBAL) - parent--; - - return &cg->scopes[parent]; -} - -static bool inside_assignment(Codegen *cg) -{ - ASSERT(cg->num_scopes > 0); - - int parent = cg->num_scopes-1; - while (cg->scopes[parent].type != SCOPE_PROC - && cg->scopes[parent].type != SCOPE_GLOBAL - && cg->scopes[parent].type != SCOPE_ASSIGNMENT) - parent--; - - return cg->scopes[parent].type == SCOPE_ASSIGNMENT; -} - -static int count_function_vars(Codegen *cg) -{ - int n = 0; - Scope *scope = parent_scope(cg); - for (int i = scope->idx_syms; i < cg->num_syms; i++) - if (cg->syms[i].type == SYMBOL_VARIABLE) - n++; - return n; -} - -static Symbol *cg_find_symbol(Codegen *cg, String name, bool local) -{ - if (cg->err) return NULL; - - if (name.len == 0) return NULL; - ASSERT(cg->num_scopes > 0); - Scope *scope = local ? &cg->scopes[cg->num_scopes-1] : parent_scope(cg); - for (int i = cg->num_syms-1; i >= scope->idx_syms; i--) - if (streq(cg->syms[i].name, name)) - return &cg->syms[i]; - return NULL; -} - -static int cg_declare_variable(Codegen *cg, String name, bool cnst) -{ - if (cg->err) return -1; - - Symbol *sym = cg_find_symbol(cg, name, true); - if (sym) { - cg_report(cg, "Variable declared twice"); - return -1; - } - - if (cg->num_syms == MAX_SYMBOLS) { - cg_report(cg, "Symbol count limit reached"); - return -1; - } - - int off = count_function_vars(cg); - - Scope *parent = parent_scope(cg); - parent->max_vars = MAX(parent->max_vars, off+1); - - cg->syms[cg->num_syms++] = (Symbol) { - .type = SYMBOL_VARIABLE, - .name = name, - .cnst = cnst, - .off = off, - }; - return off; -} - -static void cg_declare_procedure(Codegen *cg, String name, int off) -{ - if (cg->err) return; - - Symbol *sym = cg_find_symbol(cg, name, true); - if (sym) { - cg_report(cg, "Procedure declared twice"); - return; - } - - if (cg->num_syms == MAX_SYMBOLS) { - cg_report(cg, "Symbol count limit reached"); - return; - } - - cg->syms[cg->num_syms++] = (Symbol) { - .type = SYMBOL_PROCEDURE, - .name = name, - .cnst = true, - .off = off, - }; -} - -static void cg_push_scope(Codegen *cg, ScopeType type) -{ - if (cg->err) return; - - if (cg->num_scopes == MAX_SCOPES) { - cg_report(cg, "Scope limit reached"); - return; - } - - Scope *scope = &cg->scopes[cg->num_scopes++]; - scope->type = type; - scope->idx_syms = cg->num_syms; - scope->max_vars = 0; - scope->calls = NULL; -} - -static void cg_pop_scope(Codegen *cg) -{ - if (cg->err) return; - - ASSERT(cg->num_scopes > 0); - Scope *scope = &cg->scopes[cg->num_scopes-1]; - - Scope *parent_scope = NULL; - if (cg->num_scopes > 1) - parent_scope = &cg->scopes[cg->num_scopes-2]; - - while (scope->calls) { - - UnpatchedCall *call = scope->calls; - scope->calls = call->next; - - ASSERT(call - cg->calls >= 0 && call - cg->calls < MAX_UNPATCHED_CALLS); - - Symbol *sym = cg_find_symbol(cg, call->name, true); - - if (sym == NULL) { - if (parent_scope == NULL) { - cg_report(cg, "Undefined function '%.*s'", - scope->calls->name.len, - scope->calls->name.ptr); - return; - } - call->next = parent_scope->calls; - parent_scope->calls = call; - continue; - } - - if (sym->type != SYMBOL_PROCEDURE) { - cg_report(cg, "Symbol '%.*s' is not a procedure", call->name.len, call->name.ptr); - return; - } - - cg_patch_u32(cg, call->off, sym->off); - - call->next = cg->free_list_calls; - cg->free_list_calls = call; - - // TODO: remove - ASSERT(cg->scopes[cg->num_scopes-1].calls == NULL || (cg->scopes[cg->num_scopes-1].calls - cg->calls >= 0 && cg->scopes[cg->num_scopes-1].calls - cg->calls < MAX_UNPATCHED_CALLS)); - } - - cg->num_syms = scope->idx_syms; - cg->num_scopes--; -} - -static void cg_append_unpatched_call(Codegen *cg, String name, int p) -{ - if (cg->err) return; - - if (cg->free_list_calls == NULL) { - cg_report(cg, "Out of memory"); - return; - } - UnpatchedCall *call = cg->free_list_calls; - cg->free_list_calls = call->next; - - ASSERT(call - cg->calls >= 0 && call - cg->calls < MAX_UNPATCHED_CALLS); - - call->name = name; - call->off = p; - call->next = NULL; - - ASSERT(cg->num_scopes > 0); - Scope *scope = &cg->scopes[cg->num_scopes-1]; - - call->next = scope->calls; - scope->calls = call; -} - -static bool cg_global_scope(Codegen *cg) -{ - Scope *scope = parent_scope(cg); - return scope->type == SCOPE_GLOBAL; -} - -static void cg_flush_pushs(Codegen *cg) -{ - if (cg->data_off != -1) { - if (cg->data_off < cg->data.len) { - cg_write_u8(cg, OPCODE_PUSHS); - cg_write_u32(cg, cg->data_off); - cg_write_u32(cg, cg->data.len - cg->data_off); - } - cg->data_off = -1; - } -} - -static int cg_write_opcode(Codegen *cg, uint8_t opcode) -{ - ASSERT(opcode != OPCODE_PUSHS); - cg_flush_pushs(cg); - return cg_write_u8(cg, opcode); -} - -static void cg_write_pushs(Codegen *cg, String str, bool dont_group) -{ - if (dont_group) { - cg_flush_pushs(cg); - cg_write_u8(cg, OPCODE_PUSHS); - cg_write_str(cg, str); - } else { - if (cg->data_off == -1) - cg->data_off = cg->data.len; - write_raw_mem(&cg->data, str.ptr, str.len); - } -} - -static void walk_node(Codegen *cg, Node *node, bool inside_html); - -static void walk_expr_node(Codegen *cg, Node *node, bool one) -{ - // TODO: remove - ASSERT(cg->scopes[cg->num_scopes-1].calls == NULL || (cg->scopes[cg->num_scopes-1].calls - cg->calls >= 0 && cg->scopes[cg->num_scopes-1].calls - cg->calls < MAX_UNPATCHED_CALLS)); - - switch (node->type) { - - case NODE_NESTED: - walk_expr_node(cg, node->left, one); - break; - - case NODE_OPER_LEN: - walk_expr_node(cg, node->left, true); - cg_write_opcode(cg, OPCODE_LEN); - break; - - case NODE_OPER_ESCAPE: - cg_write_opcode(cg, OPCODE_GROUP); - walk_expr_node(cg, node->left, false); - cg_write_opcode(cg, OPCODE_ESCAPE); - break; - - case NODE_OPER_POS: - walk_expr_node(cg, node->left, one); - break; - - case NODE_OPER_NEG: - walk_expr_node(cg, node->left, true); - cg_write_opcode(cg, OPCODE_NEG); - break; - - case NODE_OPER_ASS: - { - Node *dst = node->left; - Node *src = node->right; - - if (dst->type == NODE_VALUE_VAR) { - - String name = dst->sval; - Symbol *sym = cg_find_symbol(cg, name, false); - if (sym == NULL) { - cg_report(cg, "Write to undeclared variable"); - return; - } - if (sym->type == SYMBOL_PROCEDURE) { - cg_report(cg, "Symbol is not a variable"); - return; - } - if (sym->cnst) { - cg_report(cg, "Variable is constant"); - return; - } - - cg_push_scope(cg, SCOPE_ASSIGNMENT); - walk_expr_node(cg, src, true); - cg_pop_scope(cg); - - cg_write_opcode(cg, OPCODE_SETV); - cg_write_u8(cg, sym->off); - - if (!one) - cg_write_opcode(cg, OPCODE_POP); - - } else if (dst->type == NODE_SELECT) { - - cg_push_scope(cg, SCOPE_ASSIGNMENT); - walk_expr_node(cg, src, true); - cg_pop_scope(cg); - - walk_expr_node(cg, dst->left, true); - walk_expr_node(cg, dst->right, true); - cg_write_opcode(cg, OPCODE_INSERT2); - - if (!one) - cg_write_opcode(cg, OPCODE_POP); - - } else { - - cg_report(cg, "Assignment left side can't be assigned to"); - return; - } - } - break; - - case NODE_OPER_SHOVEL: - { - walk_expr_node(cg, node->left, true); - - cg_push_scope(cg, SCOPE_ASSIGNMENT); - walk_expr_node(cg, node->right, true); - cg_pop_scope(cg); - - cg_write_opcode(cg, OPCODE_APPEND); - if (!one) - cg_write_opcode(cg, OPCODE_POP); - } - break; - - case NODE_OPER_EQL: - walk_expr_node(cg, node->left, true); - walk_expr_node(cg, node->right, true); - cg_write_opcode(cg, OPCODE_EQL); - break; - - case NODE_OPER_NQL: - walk_expr_node(cg, node->left, true); - walk_expr_node(cg, node->right, true); - cg_write_opcode(cg, OPCODE_NQL); - break; - - case NODE_OPER_LSS: - walk_expr_node(cg, node->left, true); - walk_expr_node(cg, node->right, true); - cg_write_opcode(cg, OPCODE_LSS); - break; - - case NODE_OPER_GRT: - walk_expr_node(cg, node->left, true); - walk_expr_node(cg, node->right, true); - cg_write_opcode(cg, OPCODE_GRT); - break; - - case NODE_OPER_ADD: - walk_expr_node(cg, node->left, true); - walk_expr_node(cg, node->right, true); - cg_write_opcode(cg, OPCODE_ADD); - break; - - case NODE_OPER_SUB: - walk_expr_node(cg, node->left, true); - walk_expr_node(cg, node->right, true); - cg_write_opcode(cg, OPCODE_SUB); - break; - - case NODE_OPER_MUL: - walk_expr_node(cg, node->left, true); - walk_expr_node(cg, node->right, true); - cg_write_opcode(cg, OPCODE_MUL); - break; - - case NODE_OPER_DIV: - walk_expr_node(cg, node->left, true); - walk_expr_node(cg, node->right, true); - cg_write_opcode(cg, OPCODE_DIV); - break; - - case NODE_OPER_MOD: - walk_expr_node(cg, node->left, true); - walk_expr_node(cg, node->right, true); - cg_write_opcode(cg, OPCODE_MOD); - break; - - case NODE_VALUE_INT: - cg_write_opcode(cg, OPCODE_PUSHI); - cg_write_s64(cg, node->ival); - break; - - case NODE_VALUE_FLOAT: - cg_write_opcode(cg, OPCODE_PUSHF); - cg_write_f64(cg, node->fval); - break; - - case NODE_VALUE_STR: - cg_write_pushs(cg, node->sval, one); - break; - - case NODE_VALUE_NONE: - cg_write_opcode(cg, OPCODE_PUSHN); - break; - - case NODE_VALUE_TRUE: - cg_write_opcode(cg, OPCODE_PUSHT); - break; - - case NODE_VALUE_FALSE: - cg_write_opcode(cg, OPCODE_PUSHFL); - break; - - case NODE_VALUE_VAR: - { - String name = node->sval; - Symbol *sym = cg_find_symbol(cg, name, false); - if (sym == NULL) { - cg_report(cg, "Access to undeclared variable '%.*s'", name.len, name.ptr); - return; - } - if (sym->type == SYMBOL_PROCEDURE) { - cg_report(cg, "Symbol is not a variable"); - return; - } - - cg_write_opcode(cg, OPCODE_PUSHV); - cg_write_u8(cg, sym->off); - } - break; - - case NODE_VALUE_SYSVAR: - cg_write_opcode(cg, OPCODE_SYSVAR); - cg_write_str(cg, node->sval); - break; - - case NODE_VALUE_HTML: - { - if (one) - cg_write_opcode(cg, OPCODE_GROUP); - - cg_write_pushs(cg, S("<"), false); - cg_write_pushs(cg, node->html_tag, false); - - Node *child = node->html_attr; - while (child) { - walk_node(cg, child, true); - child = child->next; - } - - if (!node->html_body) { - cg_write_pushs(cg, S("/>"), false); - } else { - cg_write_pushs(cg, S(">"), false); - Node *child = node->html_child; - while (child) { - walk_node(cg, child, true); - child = child->next; - } - cg_write_pushs(cg, S("html_tag, false); - cg_write_pushs(cg, S(">"), false); - } - - if (one) - cg_write_opcode(cg, OPCODE_PACK); - } - break; - - case NODE_VALUE_ARRAY: - { - cg_write_opcode(cg, OPCODE_PUSHA); - cg_write_u32(cg, count_nodes(node->child)); - - Node *child = node->child; - while (child) { - walk_expr_node(cg, child, true); - cg_write_opcode(cg, OPCODE_APPEND); - child = child->next; - } - } - break; - - case NODE_VALUE_MAP: - { - cg_write_opcode(cg, OPCODE_PUSHM); - cg_write_u32(cg, count_nodes(node->child)); - - Node *child = node->child; - while (child) { - walk_expr_node(cg, child, true); - walk_expr_node(cg, child->key, true); - cg_write_opcode(cg, OPCODE_INSERT1); - child = child->next; - } - } - break; - - case NODE_SELECT: - { - Node *set = node->left; - Node *key = node->right; - walk_expr_node(cg, set, true); - walk_expr_node(cg, key, true); - cg_write_opcode(cg, OPCODE_SELECT); - } - break; - - case NODE_PROCEDURE_CALL: - { - if (one) - cg_write_opcode(cg, OPCODE_GROUP); - - int count = 0; - Node *arg = node->right; - while (arg) { - walk_expr_node(cg, arg, true); - count++; - arg = arg->next; - } - - Node *proc = node->left; - if (proc->type == NODE_VALUE_VAR) { - - cg_write_opcode(cg, OPCODE_CALL); - cg_write_u8(cg, count); - int p = cg_write_u32(cg, 0); - cg_append_unpatched_call(cg, proc->sval, p); - - } else { - - ASSERT(proc->type == NODE_VALUE_SYSVAR); - cg_write_opcode(cg, OPCODE_SYSCALL); - cg_write_u8(cg, count); - cg_write_str(cg, proc->sval); - } - - if (one) - cg_write_opcode(cg, OPCODE_PACK); - } - break; - - default: - UNREACHABLE; - } -} - -static void walk_node(Codegen *cg, Node *node, bool inside_html) -{ - // TODO: remove - ASSERT(cg->scopes[cg->num_scopes-1].calls == NULL || (cg->scopes[cg->num_scopes-1].calls - cg->calls >= 0 && cg->scopes[cg->num_scopes-1].calls - cg->calls < MAX_UNPATCHED_CALLS)); - - switch (node->type) { - - case NODE_GLOBAL: - for (Node *child = node->left; - child; child = child->next) { - walk_node(cg, child, false); - } - break; - - case NODE_COMPOUND: - cg_push_scope(cg, SCOPE_COMPOUND); - for (Node *child = node->left; - child; child = child->next) - walk_node(cg, child, inside_html); - cg_pop_scope(cg); - break; - - case NODE_PROCEDURE_DECL: - { - cg_push_scope(cg, SCOPE_PROC); - - cg_write_opcode(cg, OPCODE_JUMP); - int off0 = cg_write_u32(cg, 0); - - #define MAX_ARGS 128 - - int num_args = 0; - Node *args[MAX_ARGS]; - - Node *arg = node->proc_args; - while (arg) { - if (num_args == MAX_ARGS) { - cg_report(cg, "Procedure argument limit reached"); - return; - } - args[num_args++] = arg; - arg = arg->next; - } - - for (int i = num_args-1; i >= 0; i--) - cg_declare_variable(cg, args[i]->sval, false); - - int off1 = cg_write_opcode(cg, OPCODE_VARS); - int off2 = cg_write_u8(cg, 0); - - walk_node(cg, node->proc_body, false); - cg_write_opcode(cg, OPCODE_RET); - - cg_patch_u8 (cg, off2, count_function_vars(cg)); - cg_patch_u32(cg, off0, cg_current_offset(cg)); - - cg_pop_scope(cg); - - cg_declare_procedure(cg, node->proc_name, off1); - } - break; - - case NODE_VAR_DECL: - { - int off = cg_declare_variable(cg, node->var_name, false); - if (node->var_value) { - cg_push_scope(cg, SCOPE_ASSIGNMENT); - walk_expr_node(cg, node->var_value, true); - cg_pop_scope(cg); - } else - cg_write_opcode(cg, OPCODE_PUSHN); - cg_write_opcode(cg, OPCODE_SETV); - cg_write_u8(cg, off); - cg_write_opcode(cg, OPCODE_POP); - } - break; - - case NODE_IFELSE: - { - // If there is no else branch: - // - // - // JIFP end - // - // end: - // ... - // - // If there is: - // - // - // JIFP else - // - // JUMP end - // else: - // - // end: - // ... - - if (node->if_branch2) { - - walk_expr_node(cg, node->if_cond, true); - - cg_write_opcode(cg, OPCODE_JIFP); - int p1 = cg_write_u32(cg, 0); - - cg_push_scope(cg, SCOPE_IF); - walk_node(cg, node->if_branch1, inside_html); - cg_pop_scope(cg); - - cg_write_opcode(cg, OPCODE_JUMP); - int p2 = cg_write_u32(cg, 0); - - cg_flush_pushs(cg); - cg_patch_u32(cg, p1, cg_current_offset(cg)); - - cg_push_scope(cg, SCOPE_ELSE); - walk_node(cg, node->if_branch2, inside_html); - cg_pop_scope(cg); - - cg_flush_pushs(cg); - cg_patch_u32(cg, p2, cg_current_offset(cg)); - - } else { - - walk_expr_node(cg, node->if_cond, true); - - cg_write_opcode(cg, OPCODE_JIFP); - int p1 = cg_write_u32(cg, 0); - - cg_push_scope(cg, SCOPE_IF); - walk_node(cg, node->if_branch1, inside_html); - cg_pop_scope(cg); - - cg_flush_pushs(cg); - cg_patch_u32(cg, p1, cg_current_offset(cg)); - } - } - break; - - case NODE_FOR: - { - cg_push_scope(cg, SCOPE_FOR); - - int var_1 = cg_declare_variable(cg, node->for_var1, false); - int var_2 = cg_declare_variable(cg, node->for_var2, true); - int var_3 = cg_declare_variable(cg, (String) { NULL, 0 }, true); - - walk_expr_node(cg, node->for_set, true); - cg_write_opcode(cg, OPCODE_SETV); - cg_write_u8(cg, var_3); - cg_write_opcode(cg, OPCODE_POP); - - cg_write_opcode(cg, OPCODE_PUSHI); - cg_write_s64(cg, -1); - cg_write_opcode(cg, OPCODE_SETV); - cg_write_u8(cg, var_2); - cg_write_opcode(cg, OPCODE_POP); - - int start = cg_write_opcode(cg, OPCODE_FOR); - cg_write_u8(cg, var_3); - cg_write_u8(cg, var_1); - cg_write_u8(cg, var_2); - int p = cg_write_u32(cg, 0); - - walk_node(cg, node->left, inside_html); - - cg_write_opcode(cg, OPCODE_JUMP); - cg_write_u32(cg, start); - - cg_patch_u32(cg, p, cg_current_offset(cg)); - - cg_pop_scope(cg); - } - break; - - case NODE_WHILE: - { - // start: - // - // JIFP end - // - // JUMP start - // end: - // ... - - int start = cg_current_offset(cg); - - walk_expr_node(cg, node->while_cond, true); - - cg_write_opcode(cg, OPCODE_JIFP); - int p = cg_write_u32(cg, 0); - - cg_push_scope(cg, SCOPE_WHILE); - walk_node(cg, node->left, inside_html); - cg_pop_scope(cg); - - cg_write_opcode(cg, OPCODE_JUMP); - cg_write_u32(cg, start); - - cg_patch_u32(cg, p, cg_current_offset(cg)); - } - break; - - case NODE_INCLUDE: - walk_node(cg, node->include_root, false); - break; - - default: - walk_expr_node(cg, node, false); - if (cg_global_scope(cg) && !inside_assignment(cg) && !inside_html) - cg_write_opcode(cg, OPCODE_OUTPUT); - break; - } -} - -#define WL_MAGIC 0xFEEDBEEF - -static int codegen(Node *node, char *dst, int cap, char *errmsg, int errcap) -{ - char *hdr; - if (cap < SIZEOF(uint32_t) * 3) - hdr = NULL; - else { - hdr = dst; - dst += SIZEOF(uint32_t) * 3; - cap -= SIZEOF(uint32_t) * 3; - } - - Codegen cg = { - .code = { dst, cap/2, 0 }, - .data = { dst + cap/2, cap/2, 0 }, - .num_scopes = 0, - .err = false, - .errmsg = errmsg, - .errcap = errcap, - .data_off = -1, - }; - - cg.free_list_calls = cg.calls; - for (int i = 0; i < MAX_UNPATCHED_CALLS-1; i++) - cg.calls[i].next = &cg.calls[i+1]; - cg.calls[MAX_UNPATCHED_CALLS-1].next = NULL; - - cg_push_scope(&cg, SCOPE_GLOBAL); - cg_write_opcode(&cg, OPCODE_VARS); - int off = cg_write_u8(&cg, 0); - walk_node(&cg, node, false); - cg_write_opcode(&cg, OPCODE_EXIT); - cg_patch_u8(&cg, off, cg.scopes[0].max_vars); - cg_pop_scope(&cg); - - if (cg.err) - return -1; - - if (hdr) { - - uint32_t magic = WL_MAGIC; - uint32_t code_len = cg.code.len; - uint32_t data_len = cg.data.len; - memcpy(hdr + 0, &magic , sizeof(uint32_t)); - memcpy(hdr + 4, &code_len, sizeof(uint32_t)); - memcpy(hdr + 8, &data_len, sizeof(uint32_t)); - - if (cg.code.len + cg.data.len <= cap) - memmove(dst + cg.code.len, dst + cap/2, cg.data.len); - } - - return cg.code.len + cg.data.len + SIZEOF(uint32_t) * 3; -} - -static int write_instr(Writer *w, char *src, int len, String data) -{ - if (len == 0) - return -1; - - switch (src[0]) { - - uint8_t b0; - uint8_t b1; - uint8_t b2; - uint32_t w0; - uint32_t w1; - int64_t i; - double d; - - case OPCODE_NOPE: - write_text(w, S("NOPE\n")); - return 1; - - case OPCODE_JUMP: - if (len < 5) return -1; - memcpy(&w0, src + 1, sizeof(uint32_t)); - write_text(w, S("JUMP ")); - write_text_s64(w, w0); - write_text(w, S("\n")); - return 5; - - case OPCODE_JIFP: - if (len < 5) return -1; - memcpy(&w0, src + 1, sizeof(uint32_t)); - write_text(w, S("JIFP ")); - write_text_s64(w, w0); - write_text(w, S("\n")); - return 5; - - case OPCODE_OUTPUT: - write_text(w, S("OUTPUT\n")); - return 1; - - case OPCODE_SYSVAR: - if (len < 9) return -1; - memcpy(&w0, src + 1, sizeof(uint32_t)); - memcpy(&w1, src + 5, sizeof(uint32_t)); - write_text(w, S("SYSVAR \"")); - write_text(w, (String) { data.ptr + w0, w1 }); - write_text(w, S("\"\n")); - return 9; - - case OPCODE_SYSCALL: - if (len < 10) return -1; - memcpy(&b0, src + 1, sizeof(uint8_t)); - memcpy(&w0, src + 2, sizeof(uint32_t)); - memcpy(&w1, src + 6, sizeof(uint32_t)); - write_text(w, S("SYSCALL ")); - write_text_s64(w, b0); - write_text(w, S(" \"")); - write_text(w, (String) { data.ptr + w0, w1 }); - write_text(w, S("\"\n")); - return 10; - - case OPCODE_CALL: - if (len < 6) return -1; - memcpy(&b0, src + 1, sizeof(uint8_t)); - memcpy(&w0, src + 2, sizeof(uint32_t)); - write_text(w, S("CALL ")); - write_text_s64(w, b0); - write_text(w, S(" ")); - write_text_s64(w, w0); - write_text(w, S("\n")); - return 6; - - case OPCODE_RET: - write_text(w, S("RET\n")); - return 1; - - case OPCODE_GROUP: - write_text(w, S("GROUP\n")); - return 1; - - case OPCODE_ESCAPE: - write_text(w, S("ESCAPE\n")); - return 1; - - case OPCODE_PACK: - write_text(w, S("PACK\n")); - return 1; - - case OPCODE_GPOP: - write_text(w, S("GPOP\n")); - return 1; - - case OPCODE_FOR: - if (len < 8) return -1; - memcpy(&b0, src + 1, sizeof(b0)); - memcpy(&b1, src + 2, sizeof(b1)); - memcpy(&b2, src + 3, sizeof(b2)); - memcpy(&w0, src + 4, sizeof(w0)); - write_text(w, S("FOR ")); - write_text_s64(w, b0); - write_text(w, S(" ")); - write_text_s64(w, b1); - write_text(w, S(" ")); - write_text_s64(w, b2); - write_text(w, S(" ")); - write_text_s64(w, w0); - write_text(w, S("\n")); - return 8; - - case OPCODE_EXIT: - write_text(w, S("EXIT\n")); - return 1; - - case OPCODE_VARS: - if (len < 2) return -1; - memcpy(&b0, src + 1, sizeof(b0)); - write_text(w, S("VARS ")); - write_text_s64(w, b0); - write_text(w, S("\n")); - return 2; - - case OPCODE_POP: - write_text(w, S("POP\n")); - return 1; - - case OPCODE_SETV: - if (len < 2) return -1; - memcpy(&b0, src + 1, sizeof(uint8_t)); - write_text(w, S("SETV ")); - write_text_s64(w, b0); - write_text(w, S("\n")); - return 2; - - case OPCODE_PUSHV: - if (len < 2) return -1; - memcpy(&b0, src + 1, sizeof(uint8_t)); - write_text(w, S("PUSHV ")); - write_text_s64(w, b0); - write_text(w, S("\n")); - return 2; - - case OPCODE_PUSHI: - if (len < 9) return -1; - memcpy(&i, src + 1, sizeof(int64_t)); - write_text(w, S("PUSHI ")); - write_text_s64(w, i); - write_text(w, S("\n")); - return 9; - - case OPCODE_PUSHF: - if (len < 9) return -1; - memcpy(&d, src + 1, sizeof(double)); - write_text(w, S("PUSHF ")); - write_text_f64(w, d); - write_text(w, S("\n")); - return 9; - - case OPCODE_PUSHS: - if (len < 9) return -1; - memcpy(&w0, src + 1, sizeof(uint32_t)); - memcpy(&w1, src + 5, sizeof(uint32_t)); - write_text(w, S("PUSHS \"")); - write_text(w, (String) { data.ptr + w0, w1 }); - write_text(w, S("\"\n")); - return 9; - - case OPCODE_PUSHA: - if (len < 5) return -1; - memcpy(&w0, src + 1, sizeof(w0)); - write_text(w, S("PUSHA ")); - write_text_s64(w, w0); - write_text(w, S("\n")); - return 5; - - case OPCODE_PUSHM: - if (len < 5) return -1; - memcpy(&w0, src + 1, sizeof(w0)); - write_text(w, S("PUSHM ")); - write_text_s64(w, w0); - write_text(w, S("\n")); - return 5; - - case OPCODE_PUSHN: - write_text(w, S("PUSHN\n")); - return 1; - - case OPCODE_PUSHT: - write_text(w, S("PUSHT\n")); - return 1; - - case OPCODE_PUSHFL: - write_text(w, S("PUSHFL\n")); - return 1; - - case OPCODE_LEN: - write_text(w, S("LEN\n")); - return 1; - - case OPCODE_NEG: - write_text(w, S("NEG\n")); - return 1; - - case OPCODE_EQL: - write_text(w, S("EQL\n")); - return 1; - - case OPCODE_NQL: - write_text(w, S("NQL\n")); - return 1; - - case OPCODE_LSS: - write_text(w, S("LSS\n")); - return 1; - - case OPCODE_GRT: - write_text(w, S("GRT\n")); - return 1; - - case OPCODE_ADD: - write_text(w, S("ADD\n")); - return 1; - - case OPCODE_SUB: - write_text(w, S("SUB\n")); - return 1; - - case OPCODE_MUL: - write_text(w, S("MUL\n")); - return 1; - - case OPCODE_DIV: - write_text(w, S("DIV\n")); - return 1; - - case OPCODE_MOD: - write_text(w, S("MOD\n")); - return 1; - - case OPCODE_APPEND: - write_text(w, S("APPEND\n")); - return 1; - - case OPCODE_INSERT1: - write_text(w, S("INSERT1\n")); - return 1; - - case OPCODE_INSERT2: - write_text(w, S("INSERT2\n")); - return 1; - - case OPCODE_SELECT: - write_text(w, S("SELECT\n")); - return 1; - - default: - write_text(w, S("byte ")); - write_text_s64(w, src[0]); - return 1; - } - - return -1; -} - -static int write_program(WL_Program program, char *dst, int cap) -{ - if (program.len < 3 * sizeof(uint32_t)) - return -1; - - uint32_t magic; - uint32_t code_len; - uint32_t data_len; - - memcpy(&magic , program.ptr + 0, sizeof(uint32_t)); - memcpy(&code_len, program.ptr + 4, sizeof(uint32_t)); - memcpy(&data_len, program.ptr + 8, sizeof(uint32_t)); - - if (magic != WL_MAGIC) - return -1; - - if (code_len + data_len + 3 * sizeof(uint32_t) != program.len) - return -1; - - String code = { program.ptr + 3 * sizeof(uint32_t) , code_len }; - String data = { program.ptr + 3 * sizeof(uint32_t) + code_len, data_len }; - - Writer w = { dst, cap, 0 }; - - int cur = 0; - while (cur < code.len) { - write_text_s64(&w, cur); - write_text(&w, S(": ")); - int ret = write_instr(&w, code.ptr + cur, code.len - cur, data); - if (ret < 0) return -1; - cur += ret; - } - - return w.len; -} - -void wl_dump_program(WL_Program program) -{ - char buf[1<<10]; - int len = write_program(program, buf, SIZEOF(buf)); - - if (len < 0) { - printf("Invalid program\n"); - return; - } - - if (len > SIZEOF(buf)) { - char *p = malloc(len+1); - if (p == NULL) { - printf("Out of memory\n"); - return; - } - write_program(program, p, len); - p[len] = '\0'; - fwrite(p, 1, len, stdout); - } else { - fwrite(buf, 1, len, stdout); - } -} - -///////////////////////////////////////////////////////////////////////// -// COMPILER -///////////////////////////////////////////////////////////////////////// - -#define FILE_LIMIT 128 - -typedef struct { - String file; - Node* root; - Node* includes; -} CompiledFile; - -struct WL_Compiler { - - WL_Arena* arena; - CompiledFile files[FILE_LIMIT]; - int num_files; - String waiting_file; - - bool err; - char msg[1<<8]; -}; - -WL_Compiler *wl_compiler_init(WL_Arena *arena) -{ - WL_Compiler *compiler = alloc(arena, SIZEOF(WL_Compiler), _Alignof(WL_Compiler)); - if (compiler == NULL) - return NULL; - compiler->arena = arena; - compiler->num_files = 0; - compiler->waiting_file = (String) { NULL, 0 }; - compiler->err = false; - return compiler; -} - -WL_AddResult wl_compiler_add(WL_Compiler *compiler, WL_String content) -{ - if (compiler->err) - return (WL_AddResult) { .type=WL_ADD_ERROR }; - - ParseResult pres = parse((String) { content.ptr, content.len }, compiler->arena, compiler->msg, SIZEOF(compiler->msg)); - if (pres.node == NULL) { - compiler->err = true; - return (WL_AddResult) { .type=WL_ADD_ERROR }; - } - - CompiledFile compiled_file = { - .file = compiler->waiting_file, - .root = pres.node, - .includes = pres.includes, - }; - compiler->files[compiler->num_files++] = compiled_file; - compiler->waiting_file = (String) { NULL, 0 }; - - for (int i = 0; i < compiler->num_files; i++) { - - Node *include = compiler->files[i].includes; - while (include) { - - ASSERT(include->type == NODE_INCLUDE); - - if (include->include_root == NULL) { - for (int j = 0; j < compiler->num_files; j++) { - if (streq(include->include_path, compiler->files[j].file)) { - include->include_root = compiler->files[j].root; - break; - } - } - } - - if (include->include_root == NULL) { - - if (compiler->num_files == FILE_LIMIT) { - ASSERT(0); // TODO - } - - // TODO: Make the path relative to the compiled file - - compiler->waiting_file = include->include_path; - return (WL_AddResult) { .type=WL_ADD_AGAIN, .path={ include->include_path.ptr, include->include_path.len } }; - } - - include = include->include_next; - } - } - - return (WL_AddResult) { .type=WL_ADD_LINK }; -} - -int wl_compiler_link(WL_Compiler *compiler, WL_Program *program) -{ - if (compiler->err) return -1; - - if (compiler->num_files == 0 || compiler->waiting_file.len > 0) { - int len = snprintf(compiler->msg, SIZEOF(compiler->msg), "Missing files in compilation unit"); - if (len > SIZEOF(compiler->msg)) - len = SIZEOF(compiler->msg)-1; - compiler->msg[len] = '\0'; - compiler->err = true; - return -1; - } - - char *dst = compiler->arena->ptr + compiler->arena->cur; - int cap = compiler->arena->len - compiler->arena->cur; - - int len = codegen(compiler->files[0].root, dst, cap, compiler->msg, SIZEOF(compiler->msg)); - if (len < 0) { - compiler->err = true; - return -1; - } - if (len > cap) { - int len = snprintf(compiler->msg, SIZEOF(compiler->msg), "Out of memory"); - if (len > SIZEOF(compiler->msg)) - len = SIZEOF(compiler->msg)-1; - compiler->msg[len] = '\0'; - compiler->err = true; - return -1; - } - - *program = (WL_Program) { dst, len }; - - compiler->arena->cur += len; - return 0; -} - -WL_String wl_compiler_error(WL_Compiler *compiler) -{ - return compiler->err - ? (WL_String) { compiler->msg, strlen(compiler->msg) } - : (WL_String) { NULL, 0 }; -} - -int wl_dump_ast(WL_Compiler *compiler, char *dst, int cap) -{ - Writer w = { dst, cap, 0 }; - for (int i = 0; i < compiler->num_files; i++) { - write_text(&w, S("(file \"")); - write_text(&w, compiler->files[i].file); - write_text(&w, S("\" ")); - write_node(&w, compiler->files[i].root); - write_text(&w, S(")")); - } - return w.len; -} - -///////////////////////////////////////////////////////////////////////// -// OBJECT MODEL -///////////////////////////////////////////////////////////////////////// - -typedef enum { - TYPE_NONE, - TYPE_BOOL, - TYPE_INT, - TYPE_FLOAT, - TYPE_STRING, - TYPE_ARRAY, - TYPE_MAP, - TYPE_ERROR, -} Type; - -#define TAG_ERROR 0 -#define TAG_POSITIVE_INT 1 -#define TAG_NEGATIVE_INT 2 -#define TAG_BOOL 3 -#define TAG_NONE 4 -#define TAG_PTR 5 - -#define VALUE_NONE ((0 << 3) | TAG_NONE) -#define VALUE_TRUE ((0 << 3) | TAG_BOOL) -#define VALUE_FALSE ((1 << 3) | TAG_BOOL) -#define VALUE_ERROR ((0 << 3) | TAG_ERROR) - -typedef uint64_t Value; - -typedef struct Extension Extension; -struct Extension { - Extension *next; - int count; - int capacity; - Value vals[]; -}; - -typedef struct { - Type type; - int count; - int capacity; - Extension *ext; - Value vals[]; -} AggregateValue; - -typedef struct { - Type type; - double raw; -} FloatValue; - -typedef struct { - Type type; - int64_t raw; -} IntValue; - -typedef struct { - Type type; - int len; - char data[]; -} StringValue; - -static int value_convert_to_str(Value v, char *dst, int cap); - -static Type value_type(Value v) -{ - switch (v & 7) { - case TAG_ERROR: return TYPE_ERROR; - case TAG_POSITIVE_INT: return TYPE_INT; - case TAG_NEGATIVE_INT: return TYPE_INT; - case TAG_BOOL : return TYPE_BOOL; - case TAG_NONE : return TYPE_NONE; - case TAG_PTR : return *(Type*) (v & ~(Value) 7); break; - } - return TAG_ERROR; -} - -static int64_t value_to_s64(Value v) -{ - ASSERT(value_type(v) == TYPE_INT); - - if ((v & 7) == TAG_POSITIVE_INT) - return (int64_t) (v >> 3); - - if ((v & 7) == TAG_NEGATIVE_INT) - return (int64_t) ((v >> 3) | ((Value) 7 << 61)); - - IntValue *p = (IntValue*) (v & ~(Value) 7); - return p->raw; -} - -static double value_to_f64(Value v) -{ - ASSERT(value_type(v) == TYPE_FLOAT); - - FloatValue *p = (FloatValue*) (v & ~(Value) 7); - return p->raw; -} - -static String value_to_str(Value v) -{ - ASSERT(value_type(v) == TYPE_STRING); - - StringValue *p = (StringValue*) (v & ~(Value) 7); - return (String) { p->data, p->len }; -} - -/* - -2 bits -> 2^2 = 4 - -00000 0 . -00001 1 . -00010 2 . -00011 3 . -00100 4 . -00101 5 . -00110 6 . -00111 7 . -01000 8 -01001 9 -01010 10 -01011 11 -01100 12 -01101 13 -01110 14 -01111 15 -10000 -16 -10001 -15 -10010 -14 -10011 -13 -10100 -12 -10101 -11 -10110 -10 -10111 -9 -11000 -8 . -11001 -7 . -11010 -6 . -11011 -5 . -11100 -4 . -11101 -3 . -11110 -2 . -11111 -1 . - -*/ - -static Value value_from_s64(int64_t x, WL_Arena *arena, Error *err) -{ - Value v = (Value) x; - Value upper3bits = v >> 61; - - if (upper3bits == 0) - return (v << 3) | TAG_POSITIVE_INT; - - if (upper3bits == 7) - return (v << 3) | TAG_NEGATIVE_INT; - - IntValue *p = alloc(arena, SIZEOF(IntValue), _Alignof(IntValue)); - if (p == NULL) { - REPORT(err, "Out of memory"); - return VALUE_ERROR; - } - - p->type = TYPE_INT; - p->raw = x; - - ASSERT(((Value) p & 7) == 0); - return ((Value) p) | TAG_PTR; -} - -static Value value_from_f64(double x, WL_Arena *arena, Error *err) -{ - FloatValue *v = alloc(arena, SIZEOF(FloatValue), _Alignof(FloatValue)); - if (v == NULL) { - REPORT(err, "Out of memory"); - return VALUE_ERROR; - } - - v->type = TYPE_FLOAT; - v->raw = x; - - ASSERT(((uintptr_t) v & 7) == 0); - return ((Value) v) | TAG_PTR; -} - -static Value value_from_str(String x, WL_Arena *arena, Error *err) -{ - StringValue *v = alloc(arena, SIZEOF(StringValue) + x.len, 8); - if (v == NULL) { - REPORT(err, "Out of memory"); - return VALUE_ERROR; - } - - v->type = TYPE_STRING; - v->len = x.len; - memcpy(v->data, x.ptr, x.len); - - ASSERT(((uintptr_t) v & 7) == 0); - return ((Value) v) | TAG_PTR; -} - -static Value aggregate_empty(bool map, uint32_t cap, WL_Arena *arena, Error *err) -{ - AggregateValue *v = alloc(arena, SIZEOF(AggregateValue) + 2 * cap * SIZEOF(Value), MAX(_Alignof(AggregateValue), 8)); - if (v == NULL) { - REPORT(err, "Out of memory"); - return VALUE_ERROR; - } - - v->type = map ? TYPE_MAP : TYPE_ARRAY; - v->count = 0; - v->capacity = cap; - v->ext = NULL; - - ASSERT(((uintptr_t) v & 7) == 0); - return ((Value) v) | TAG_PTR; -} - -static int64_t aggregate_length(AggregateValue *agg) -{ - int64_t n = agg->count; - - Extension *ext = agg->ext; - while (ext) { - n += ext->count; - ext = ext->next; - } - - return n; -} - -static Value *aggregate_select_by_raw_index(AggregateValue *agg, int64_t idx) -{ - ASSERT(agg->type == TYPE_ARRAY || agg->type == TYPE_MAP); - - if (idx < 0 || idx >= aggregate_length(agg)) - return NULL; - - if (idx < agg->count) - return &agg->vals[idx]; - - idx -= agg->count; - Extension *ext = agg->ext; - while (ext) { - if (idx < ext->count) - return &ext->vals[idx]; - idx -= ext->count; - ext = ext->next; - } - - UNREACHABLE; - return NULL; -} - -static bool value_eql(Value a, Value b); - -static Value *aggregate_select(AggregateValue *agg, Value key) -{ - if (agg->type == TYPE_MAP) { - - for (int i = 0; i < agg->count; i += 2) - if (value_eql(agg->vals[i], key)) - return &agg->vals[i+1]; - - Extension *ext = agg->ext; - while (ext) { - for (int i = 0; i < ext->count; i += 2) - if (value_eql(ext->vals[i], key)) { - return &ext->vals[i+1]; - } - ext = ext->next; - } - - return NULL; - - } else { - - ASSERT(agg->type == TYPE_ARRAY); - - if (value_type(key) != TYPE_INT) - return NULL; - int64_t idx = value_to_s64(key); - - return aggregate_select_by_raw_index(agg, idx); - } -} - -static bool aggregate_append(AggregateValue *agg, Value v1, Value v2, WL_Arena *arena) -{ - if (agg->count < agg->capacity) { - agg->vals[agg->count++] = v1; - if (v2 != VALUE_ERROR) - agg->vals[agg->count++] = v2; - return true; - } - - Extension *tail = agg->ext; - if (tail) - while (tail->next) - tail = tail->next; - - Extension *ext; - if (tail == NULL || tail->count == tail->capacity) { - - int cap = 8; - ext = alloc(arena, SIZEOF(Extension) + cap * sizeof(Value), ALIGNOF(Extension)); - if (ext == NULL) - return false; - - ext->count = 0; - ext->capacity = cap; - ext->next = NULL; - - if (tail) - tail->next = ext; - else - agg->ext = ext; - - } else - ext = tail; - - ext->vals[ext->count++] = v1; - if (v2 != VALUE_ERROR) - ext->vals[ext->count++] = v2; - return true; -} - -static Value value_empty_map(uint32_t cap, WL_Arena *arena, Error *err) -{ - return aggregate_empty(true, 2 * cap, arena, err); -} - -static Value value_empty_array(uint32_t cap, WL_Arena *arena, Error *err) -{ - return aggregate_empty(false, cap, arena, err); -} - -static int64_t value_length(Value set) -{ - ASSERT(value_type(set) == TYPE_MAP || value_type(set) == TYPE_ARRAY); - AggregateValue *agg = (void*) (set & ~(Value) 7); - int64_t len = aggregate_length(agg); - if (agg->type == TYPE_MAP) - len /= 2; - return len; -} - -static bool value_insert(Value set, Value key, Value val, WL_Arena *arena, Error *err) -{ - Type t = value_type(set); - if (t != TYPE_MAP && t != TYPE_ARRAY) { - REPORT(err, "Invalid insertion on non-map and non-array value"); - return false; - } - AggregateValue *agg = (void*) (set & ~(Value) 7); - - Value *dst = aggregate_select(agg, key); - if (dst != NULL) { - *dst = val; - return true; - } - - if (agg->type == TYPE_ARRAY && value_type(key) != TYPE_INT) { - REPORT(err, "Invalid index used in array access"); - return false; - } - - if (!aggregate_append(agg, key, val, arena)) { - REPORT(err, "Out of memory"); - return false; - } - - return true; -} - -static Value value_select(Value set, Value key, Error *err) -{ - Type t = value_type(set); - if (t != TYPE_MAP && t != TYPE_ARRAY) { - REPORT(err, "Invalid selection from non-map and non-array value"); - return VALUE_ERROR; - } - AggregateValue *agg = (void*) (set & ~(Value) 7); - - Value *dst = aggregate_select(agg, key); - if (dst) return *dst; - - if (agg->type == TYPE_ARRAY && value_type(key) != TYPE_INT) { - REPORT(err, "Invalid index used in array access"); - return VALUE_ERROR; - } - - char key_buf[1<<8]; - int key_len = value_convert_to_str(key, key_buf, SIZEOF(key_buf)); - if (key_len > SIZEOF(key_buf)-1) - key_len = SIZEOF(key_buf)-1; - key_buf[key_len] = '\0'; - - char set_buf[1<<8]; - int set_len = value_convert_to_str(set, set_buf, SIZEOF(set_buf)); - if (set_len > SIZEOF(set_buf)-1) - set_len = SIZEOF(set_buf)-1; - set_buf[set_len] = '\0'; - - REPORT(err, "Invalid key '%s' used in access to map '%s'", key_buf, set_buf); - return VALUE_ERROR; -} - -static Value value_select_by_index(Value set, int64_t idx, Error *err) -{ - Type t = value_type(set); - if (t != TYPE_MAP && t != TYPE_ARRAY) { - REPORT(err, "Invalid selection from non-map and non-array value"); - return VALUE_ERROR; - } - AggregateValue *agg = (void*) (set & ~(Value) 7); - - if (agg->type == TYPE_MAP) - idx *= 2; - - Value *src = aggregate_select_by_raw_index(agg, idx); - if (src == NULL) { - REPORT(err, "Invalid selection from non-map and non-array value"); - return VALUE_ERROR; - } - - return *src; -} - -static bool value_append(Value set, Value val, WL_Arena *arena, Error *err) -{ - Type t = value_type(set); - if (t != TYPE_ARRAY) { - REPORT(err, "Invalid append on non-array value"); - return false; - } - AggregateValue *agg = (void*) (set & ~(Value) 7); - - if (!aggregate_append(agg, val, VALUE_ERROR, arena)) { - REPORT(err, "Out of memory"); - return false; - } - - return true; -} - -static bool value_eql(Value a, Value b) -{ - Type t1 = value_type(a); - Type t2 = value_type(b); - - if (t1 != t2) - return false; - - switch (t1) { - - case TYPE_NONE: - return true; - - case TYPE_BOOL: - return a == b; - - case TYPE_INT: - return value_to_s64(a) == value_to_s64(b); - - case TYPE_FLOAT: - return value_to_f64(a) == value_to_f64(b); - - case TYPE_MAP: - return false; // TODO - - case TYPE_ARRAY: - return false; // TODO - - case TYPE_STRING: - return streq(value_to_str(a), value_to_str(b)); - - case TYPE_ERROR: - return true; - } - - return false; -} - -static bool value_nql(Value a, Value b) -{ - return !value_eql(a, b); -} - -#define TYPE_PAIR(X, Y) (((uint16_t) (X) << 16) | (uint16_t) (Y)) - -bool value_greater(Value a, Value b, Error *err) -{ - Type t1 = value_type(a); - Type t2 = value_type(b); - switch (TYPE_PAIR(t1, t2)) { - case TYPE_PAIR(TYPE_INT , TYPE_INT ): return value_to_s64(a) > value_to_s64(b); - case TYPE_PAIR(TYPE_INT , TYPE_FLOAT): return value_to_s64(a) > value_to_f64(b); - case TYPE_PAIR(TYPE_FLOAT, TYPE_INT ): return value_to_f64(a) > value_to_s64(b); - case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): return value_to_f64(a) > value_to_f64(b); - default:break; - } - REPORT(err, "Invalid '>' operation on non-numeric type"); - return false; -} - -bool value_lower(Value a, Value b, Error *err) -{ - Type t1 = value_type(a); - Type t2 = value_type(b); - switch (TYPE_PAIR(t1, t2)) { - case TYPE_PAIR(TYPE_INT , TYPE_INT ): return value_to_s64(a) < value_to_s64(b); - case TYPE_PAIR(TYPE_INT , TYPE_FLOAT): return value_to_s64(a) < value_to_f64(b); - case TYPE_PAIR(TYPE_FLOAT, TYPE_INT ): return value_to_f64(a) < value_to_s64(b); - case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): return value_to_f64(a) < value_to_f64(b); - default:break; - } - REPORT(err, "Invalid '<' operation on non-numeric type"); - return false; -} - -static Value value_neg(Value v, WL_Arena *arena, Error *err) -{ - Type t = value_type(v); - if (t == TYPE_INT) - return value_from_s64(-value_to_s64(v), arena, err); // TODO: overflow - - if (t == TYPE_FLOAT) - return value_from_f64(-value_to_f64(v), arena, err); - - REPORT(err, "Invalid '-' operation on non-numeric type"); - return VALUE_ERROR; -} - -static Value value_add(Value v1, Value v2, WL_Arena *arena, Error *err) -{ - Type t1 = value_type(v1); - Type t2 = value_type(v2); - - Value r; - switch (TYPE_PAIR(t1, t2)) { - - case TYPE_PAIR(TYPE_INT, TYPE_INT): - { - int64_t u = value_to_s64(v1); - int64_t v = value_to_s64(v2); - // TODO: check overflow and underflow - r = value_from_s64(u + v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_INT, TYPE_FLOAT): - { - double u = (double) value_to_s64(v1); - double v = value_to_f64(v2); - r = value_from_f64(u + v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_FLOAT, TYPE_INT): - { - double u = value_to_f64(v1); - double v = (double) value_to_s64(v2); - r = value_from_f64(u + v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): - { - double u = value_to_f64(v1); - double v = value_to_f64(v2); - // TODO: check overflow and underflow - r = value_from_f64(u + v, arena, err); - } - break; - - default: - REPORT(err, "Invalid operation '+' on non-numeric value"); - return VALUE_ERROR; - } - - return r; -} - -static Value value_sub(Value v1, Value v2, WL_Arena *arena, Error *err) -{ - Type t1 = value_type(v1); - Type t2 = value_type(v2); - - Value r; - switch (TYPE_PAIR(t1, t2)) { - - case TYPE_PAIR(TYPE_INT, TYPE_INT): - { - int64_t u = value_to_s64(v1); - int64_t v = value_to_s64(v2); - // TODO: check overflow and underflow - r = value_from_s64(u - v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_INT, TYPE_FLOAT): - { - double u = (double) value_to_s64(v1); - double v = value_to_f64(v2); - r = value_from_f64(u - v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_FLOAT, TYPE_INT): - { - double u = value_to_f64(v1); - double v = (double) value_to_s64(v2); - r = value_from_f64(u - v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): - { - double u = value_to_f64(v1); - double v = value_to_f64(v2); - // TODO: check overflow and underflow - r = value_from_f64(u - v, arena, err); - } - break; - - default: - REPORT(err, "Invalid operation '-' on non-numeric value"); - return VALUE_ERROR; - } - - return r; -} - -static Value value_mul(Value v1, Value v2, WL_Arena *arena, Error *err) -{ - Type t1 = value_type(v1); - Type t2 = value_type(v2); - - Value r; - switch (TYPE_PAIR(t1, t2)) { - - case TYPE_PAIR(TYPE_INT, TYPE_INT): - { - int64_t u = value_to_s64(v1); - int64_t v = value_to_s64(v2); - // TODO: check overflow and underflow - r = value_from_s64(u * v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_INT, TYPE_FLOAT): - { - double u = (double) value_to_s64(v1); - double v = value_to_f64(v2); - r = value_from_f64(u * v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_FLOAT, TYPE_INT): - { - double u = value_to_f64(v1); - double v = (double) value_to_s64(v2); - r = value_from_f64(u * v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): - { - double u = value_to_f64(v1); - double v = value_to_f64(v2); - // TODO: check overflow and underflow - r = value_from_f64(u * v, arena, err); - } - break; - - default: - REPORT(err, "Invalid operation '*' on non-numeric value"); - return VALUE_ERROR; - } - - return r; -} - -static Value value_div(Value v1, Value v2, WL_Arena *arena, Error *err) -{ - Type t1 = value_type(v1); - Type t2 = value_type(v2); - - Value r; - switch (TYPE_PAIR(t1, t2)) { - - case TYPE_PAIR(TYPE_INT, TYPE_INT): - { - // TODO: check division by 0 - - int64_t u = value_to_s64(v1); - int64_t v = value_to_s64(v2); - r = value_from_s64(u / v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_INT, TYPE_FLOAT): - { - // TODO: check division by 0 - - double u = (double) value_to_s64(v1); - double v = value_to_f64(v2); - r = value_from_f64(u / v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_FLOAT, TYPE_INT): - { - // TODO: check division by 0 - - double u = value_to_f64(v1); - double v = (double) value_to_s64(v2); - r = value_from_f64(u / v, arena, err); - } - break; - - case TYPE_PAIR(TYPE_FLOAT, TYPE_FLOAT): - { - double u = value_to_f64(v1); - double v = value_to_f64(v2); - r = value_from_f64(u / v, arena, err); - } - break; - - default: - REPORT(err, "Invalid operation '/' on non-numeric value"); - return VALUE_ERROR; - } - - return r; -} - -static Value value_mod(Value v1, Value v2, WL_Arena *arena, Error *err) -{ - Type t1 = value_type(v1); - Type t2 = value_type(v2); - - if (t1 != TYPE_INT || t2 != TYPE_INT) { - REPORT(err, "Invalid operation '%%' on non-integer value"); - return VALUE_ERROR; - } - - int64_t u = value_to_s64(v1); - int64_t v = value_to_s64(v2); - Value r = value_from_s64(u % v, arena, err); - return r; -} - -static void value_convert_to_str_inner(Writer *w, Value v) -{ - Type t = value_type(v); - switch (t) { - - case TYPE_NONE: - break; - - case TYPE_BOOL: - write_text(w, v == VALUE_TRUE ? S("true") : S("false")); - break; - - case TYPE_INT: - write_text_s64(w, value_to_s64(v)); - break; - - case TYPE_FLOAT: - write_text_f64(w, value_to_f64(v)); - break; - - case TYPE_STRING: - write_text(w, value_to_str(v)); - break; - - case TYPE_ARRAY: - { - AggregateValue *agg = (void*) (v & ~(Value) 7); - for (int i = 0; i < agg->count; i++) - value_convert_to_str_inner(w, agg->vals[i]); - Extension *ext = agg->ext; - while (ext) { - for (int i = 0; i < ext->count; i++) - value_convert_to_str_inner(w, ext->vals[i]); - ext = ext->next; - } - } - break; - - case TYPE_MAP: - write_text(w, S("")); - break; - - case TYPE_ERROR: - break; - } -} - -static int value_convert_to_str(Value v, char *dst, int cap) -{ - Writer w = { dst, cap, 0}; - value_convert_to_str_inner(&w, v); - return w.len; -} - -static Value value_escape_packed(Value v, WL_Arena *arena, Error *err); - -static int array_escape(Value v, Value *out, int max, WL_Arena *arena, Error *err) -{ - Value v2 = value_empty_array(value_length(v), arena, err); - if (v2 == VALUE_ERROR) return -1; - - AggregateValue *src = (void*) (v & ~(Value) 7); - - for (int i = 0; i < src->count; i++) { - - Value child = src->vals[i]; - - Value escaped_child = value_escape_packed(child, arena, err); - if (escaped_child == VALUE_ERROR) - return -1; - - if (!value_append(v2, escaped_child, arena, err)) - return -1; - } - Extension *ext = src->ext; - while (ext) { - for (int i = 0; i < ext->count; i++) { - - Value child = src->vals[i]; - - Value escaped_child = value_escape_packed(child, arena, err); - if (escaped_child == VALUE_ERROR) - return -1; - - if (!value_append(v2, escaped_child, arena, err)) - return -1; - } - ext = ext->next; - } - - if (max == 0) - return -1; - out[0] = v2; - return 1; -} - -static int string_escape(Value v, Value *out, int max, WL_Arena *arena, Error *err) -{ - String s = value_to_str(v); - - int i = 0; - int num = 0; - for (;;) { - - int off = i; - while (i < s.len - && s.ptr[i] != '<' - && s.ptr[i] != '>' - && s.ptr[i] != '&' - && s.ptr[i] != '"' - && s.ptr[i] != '\'') - i++; - String substr = { s.ptr + off, i - off }; - - Value escaped_v = value_from_str(substr, arena, err); // TODO: don't copy the string - if (escaped_v == VALUE_ERROR) return -1; - - if (num == max) { - REPORT(err, "Escape buffer limit reached"); - return -1; - } - out[num++] = escaped_v; - - if (i == s.len) break; - - switch (s.ptr[i++]) { - case '<' : escaped_v = value_from_str(S("<"), arena, err); break; // TODO: don't come these strings - case '>' : escaped_v = value_from_str(S(">"), arena, err); break; - case '&' : escaped_v = value_from_str(S("&"), arena, err); break; - case '"' : escaped_v = value_from_str(S("""), arena, err); break; - case '\'': escaped_v = value_from_str(S("'"), arena, err); break; - } - if (escaped_v == VALUE_ERROR) return -1; - - if (num == max) { - REPORT(err, "Escape buffer limit reached"); - return -1; - } - out[num++] = escaped_v; - } - - return num; -} - -static int value_escape(Value v, Value *out, int max, WL_Arena *arena, Error *err) -{ - Type t = value_type(v); - - if (t == TYPE_ARRAY) - return array_escape(v, out, max, arena, err); - - if (t == TYPE_STRING) - return string_escape(v, out, max, arena, err); - - if (max < 1) - return -1; - out[0] = v; - return 1; -} - -static Value value_escape_packed(Value v, WL_Arena *arena, Error *err) -{ - Value tmp[32]; - int num = value_escape(v, tmp, COUNT(tmp), arena, err); - if (num < 0) return VALUE_ERROR; - - Value escaped_v; - - if (num > 1) { - - Value packed = value_empty_array(num, arena, err); - if (packed == VALUE_ERROR) - return VALUE_ERROR; - - for (int j = 0; j < num; j++) - if (!value_append(packed, tmp[j], arena, err)) - return VALUE_ERROR; - escaped_v = packed; - - } else { - - ASSERT(num == 1); - escaped_v = tmp[0]; - } - - return escaped_v; -} - -#undef TYPE_PAIR - -///////////////////////////////////////////////////////////////////////// -// RUNTIME -///////////////////////////////////////////////////////////////////////// - -#define MAX_STACK 1024 -#define MAX_FRAMES 1024 -#define MAX_GROUPS 8 - -typedef struct { - int retaddr; - int varbase; -} Frame; - -typedef enum { - RUNTIME_BEGIN, - RUNTIME_LOOP, - RUNTIME_DONE, - RUNTIME_ERROR, - RUNTIME_OUTPUT, - RUNTIME_SYSVAR, - RUNTIME_SYSCALL, -} RuntimeState; - -struct WL_Runtime { - - RuntimeState state; - - String code; - String data; - int off; - - int vars; - int stack; - Value values[MAX_STACK]; - - int num_frames; - Frame frames[MAX_FRAMES]; - - int num_groups; - int groups[MAX_GROUPS]; - - WL_Arena *arena; - - char msg[128]; - Error err; - - int stack_before_user; - String str_for_user; - int num_output; - int cur_output; - char buf[128]; -}; - -WL_Runtime *wl_runtime_init(WL_Arena *arena, WL_Program program) -{ - if (program.len < 3 * sizeof(uint32_t)) - return NULL; - - uint32_t magic; - uint32_t code_len; - uint32_t data_len; - - memcpy(&magic , program.ptr + 0, sizeof(uint32_t)); - memcpy(&code_len, program.ptr + 4, sizeof(uint32_t)); - memcpy(&data_len, program.ptr + 8, sizeof(uint32_t)); - - if (magic != WL_MAGIC) - return NULL; - - String code = { program.ptr + sizeof(uint32_t) * 3 , code_len }; - String data = { program.ptr + sizeof(uint32_t) * 3 + code_len, data_len }; - - WL_Runtime *rt = alloc(arena, SIZEOF(WL_Runtime), ALIGNOF(WL_Runtime)); - if (rt == NULL) - return NULL; - - *rt = (WL_Runtime) { - .state = RUNTIME_BEGIN, - .code = code, - .data = data, - .off = 0, - .stack = 0, - .vars = MAX_STACK-1, - .num_frames = 0, - .arena = arena, - .err = { NULL, 0, false }, - }; - rt->err.buf = rt->msg; - rt->err.cap = SIZEOF(rt->msg); - - rt->frames[rt->num_frames++] = (Frame) { - .retaddr = 0, - .varbase = rt->vars, - }; - - return rt; -} - -WL_String wl_runtime_error(WL_Runtime *rt) -{ - return rt->err.yes - ? (WL_String) { rt->msg, strlen(rt->msg) } - : (WL_String) { NULL, 0 }; -} - -static void rt_read_mem(WL_Runtime *r, void *dst, int len) -{ - ASSERT(r->off + len <= r->code.len); - memcpy(dst, r->code.ptr + r->off, len); - r->off += len; -} - -static uint8_t rt_read_u8(WL_Runtime *rt) -{ - ASSERT(rt->state == RUNTIME_LOOP); - - uint8_t x; - rt_read_mem(rt, &x, SIZEOF(x)); - - return x; -} - -static uint32_t rt_read_u32(WL_Runtime *rt) -{ - ASSERT(rt->state == RUNTIME_LOOP); - - uint32_t x; - rt_read_mem(rt, &x, SIZEOF(x)); - - return x; -} - -static int64_t rt_read_s64(WL_Runtime *rt) -{ - ASSERT(rt->state == RUNTIME_LOOP); - - int64_t x; - rt_read_mem(rt, &x, SIZEOF(x)); - - return x; -} - -static double rt_read_f64(WL_Runtime *rt) -{ - ASSERT(rt->state == RUNTIME_LOOP); - - double x; - rt_read_mem(rt, &x, SIZEOF(x)); - - return x; -} - -static String rt_read_str(WL_Runtime *rt) -{ - ASSERT(rt->state == RUNTIME_LOOP); - uint32_t off = rt_read_u32(rt); - uint32_t len = rt_read_u32(rt); - ASSERT(off + len <= (uint32_t) rt->data.len); - return (String) { rt->data.ptr + off, len }; -} - -static Value *rt_variable(WL_Runtime *rt, uint8_t x) -{ - ASSERT(rt->num_frames > 0); - - Frame *frame = &rt->frames[rt->num_frames-1]; - - ASSERT(frame->varbase - x >= 0 - && frame->varbase - x < MAX_STACK); - - return &rt->values[frame->varbase - x]; -} - -static int values_usage(WL_Runtime *rt) -{ - int num_vars = (MAX_STACK - rt->vars - 1); - return rt->stack + num_vars; -} - -static bool rt_check_stack(WL_Runtime *rt, int min) -{ - if (MAX_STACK - values_usage(rt) < min) { - REPORT(&rt->err, "Out of stack"); - rt->state = RUNTIME_ERROR; - return false; - } - return true; -} - -static bool rt_push_frame(WL_Runtime *rt, uint8_t args) -{ - if (rt->num_frames == MAX_FRAMES) { - REPORT(&rt->err, "Call stack limit reached"); - rt->state = RUNTIME_ERROR; - return false; - } - - if (MAX_STACK - values_usage(rt) < args) { - REPORT(&rt->err, "Stack limit reached"); - rt->state = RUNTIME_ERROR; - return false; - } - - Frame *frame = &rt->frames[rt->num_frames++]; - frame->retaddr = rt->off; - frame->varbase = rt->vars; - - for (int i = 0; i < args; i++) - rt->values[rt->vars--] = rt->values[--rt->stack]; - - return true; -} - -static void rt_pop_frame(WL_Runtime *rt) -{ - ASSERT(rt->num_frames > 0); - Frame *frame = &rt->frames[rt->num_frames-1]; - rt->off = frame->retaddr; - rt->vars = frame->varbase; - rt->num_frames--; -} - -static void rt_set_frame_vars(WL_Runtime *rt, uint8_t num) -{ - ASSERT(rt->num_frames > 0); - Frame *frame = &rt->frames[rt->num_frames-1]; - int num_vars = frame->varbase - rt->vars; - if (num_vars < num) - for (int i = 0; i < num - num_vars; i++) - rt->values[rt->vars - i] = VALUE_NONE; - rt->vars = frame->varbase - num; -} - -static void rt_push_group(WL_Runtime *rt) -{ - if (rt->num_groups == MAX_GROUPS) { - REPORT(&rt->err, "Out of memory"); - rt->state = RUNTIME_ERROR; - return; - } - rt->groups[rt->num_groups++] = rt->stack; -} - -static void rt_pack_group(WL_Runtime *rt) -{ - if (!rt_check_stack(rt, 1)) - return; - - ASSERT(rt->num_groups > 0); - int start = rt->groups[--rt->num_groups]; - int end = rt->stack; - - if (end - start > 1) { - - Value set = value_empty_array(end - start, rt->arena, &rt->err); - if (set == VALUE_ERROR) - return; - - for (int i = start; i < end; i++) - if (!value_append(set, rt->values[i], rt->arena, &rt->err)) - return; - - rt->stack = start; - rt->values[rt->stack++] = set; - } -} - -static void rt_pop_group(WL_Runtime *rt) -{ - ASSERT(rt->num_groups > 0); - rt->stack = rt->groups[--rt->num_groups]; -} - -static void value_print(Value v) -{ - char buf[1<<8]; - int len = value_convert_to_str(v, buf, SIZEOF(buf)); - if (len < SIZEOF(buf)) - fwrite(buf, 1, len, stdout); - else { - len = SIZEOF(buf)-1; - fwrite(buf, 1, len, stdout); - fprintf(stdout, " [...]"); - } - putc('\n', stdout); - fflush(stdout); -} - -static void step(WL_Runtime *rt) -{ - switch (rt_read_u8(rt)) { - - Type t; - Value v1; - Value v2; - Value v3; - uint32_t o; - uint8_t b1; - uint8_t b2; - uint8_t b3; - int64_t i; - double f; - String s; - - case OPCODE_NOPE: - break; - - case OPCODE_JUMP: - rt->off = rt_read_u32(rt); - break; - - case OPCODE_JIFP: - ASSERT(rt->stack > 0); - o = rt_read_u32(rt); - v1 = rt->values[--rt->stack]; - if (v1 == VALUE_FALSE) - rt->off = o; - else if (value_type(v1) != TYPE_BOOL) { - REPORT(&rt->err, "Invalid non-boolean condition"); - rt->state = RUNTIME_ERROR; - break; - } - break; - - case OPCODE_VARS: - b1 = rt_read_u8(rt); - rt_set_frame_vars(rt, b1); - break; - - case OPCODE_OUTPUT: - if (rt->stack > 0) { - rt->cur_output = 0; - rt->num_output = rt->stack; - rt->state = RUNTIME_OUTPUT; - } - break; - - case OPCODE_SYSVAR: - s = rt_read_str(rt); - rt_push_frame(rt, 0); - rt->stack_before_user = rt->stack; - rt->str_for_user = s; - rt->state = RUNTIME_SYSVAR; - break; - - case OPCODE_SYSCALL: - b1 = rt_read_u8(rt); - s = rt_read_str(rt); - rt_push_frame(rt, b1); - rt->stack_before_user = rt->stack; - rt->str_for_user = s; - rt->state = RUNTIME_SYSCALL; - break; - - case OPCODE_CALL: - b1 = rt_read_u8(rt); - o = rt_read_u32(rt); - rt_push_frame(rt, b1); - rt->off = o; - break; - - case OPCODE_RET: - rt_pop_frame(rt); - break; - - case OPCODE_GROUP: - rt_push_group(rt); - break; - - case OPCODE_ESCAPE: - { - ASSERT(rt->num_groups > 0); - int start = rt->groups[--rt->num_groups]; - int end = rt->stack; - - Value escaped[256]; - int num_escaped = 0; - - for (int i = start; i < end; i++) { - Value v = rt->values[i]; - int num = value_escape(v, escaped + num_escaped, COUNT(escaped) - num_escaped, rt->arena, &rt->err); - if (num < 0) break; - num_escaped += num; - } - - if (num_escaped > COUNT(escaped)) { - REPORT(&rt->err, "Escape buffer limit reached"); - rt->state = RUNTIME_ERROR; - break; - } - - rt->stack = start; - if (!rt_check_stack(rt, num_escaped)) break; - - for (int i = 0; i < num_escaped; i++) - rt->values[rt->stack + i] = escaped[i]; - rt->stack += num_escaped; - } - break; - - case OPCODE_PACK: - rt_pack_group(rt); - break; - - case OPCODE_GPOP: - rt_pop_group(rt); - break; - - case OPCODE_FOR: - b1 = rt_read_u8(rt); - b2 = rt_read_u8(rt); - b3 = rt_read_u8(rt); - o = rt_read_u32(rt); - - v1 = *rt_variable(rt, b3); - ASSERT(value_type(v1) == TYPE_INT); - i = value_to_s64(v1); - - v2 = *rt_variable(rt, b1); - - if (value_length(v2)-1 == i) { - rt->off = o; - break; - } - i++; - - v1 = value_select_by_index(v2, i, &rt->err); - if (v1 == VALUE_ERROR) break; - - *rt_variable(rt, b2) = v1; - - v1 = value_from_s64(i, rt->arena, &rt->err); // TODO: this could be in-place - *rt_variable(rt, b3) = v1; - break; - - case OPCODE_EXIT: - rt->state = RUNTIME_DONE; - break; - - case OPCODE_POP: - ASSERT(rt->stack > 0); - rt->stack--; - break; - - case OPCODE_SETV: - ASSERT(rt->stack > 0); - b1 = rt_read_u8(rt); - *rt_variable(rt, b1) = rt->values[rt->stack-1]; - break; - - case OPCODE_PUSHV: - if (!rt_check_stack(rt, 1)) break; - b1 = rt_read_u8(rt); - rt->values[rt->stack++] = *rt_variable(rt, b1); - break; - - case OPCODE_PUSHI: - if (!rt_check_stack(rt, 1)) break; - i = rt_read_s64(rt); - v1 = value_from_s64(i, rt->arena, &rt->err); - rt->values[rt->stack++] = v1; - break; - - case OPCODE_PUSHF: - if (!rt_check_stack(rt, 1)) break; - f = rt_read_f64(rt); - v1 = value_from_f64(f, rt->arena, &rt->err); - rt->values[rt->stack++] = v1; - break; - - case OPCODE_PUSHS: - if (!rt_check_stack(rt, 1)) break; - s = rt_read_str(rt); - v1 = value_from_str(s, rt->arena, &rt->err); - rt->values[rt->stack++] = v1; - break; - - case OPCODE_PUSHA: - if (!rt_check_stack(rt, 1)) break; - o = rt_read_u32(rt); - v1 = value_empty_array(o, rt->arena, &rt->err); - rt->values[rt->stack++] = v1; - break; - - case OPCODE_PUSHM: - if (!rt_check_stack(rt, 1)) break; - o = rt_read_u32(rt); - v1 = value_empty_map(o, rt->arena, &rt->err); - rt->values[rt->stack++] = v1; - break; - - case OPCODE_PUSHN: - if (!rt_check_stack(rt, 1)) break; - rt->values[rt->stack++] = VALUE_NONE; - break; - - case OPCODE_PUSHT: - if (!rt_check_stack(rt, 1)) break; - rt->values[rt->stack++] = VALUE_TRUE; - break; - - case OPCODE_PUSHFL: - if (!rt_check_stack(rt, 1)) break; - rt->values[rt->stack++] = VALUE_FALSE; - break; - - case OPCODE_LEN: - ASSERT(rt->stack > 0); - v1 = rt->values[rt->stack-1]; - t = value_type(v1); - if (t != TYPE_ARRAY && t != TYPE_MAP) { - REPORT(&rt->err, "Invalid operation 'len' on non-aggregate value"); - rt->state = RUNTIME_ERROR; - break; - } - v2 = value_from_s64(value_length(v1), rt->arena, &rt->err); - rt->values[rt->stack-1] = v2; - break; - - case OPCODE_NEG: - ASSERT(rt->stack > 0); - v1 = rt->values[rt->stack-1]; - v2 = value_neg(v1, rt->arena, &rt->err); - rt->values[rt->stack-1] = v2; - break; - - case OPCODE_EQL: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_eql(v2, v1) ? VALUE_TRUE : VALUE_FALSE; - rt->values[rt->stack++] = v3; - break; - - case OPCODE_NQL: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_nql(v2, v1) ? VALUE_TRUE : VALUE_FALSE; - rt->values[rt->stack++] = v3; - break; - - case OPCODE_LSS: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_lower(v2, v1, &rt->err) ? VALUE_TRUE : VALUE_FALSE; - rt->values[rt->stack++] = v3; - break; - - case OPCODE_GRT: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_greater(v2, v1, &rt->err) ? VALUE_TRUE : VALUE_FALSE; - rt->values[rt->stack++] = v3; - break; - - case OPCODE_ADD: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_add(v2, v1, rt->arena, &rt->err); - rt->values[rt->stack++] = v3; - break; - - case OPCODE_SUB: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_sub(v2, v1, rt->arena, &rt->err); - rt->values[rt->stack++] = v3; - break; - - case OPCODE_MUL: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_mul(v2, v1, rt->arena, &rt->err); - rt->values[rt->stack++] = v3; - break; - - case OPCODE_DIV: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_div(v2, v1, rt->arena, &rt->err); - rt->values[rt->stack++] = v3; - break; - - case OPCODE_MOD: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_mod(v2, v1, rt->arena, &rt->err); - rt->values[rt->stack++] = v3; - break; - - case OPCODE_APPEND: - ASSERT(rt->stack > 1); - v2 = rt->values[--rt->stack]; - v1 = rt->values[rt->stack-1]; - value_append(v1, v2, rt->arena, &rt->err); - break; - - case OPCODE_INSERT1: - ASSERT(rt->stack > 2); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = rt->values[rt->stack-1]; - value_insert(v3, v1, v2, rt->arena, &rt->err); - break; - - case OPCODE_INSERT2: - ASSERT(rt->stack > 2); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = rt->values[rt->stack-1]; - value_insert(v2, v1, v3, rt->arena, &rt->err); - break; - - case OPCODE_SELECT: - ASSERT(rt->stack > 1); - v1 = rt->values[--rt->stack]; - v2 = rt->values[--rt->stack]; - v3 = value_select(v2, v1, &rt->err); - rt->values[rt->stack++] = v3; - break; - - default: - UNREACHABLE; - } -} - -WL_EvalResult wl_runtime_eval(WL_Runtime *rt) -{ - if (rt->state != RUNTIME_OUTPUT || rt->cur_output == rt->num_output) { - - switch (rt->state) { - - case RUNTIME_BEGIN: - break; - - case RUNTIME_DONE: - return (WL_EvalResult) { .type=WL_EVAL_DONE }; - - case RUNTIME_ERROR: - return (WL_EvalResult) { .type=WL_EVAL_ERROR }; - - case RUNTIME_OUTPUT: - rt->stack -= rt->num_output; - break; - - case RUNTIME_SYSVAR: - { - ASSERT(rt->stack >= rt->stack_before_user); - - int pushed_by_user = rt->stack - rt->stack_before_user; - if (pushed_by_user > 1) { - REPORT(&rt->err, "Invalid API usage"); - rt->state = RUNTIME_ERROR; - return (WL_EvalResult) { .type=WL_EVAL_ERROR }; - } - - if (rt->stack == rt->stack_before_user) { - // User didn't push anything on the stack - if (!rt_check_stack(rt, 1)) - return (WL_EvalResult) { .type=WL_EVAL_ERROR }; - rt->values[rt->stack++] = VALUE_NONE; - } - - rt_pop_frame(rt); - } - break; - - case RUNTIME_SYSCALL: - ASSERT(rt->stack >= rt->stack_before_user); - rt_pop_frame(rt); - break; - - default: - UNREACHABLE; - } - - rt->state = RUNTIME_LOOP; - - do { - - step(rt); - - if (rt->err.yes) - rt->state = RUNTIME_ERROR; - - } while (rt->state == RUNTIME_LOOP); - - } - - switch (rt->state) { - - case RUNTIME_BEGIN: - case RUNTIME_LOOP: - UNREACHABLE; - - case RUNTIME_DONE: - break; - - case RUNTIME_ERROR: - return (WL_EvalResult) { .type=WL_EVAL_ERROR }; - - case RUNTIME_OUTPUT: - { - ASSERT(rt->cur_output < rt->num_output); - - Value v = rt->values[rt->stack - rt->num_output + rt->cur_output]; - Type type = value_type(v); - - String str; - if (type == TYPE_STRING) - str = value_to_str(v); - else { - int len = value_convert_to_str(v, rt->buf, SIZEOF(rt->buf)); - if (len > SIZEOF(rt->buf)) { - char *p = alloc(rt->arena, len, 1); - if (p == NULL) { - REPORT(&rt->err, "Out of memory"); - rt->state = RUNTIME_ERROR; - return (WL_EvalResult) { .type=WL_EVAL_ERROR }; - } - len = value_convert_to_str(v, p, len); - str = (String) { p, len }; - } else { - str = (String) { rt->buf, len }; - } - } - - rt->cur_output++; - return (WL_EvalResult) { .type=WL_EVAL_OUTPUT, .str={ str.ptr, str.len } }; - } - - case RUNTIME_SYSVAR: - return (WL_EvalResult) { .type=WL_EVAL_SYSVAR, .str=(WL_String) { rt->str_for_user.ptr, rt->str_for_user.len } }; - - case RUNTIME_SYSCALL: - return (WL_EvalResult) { .type=WL_EVAL_SYSCALL, .str=(WL_String) { rt->str_for_user.ptr, rt->str_for_user.len } }; - } - - return (WL_EvalResult) { .type=WL_EVAL_DONE }; -} - -bool wl_streq(WL_String a, char *b, int blen) -{ - if (b == NULL) b = ""; - if (blen < 0) blen = strlen(b); - return streq((String) { a.ptr, a.len }, (String) { b, blen }); -} - -int wl_arg_count(WL_Runtime *rt) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return -1; - - ASSERT(rt->num_frames > 0); - return rt->frames[rt->num_frames-1].varbase - rt->vars; // TODO: is this right? -} - -static Value user_arg(WL_Runtime *rt, int idx, Type type) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return -1; - - int tot = wl_arg_count(rt); - if (idx < 0 || idx >= tot) - return false; - - Value v = *rt_variable(rt, tot - idx - 1); - if (value_type(v) != type) - return VALUE_ERROR; - - return v; -} - -bool wl_arg_none(WL_Runtime *rt, int idx) -{ - Value v = user_arg(rt, idx, TYPE_NONE); - if (v == VALUE_ERROR) - return false; - return true; -} - -bool wl_arg_bool(WL_Runtime *rt, int idx, bool *x) -{ - Value v = user_arg(rt, idx, TYPE_BOOL); - if (v == VALUE_ERROR) - return false; - *x = (v == VALUE_TRUE); - return true; -} - -bool wl_arg_s64(WL_Runtime *rt, int idx, int64_t *x) -{ - Value v = user_arg(rt, idx, TYPE_INT); - if (v == VALUE_ERROR) - return false; - *x = value_to_s64(v); - return true; -} - -bool wl_arg_f64(WL_Runtime *rt, int idx, double *x) -{ - Value v = user_arg(rt, idx, TYPE_FLOAT); - if (v == VALUE_ERROR) - return false; - *x = value_to_f64(v); - return true; -} - -bool wl_arg_str(WL_Runtime *rt, int idx, WL_String *x) -{ - Value v = user_arg(rt, idx, TYPE_STRING); - if (v == VALUE_ERROR) - return false; - String s = value_to_str(v); - *x = (WL_String) { s.ptr, s.len }; - return true; -} - -bool wl_arg_array(WL_Runtime *rt, int idx) -{ - Value v = user_arg(rt, idx, TYPE_ARRAY); - if (v == VALUE_ERROR) - return false; - return true; -} - -bool wl_arg_map(WL_Runtime *rt, int idx) -{ - Value v = user_arg(rt, idx, TYPE_MAP); - if (v == VALUE_ERROR) - return false; - return true; -} - -static Value user_peek(WL_Runtime *rt, int off, Type type) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return VALUE_ERROR; - - if (rt->stack + off < rt->stack_before_user || off >= 0) - return VALUE_ERROR; - - Value v = rt->values[rt->stack + off]; - if (value_type(v) != type) - return VALUE_ERROR; - - return v; -} - -bool wl_peek_none(WL_Runtime *rt, int off) -{ - Value v = user_peek(rt, off, TYPE_NONE); - if (v == VALUE_ERROR) - return false; - return true; -} - -bool wl_peek_bool(WL_Runtime *rt, int off, bool *x) -{ - Value v = user_peek(rt, off, TYPE_BOOL); - if (v == VALUE_ERROR) - return false; - *x = (v == VALUE_TRUE); - return true; -} - -bool wl_peek_s64(WL_Runtime *rt, int off, int64_t *x) -{ - Value v = user_peek(rt, off, TYPE_INT); - if (v == VALUE_ERROR) - return false; - *x = value_to_s64(v); - return true; -} - -bool wl_peek_f64(WL_Runtime *rt, int off, double *x) -{ - Value v = user_peek(rt, off, TYPE_FLOAT); - if (v == VALUE_ERROR) - return false; - *x = value_to_f64(v); - return true; -} - -bool wl_peek_str(WL_Runtime *rt, int off, WL_String *x) -{ - Value v = user_peek(rt, off, TYPE_STRING); - if (v == VALUE_ERROR) - return false; - String s = value_to_str(v); - *x = (WL_String) { s.ptr, s.len }; - return true; -} - -bool wl_pop_any(WL_Runtime *rt) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return VALUE_ERROR; - - if (rt->stack == rt->stack_before_user) - return false; - - ASSERT(rt->stack > 0); - rt->stack--; - return true; -} - -static Value user_pop(WL_Runtime *rt, Type type) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return VALUE_ERROR; - - if (rt->stack == rt->stack_before_user) - return VALUE_ERROR; - - ASSERT(rt->stack > 0); - Value v = rt->values[rt->stack-1]; - if (value_type(v) != type) - return VALUE_ERROR; - - rt->stack--; - return v; -} - -bool wl_pop_none(WL_Runtime *rt) -{ - Value v = user_pop(rt, TYPE_NONE); - if (v == VALUE_ERROR) - return false; - return true; -} - -bool wl_pop_bool(WL_Runtime *rt, bool *x) -{ - Value v = user_pop(rt, TYPE_BOOL); - if (v == VALUE_ERROR) - return false; - *x = (v == VALUE_TRUE); - return true; -} - -bool wl_pop_s64(WL_Runtime *rt, int64_t *x) -{ - Value v = user_pop(rt, TYPE_INT); - if (v == VALUE_ERROR) - return false; - *x = value_to_s64(v); - return true; -} - -bool wl_pop_f64(WL_Runtime *rt, double *x) -{ - Value v = user_pop(rt, TYPE_FLOAT); - if (v == VALUE_ERROR) - return false; - *x = value_to_f64(v); - return true; -} - -bool wl_pop_str(WL_Runtime *rt, WL_String *x) -{ - Value v = user_pop(rt, TYPE_STRING); - if (v == VALUE_ERROR) - return false; - String s = value_to_str(v); - *x = (WL_String) { s.ptr, s.len }; - return true; -} - -void wl_push_none(WL_Runtime *rt) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (!rt_check_stack(rt, 1)) - return; - - rt->values[rt->stack++] = VALUE_NONE; -} - -void wl_push_true(WL_Runtime *rt) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (!rt_check_stack(rt, 1)) - return; - - rt->values[rt->stack++] = VALUE_TRUE; -} - -void wl_push_false(WL_Runtime *rt) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (!rt_check_stack(rt, 1)) - return; - - rt->values[rt->stack++] = VALUE_FALSE; -} - -void wl_push_s64(WL_Runtime *rt, int64_t x) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (!rt_check_stack(rt, 1)) - return; - - Value v = value_from_s64(x, rt->arena, &rt->err); - if (v == VALUE_ERROR) { - rt->state = RUNTIME_ERROR; - return; - } - - rt->values[rt->stack++] = v; -} - -void wl_push_f64(WL_Runtime *rt, double x) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (!rt_check_stack(rt, 1)) - return; - - Value v = value_from_f64(x, rt->arena, &rt->err); - if (v == VALUE_ERROR) { - rt->state = RUNTIME_ERROR; - return; - } - - rt->values[rt->stack++] = v; -} - -void wl_push_str(WL_Runtime *rt, WL_String x) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (!rt_check_stack(rt, 1)) - return; - - Value v = value_from_str((String) { x.ptr, x.len }, rt->arena, &rt->err); - if (v == VALUE_ERROR) { - rt->state = RUNTIME_ERROR; - return; - } - - rt->values[rt->stack++] = v; -} - -void wl_push_array(WL_Runtime *rt, int cap) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (!rt_check_stack(rt, 1)) - return; - - Value v = value_empty_array(cap, rt->arena, &rt->err); - if (v == VALUE_ERROR) { - rt->state = RUNTIME_ERROR; - return; - } - - rt->values[rt->stack++] = v; -} - -void wl_push_map(WL_Runtime *rt, int cap) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (!rt_check_stack(rt, 1)) - return; - - Value v = value_empty_map(cap, rt->arena, &rt->err); - if (v == VALUE_ERROR) { - rt->state = RUNTIME_ERROR; - return; - } - - rt->values[rt->stack++] = v; -} - -void wl_push_arg(WL_Runtime *rt, int idx) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (!rt_check_stack(rt, 1)) - return; - - int tot = wl_arg_count(rt); - if (idx < 0 || idx >= tot) { - REPORT(&rt->err, "Invalid API usagge"); - rt->state = RUNTIME_ERROR; - return; - } - - rt->values[rt->stack++] = *rt_variable(rt, tot - idx - 1); -} - -void wl_insert(WL_Runtime *rt) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (rt->stack - rt->stack_before_user < 3) { - REPORT(&rt->err, "Invalid API usagge"); - rt->state = RUNTIME_ERROR; - return; - } - - Value key = rt->values[--rt->stack]; - Value val = rt->values[--rt->stack]; - Value set = rt->values[rt->stack-1]; - - if (!value_insert(set, key, val, rt->arena, &rt->err)) { - rt->state = RUNTIME_ERROR; - return; - } -} - -void wl_append(WL_Runtime *rt) -{ - if (rt->state != RUNTIME_SYSVAR && - rt->state != RUNTIME_SYSCALL) - return; - - if (rt->stack - rt->stack_before_user < 2) { - REPORT(&rt->err, "Invalid API usagge"); - rt->state = RUNTIME_ERROR; - return; - } - - Value val = rt->values[--rt->stack]; - Value set = rt->values[rt->stack-1]; - - if (!value_append(set, val, rt->arena, &rt->err)) { - rt->state = RUNTIME_ERROR; - return; - } -} - -void wl_runtime_dump(WL_Runtime *rt) -{ - for (int i = 0; i < rt->num_frames; i++) { - printf("=== frame %d ===\n", i); - - Frame *frame = &rt->frames[i]; - - int num_vars; - if (i+1 < rt->num_frames) - num_vars = frame->varbase - rt->frames[i+1].varbase; - else - num_vars = frame->varbase - rt->vars; - - for (int j = 0; j < num_vars; j++) { - printf(" %d = ", j); - value_print(rt->values[frame->varbase - j]); - } - } - printf("===============\n"); -} - //////////////////////////////////////////////////////////////////////////////////////// // src/main.c //////////////////////////////////////////////////////////////////////////////////////// -#include "sqlite3.h" -#include "wl.h" -#include "chttp.h" -#include "cweb.h" +#include #ifdef __linux__ #include @@ -12999,6 +961,25 @@ void wl_runtime_dump(WL_Runtime *rt) #include #endif +#ifdef CWEB_ENABLE_DATABASE +#include "sqlite3.h" +#endif + +#ifdef CWEB_ENABLE_TEMPLATE +#include "wl.h" +#endif + +#include "chttp.h" +#include "cweb.h" + +#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) +#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) +#define ASSERT(X) {if (!(X)) { __builtin_trap(); }} +#define TRACE(X, ...) ((void) 0) +#define SIZEOF(X) (int) (sizeof(X)/sizeof((X)[0])) + +#ifdef CWEB_ENABLE_TEMPLATE + typedef struct { char path[1<<8]; int pathlen; @@ -13014,12 +995,16 @@ typedef struct { static TemplateCache *template_cache_init(int capacity_log2); static void template_cache_free(TemplateCache *cache); +#endif + typedef struct SessionStorage SessionStorage; + +#ifdef CWEB_ENABLE_DATABASE + typedef struct SQLiteCache SQLiteCache; static SQLiteCache* sqlite_cache_init(sqlite3 *db, int capacity_log2); static void sqlite_cache_free(SQLiteCache *cache); -static sqlite3* sqlite_cache_getdb(SQLiteCache *cache); static int sqlite3utils_prepare(SQLiteCache *cache, sqlite3_stmt **pstmt, char *fmt, int fmtlen); @@ -13030,26 +1015,7 @@ static int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache, #define sqlite3utils_prepare_and_bind(cache, pstmt, fmt, ...) \ sqlite3utils_prepare_and_bind_impl((cache), (pstmt), (fmt), VARGS(__VA_ARGS__)) -struct CWEB { - - HTTP_Server *server; - int pool_cap; - char *pool; - - // Login - SessionStorage *session_storage; - - // Database - sqlite3 *db; - SQLiteCache *dbcache; - - // Template - TemplateCache *tpcache; - - bool allow_insecure_login; - - CWEB_Request req; -}; +#endif struct CWEB_Request { @@ -13067,6 +1033,29 @@ struct CWEB_Request { CWEB_String csrf; }; +struct CWEB { + + HTTP_Server *server; + int pool_cap; + char *pool; + + // Login + SessionStorage *session_storage; + +#ifdef CWEB_ENABLE_DATABASE + sqlite3 *db; + SQLiteCache *dbcache; +#endif + +#ifdef CWEB_ENABLE_TEMPLATE + TemplateCache *tpcache; +#endif + + bool allow_insecure_login; + + CWEB_Request req; +}; + /////////////////////////// bool cweb_streq(CWEB_String a, CWEB_String b) @@ -13105,22 +1094,94 @@ CWEB_VArg cweb_varg_from_ps (short *ps) { return (CWEB_VArg) { CWEB_VA CWEB_VArg cweb_varg_from_pi (int *pi) { return (CWEB_VArg) { CWEB_VARG_TYPE_PI, .pi=pi }; } CWEB_VArg cweb_varg_from_pl (long *pl) { return (CWEB_VArg) { CWEB_VARG_TYPE_PL, .pl=pl }; } CWEB_VArg cweb_varg_from_pll (long long *pll) { return (CWEB_VArg) { CWEB_VARG_TYPE_PLL, .pll=pll }; } -CWEB_VArg cweb_varg_from_psc (char *psc) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSC, .psc=psc }; } -CWEB_VArg cweb_varg_from_pss (short *pss) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSS, .pss=pss }; } -CWEB_VArg cweb_varg_from_psi (int *psi) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSI, .psi=psi }; } -CWEB_VArg cweb_varg_from_psl (long *psl) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSL, .psl=psl }; } -CWEB_VArg cweb_varg_from_psll (long long *psll) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSLL, .psll=psll }; } -CWEB_VArg cweb_varg_from_puc (char *puc) { return (CWEB_VArg) { CWEB_VARG_TYPE_PUC, .puc=puc }; } -CWEB_VArg cweb_varg_from_pus (short *pus) { return (CWEB_VArg) { CWEB_VARG_TYPE_PUS, .pus=pus }; } -CWEB_VArg cweb_varg_from_pui (int *pui) { return (CWEB_VArg) { CWEB_VARG_TYPE_PUI, .pui=pui }; } -CWEB_VArg cweb_varg_from_pul (long *pul) { return (CWEB_VArg) { CWEB_VARG_TYPE_PUL, .pul=pul }; } -CWEB_VArg cweb_varg_from_pull (long long *pull) { return (CWEB_VArg) { CWEB_VARG_TYPE_PULL, .pull=pull }; } +CWEB_VArg cweb_varg_from_psc (signed char *psc) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSC, .psc=psc }; } +CWEB_VArg cweb_varg_from_pss (signed short *pss) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSS, .pss=pss }; } +CWEB_VArg cweb_varg_from_psi (signed int *psi) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSI, .psi=psi }; } +CWEB_VArg cweb_varg_from_psl (signed long *psl) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSL, .psl=psl }; } +CWEB_VArg cweb_varg_from_psll (signed long long *psll) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSLL, .psll=psll }; } +CWEB_VArg cweb_varg_from_puc (unsigned char *puc) { return (CWEB_VArg) { CWEB_VARG_TYPE_PUC, .puc=puc }; } +CWEB_VArg cweb_varg_from_pus (unsigned short *pus) { return (CWEB_VArg) { CWEB_VARG_TYPE_PUS, .pus=pus }; } +CWEB_VArg cweb_varg_from_pui (unsigned int *pui) { return (CWEB_VArg) { CWEB_VARG_TYPE_PUI, .pui=pui }; } +CWEB_VArg cweb_varg_from_pul (unsigned long *pul) { return (CWEB_VArg) { CWEB_VARG_TYPE_PUL, .pul=pul }; } +CWEB_VArg cweb_varg_from_pull (unsigned long long *pull) { return (CWEB_VArg) { CWEB_VARG_TYPE_PULL, .pull=pull }; } CWEB_VArg cweb_varg_from_pf (float *pf) { return (CWEB_VArg) { CWEB_VARG_TYPE_PF, .pf=pf }; } CWEB_VArg cweb_varg_from_pd (double *pd) { return (CWEB_VArg) { CWEB_VARG_TYPE_PD, .pd=pd }; } CWEB_VArg cweb_varg_from_pb (bool *pb) { return (CWEB_VArg) { CWEB_VARG_TYPE_PB, .pb=pb }; } CWEB_VArg cweb_varg_from_pstr (CWEB_String *pstr) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSTR, .pstr=pstr }; } CWEB_VArg cweb_varg_from_phash(CWEB_PasswordHash *phash) { return (CWEB_VArg) { CWEB_VARG_TYPE_PHASH, .phash=phash }; } +///////////////////////////////////////////////////////////////// +// FILE SYSTEM +//////////////////////////////////////////////////////////////// + +typedef struct LoadedFile LoadedFile; +struct LoadedFile { + LoadedFile* next; + int len; + char data[]; +}; + +static LoadedFile *load_file(CWEB_String path) +{ + char buf[1<<10]; + if (path.len >= (int) sizeof(buf)) + return NULL; + memcpy(buf, path.ptr, path.len); + buf[path.len] = '\0'; + + FILE *stream = fopen(buf, "rb"); + if (stream == NULL) + return NULL; + + int ret = fseek(stream, 0, SEEK_END); + if (ret) { + fclose(stream); + return NULL; + } + + long tmp = ftell(stream); + if (tmp < 0 || tmp > INT_MAX) { + fclose(stream); + return NULL; + } + int len = (int) tmp; + + ret = fseek(stream, 0, SEEK_SET); + if (ret) { + fclose(stream); + return NULL; + } + + LoadedFile *result = malloc(sizeof(LoadedFile) + len + 1); + if (result == NULL) { + fclose(stream); + return NULL; + } + result->next = NULL; + result->len = len; + + int read_len = fread(result->data, 1, len+1, stream); + if (read_len != len || ferror(stream) || !feof(stream)) { + fclose(stream); + free(result); + return NULL; + } + + result->data[len] = '\0'; + + fclose(stream); + return result; +} + +static void free_loaded_files(LoadedFile *loaded_file) +{ + while (loaded_file) { + LoadedFile *next = loaded_file->next; + free(loaded_file); + loaded_file = next; + } +} + ///////////////////////////////////////////////////////////////// // RANDOM //////////////////////////////////////////////////////////////// @@ -13229,7 +1290,7 @@ static int hex_digit_to_int(char c) static void unpack_token(char *src, int srclen, char *dst, int dstlen) { - assert(2 * srclen == dstlen); + ASSERT(2 * srclen == dstlen); for (int i = 0; i < srclen; i++) { static const char table[] = "0123456789abcdef"; @@ -13245,7 +1306,7 @@ static int pack_token(char *src, int srclen, char *dst, int dstlen) if (srclen & 1) return -1; - assert(srclen == 2 * dstlen); + ASSERT(srclen == 2 * dstlen); for (int i = 0; i < srclen; i += 2) { int high = src[i+0]; @@ -13368,7 +1429,7 @@ static int delete_session(SessionStorage *storage, CWEB_String sess) Session *found = lookup_session_slot(storage, sess, false); if (found == NULL) return false; - assert(found->user >= 0); + ASSERT(found->user >= 0); found->user = -2; storage->count--; return 0; @@ -13379,7 +1440,7 @@ static int find_session(SessionStorage *storage, CWEB_String sess, CWEB_String * Session *found = lookup_session_slot(storage, sess, false); if (found == NULL) return -1; - assert(found->user >= 0); + ASSERT(found->user >= 0); *pcsrf = (CWEB_String) { found->csrf, CSRF_TOKEN_SIZE }; *puser = found->user; return 0; @@ -13389,6 +1450,8 @@ static int find_session(SessionStorage *storage, CWEB_String sess, CWEB_String * // DATABASE //////////////////////////////////////////////////////////////// +#ifdef CWEB_ENABLE_DATABASE + typedef struct { char *str; int len; @@ -13430,11 +1493,6 @@ static void sqlite_cache_free(SQLiteCache *cache) free(cache); } -static sqlite3 *sqlite_cache_getdb(SQLiteCache *cache) -{ - return cache->db; -} - static unsigned long djb2(char *src, int len) { char *ptr = src; @@ -13447,7 +1505,7 @@ static unsigned long djb2(char *src, int len) return hash; } -static int lookup(SQLiteCache *cache, char *fmt, int fmtlen) +static int sqlite_cache_lookup(SQLiteCache *cache, char *fmt, int fmtlen) { int mask = (1 << cache->capacity_log2) - 1; int hash = djb2(fmt, fmtlen); @@ -13473,7 +1531,7 @@ static int sqlite3utils_prepare(SQLiteCache *cache, sqlite3_stmt **pstmt, char * if (fmtlen < 0) fmtlen = strlen(fmt); - int i = lookup(cache, fmt, fmtlen); + int i = sqlite_cache_lookup(cache, fmt, fmtlen); if (cache->items[i].stmt == NULL) { sqlite3_stmt *stmt; @@ -13530,6 +1588,9 @@ static int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache, case CWEB_VARG_TYPE_D : ret = sqlite3_bind_double(stmt, i+1, arg.d); break; case CWEB_VARG_TYPE_B : ret = sqlite3_bind_int (stmt, i+1, arg.b); break; case CWEB_VARG_TYPE_STR: ret = sqlite3_bind_text (stmt, i+1, arg.str.ptr, arg.str.len, NULL); break; + default: + // TODO + break; } if (ret != SQLITE_OK) { sqlite3_reset(stmt); @@ -13541,8 +1602,11 @@ static int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache, return SQLITE_OK; } -int64_t cweb_database_insert_impl(CWEB *cweb, const char *fmt, CWEB_VArgs args) +#endif // CWEB_ENABLE_DATABASE + +int64_t cweb_database_insert_impl(CWEB *cweb, char *fmt, CWEB_VArgs args) { +#ifdef CWEB_ENABLE_DATABASE sqlite3_stmt *stmt; int ret = sqlite3utils_prepare_and_bind_impl(cweb->dbcache, &stmt, fmt, args); if (ret != SQLITE_OK) @@ -13562,20 +1626,28 @@ int64_t cweb_database_insert_impl(CWEB *cweb, const char *fmt, CWEB_VArgs args) sqlite3_reset(stmt); return insert_id; +#else + return -1; +#endif } -CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, const char *fmt, CWEB_VArgs args) +CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, char *fmt, CWEB_VArgs args) { +#ifdef CWEB_ENABLE_DATABASE sqlite3_stmt *stmt; int ret = sqlite3utils_prepare_and_bind_impl(cweb->dbcache, &stmt, fmt, args); if (ret != SQLITE_OK) return (CWEB_QueryResult) { NULL }; return (CWEB_QueryResult) { stmt }; +#else + return (CWEB_QueryResult) { NULL }; +#endif } int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args) { +#ifdef CWEB_ENABLE_DATABASE if (res->handle == NULL) return -1; @@ -13661,19 +1733,30 @@ int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args) } return 1; +#else + (void) res; + (void) args; + return -1; +#endif } void cweb_free_query_result(CWEB_QueryResult *res) { +#ifdef CWEB_ENABLE_DATABASE if (res->handle) { sqlite3_reset(res->handle); res->handle = NULL; } +#else + (void) res; +#endif } int cweb_global_init(void) { - http_global_init(); + if (http_global_init()) + return -1; + return 0; } void cweb_global_free(void) @@ -13681,7 +1764,7 @@ void cweb_global_free(void) http_global_free(); } -CWEB *web_init(CWEB_String addr, uint16_t port) +CWEB *cweb_init(CWEB_String addr, uint16_t port) { // If set, allows logins and signups over HTTP, which is highly insecure. // This allows compiling the application without TLS when developing. @@ -13692,56 +1775,68 @@ CWEB *web_init(CWEB_String addr, uint16_t port) CWEB *cweb = malloc(sizeof(CWEB)); if (cweb == NULL) - return -1; + return NULL; cweb->pool_cap = 1<<20; cweb->pool = malloc(cweb->pool_cap); if (cweb->pool == NULL) { free(cweb); - return -1; + return NULL; } +#ifdef CWEB_ENABLE_TEMPLATE cweb->tpcache = template_cache_init(4); if (cweb->tpcache == NULL) { free(cweb->pool); free(cweb); - return -1; + return NULL; } +#endif cweb->session_storage = session_storage_init(1024); if (cweb->session_storage == NULL) { +#ifdef CWEB_ENABLE_TEMPLATE template_cache_free(cweb->tpcache); +#endif free(cweb->pool); free(cweb); - return -1; + return NULL; } cweb->server = http_server_init((HTTP_String) { addr.ptr, addr.len }, port); if (cweb->server == NULL) { session_storage_free(cweb->session_storage); +#ifdef CWEB_ENABLE_TEMPLATE template_cache_free(cweb->tpcache); +#endif free(cweb->pool); free(cweb); - return -1; + return NULL; } +#ifdef CWEB_ENABLE_DATABASE cweb->db = NULL; cweb->dbcache = NULL; +#endif cweb->allow_insecure_login = false; - return 0; + return cweb; } void cweb_free(CWEB *cweb) { http_server_free(cweb->server); session_storage_free(cweb->session_storage); +#ifdef CWEB_ENABLE_TEMPLATE template_cache_free(cweb->tpcache); +#endif +#ifdef CWEB_ENABLE_DATABASE if (cweb->db) { sqlite_cache_free(cweb->dbcache); sqlite3_close(cweb->db); } +#endif free(cweb); } @@ -13752,6 +1847,7 @@ void cweb_version(void) int cweb_enable_database(CWEB *cweb, CWEB_String file) { +#ifdef CWEB_ENABLE_DATABASE if (cweb->db != NULL) return -1; // Already enabled @@ -13768,23 +1864,21 @@ int cweb_enable_database(CWEB *cweb, CWEB_String file) return -1; } - char *schema_data; - long schema_size; - ret = load_file(file_copy, &schema_data, &schema_size); - if (ret < 0) { + LoadedFile *schema = load_file(file); + if (schema == NULL) { sqlite3_close(cweb->db); cweb->db = NULL; return -1; } - ret = sqlite3_exec(cweb->db, schema_data, NULL, NULL, NULL); + ret = sqlite3_exec(cweb->db, schema->data, NULL, NULL, NULL); if (ret != SQLITE_OK) { sqlite3_close(cweb->db); cweb->db = NULL; return -1; } - free(schema_data); + free_loaded_files(schema); cweb->dbcache = sqlite_cache_init(cweb->db, 5); if (cweb->dbcache == NULL) { @@ -13794,6 +1888,9 @@ int cweb_enable_database(CWEB *cweb, CWEB_String file) } return 0; +#else + return -1; +#endif } CWEB_Request *cweb_wait(CWEB *cweb) @@ -13801,13 +1898,15 @@ CWEB_Request *cweb_wait(CWEB *cweb) CWEB_Request *req = &cweb->req; int ret = http_server_wait(cweb->server, &req->req, &req->builder); - if (ret < 0) return NULL; + if (ret) return NULL; // Error or signal - HTTP_String sess_token = http_get_cookie(req->req, HTTP_STR("sess_token")); + HTTP_String session_token = http_get_cookie(req->req, HTTP_STR("sess_token")); + req->cweb = cweb; req->arena = (WL_Arena) { cweb->pool, cweb->pool_cap, 0 }; req->just_created_session = false; - req->sess = (CWEB_String) { sess_token.ptr, sess_token.len }; + req->sess = (CWEB_String) { session_token.ptr, session_token.len }; + if (find_session(cweb->session_storage, req->sess, &req->csrf, &req->user_id) < 0) { req->user_id = -1; req->sess = (CWEB_String) { NULL, 0 }; @@ -13824,7 +1923,11 @@ bool cweb_match_endpoint(CWEB_Request *req, CWEB_String str) CWEB_String cweb_get_param_s(CWEB_Request *req, CWEB_String name) { - HTTP_String res = http_get_param(req->req->body, + HTTP_String src = req->req->url.query; + if (req->req->method == HTTP_METHOD_POST) + src = req->req->body; + + HTTP_String res = http_get_param(src, (HTTP_String) { name.ptr, name.len }, req->arena.ptr + req->arena.cur, req->arena.len - req->arena.cur @@ -13836,7 +1939,11 @@ CWEB_String cweb_get_param_s(CWEB_Request *req, CWEB_String name) int cweb_get_param_i(CWEB_Request *req, CWEB_String name) { - return http_get_param_i(req->req->body, (HTTP_String) { name.ptr, name.len }); + HTTP_String src = req->req->url.query; + if (req->req->method == HTTP_METHOD_POST) + src = req->req->body; + + return http_get_param_i(src, (HTTP_String) { name.ptr, name.len }); } static int set_auth_cookie_if_necessary(CWEB_Request *req) @@ -13851,9 +1958,9 @@ static int set_auth_cookie_if_necessary(CWEB_Request *req) return 0; } -void cweb_respond(CWEB_Request *req, int status, CWEB_String content) +void cweb_respond_basic(CWEB_Request *req, int status, CWEB_String content) { - http_response_builder_status(req->builder, 200); + http_response_builder_status(req->builder, status); int ret = set_auth_cookie_if_necessary(req); if (ret < 0) { http_response_builder_undo(req->builder); @@ -13889,6 +1996,11 @@ static void append_to_output_s64(StaticOutputBuffer *out, int64_t n) // TODO } +static void append_to_output_f64(StaticOutputBuffer *out, double n) +{ + // TODO +} + static void append_to_output_ptr(StaticOutputBuffer *out, void *p) { // TODO @@ -13966,7 +2078,7 @@ static void evaluate_format(StaticOutputBuffer *out, CWEB_String format, CWEB_VA cur++; if (cur < len) { - assert(src[cur] == '}'); + ASSERT(src[cur] == '}'); cur++; } @@ -13976,7 +2088,7 @@ static void evaluate_format(StaticOutputBuffer *out, CWEB_String format, CWEB_VA } } else { - assert(src[cur-1] == '\\'); + ASSERT(src[cur-1] == '\\'); if (cur < len) { append_to_output(out, &src[cur], 1); cur++; @@ -13999,7 +2111,7 @@ CWEB_String cweb_format_impl(CWEB_Request *req, char *fmt, CWEB_VArgs args) return (CWEB_String) { out.dst, out.len }; } -void cweb_respond_redirect_impl(CWEB_Request *req, CWEB_String target) +void cweb_respond_redirect(CWEB_Request *req, CWEB_String target) { CWEB_String location_header = cweb_format(req, "Location: {}", target); if (location_header.len == 0) { @@ -14045,9 +2157,15 @@ int cweb_get_user_id(CWEB_Request *req) return req->user_id; } +CWEB_String cweb_get_csrf(CWEB_Request *req) +{ + return req->csrf; +} + ///////////////////////////////////////////////////////////////// // TEMPLATE //////////////////////////////////////////////////////////////// +#ifdef CWEB_ENABLE_TEMPLATE static TemplateCache *template_cache_init(int capacity_log2) { @@ -14068,22 +2186,10 @@ static void template_cache_free(TemplateCache *cache) free(cache); } -static unsigned long djb2(WL_String str) -{ - char *ptr = str.ptr; - char *end = str.ptr + str.len; - - unsigned long hash = 5381; - int c; - while (ptr < end && (c = *ptr++)) - hash = ((hash << 5) + hash) + c; // hash * 33 + c - return hash; -} - -static int lookup(TemplateCache *cache, WL_String path) +static int template_cache_lookup(TemplateCache *cache, WL_String path) { int mask = (1 << cache->capacity_log2) - 1; - int hash = djb2(path); + int hash = djb2(path.ptr, path.len); int i = hash & mask; int perturb = hash; for (;;) { @@ -14101,72 +2207,6 @@ static int lookup(TemplateCache *cache, WL_String path) return -1; } -typedef struct LoadedFile LoadedFile; -struct LoadedFile { - LoadedFile* next; - int len; - char data[]; -}; - -static LoadedFile *load_file(WL_String path) -{ - char buf[1<<10]; - if (path.len >= (int) sizeof(buf)) - return NULL; - memcpy(buf, path.ptr, path.len); - buf[path.len] = '\0'; - - FILE *stream = fopen(buf, "rb"); - if (stream == NULL) - return NULL; - - int ret = fseek(stream, 0, SEEK_END); - if (ret) { - fclose(stream); - return NULL; - } - - long tmp = ftell(stream); - if (tmp < 0 || tmp > INT_MAX) { - fclose(stream); - return NULL; - } - int len = (int) tmp; - - ret = fseek(stream, 0, SEEK_SET); - if (ret) { - fclose(stream); - return NULL; - } - - LoadedFile *result = malloc(sizeof(LoadedFile) + len + 1); - if (result == NULL) { - fclose(stream); - return NULL; - } - result->next = NULL; - result->len = len; - - int read_len = fread(result->data, 1, len+1, stream); - if (read_len != len || ferror(stream) || !feof(stream)) { - fclose(stream); - free(result); - return NULL; - } - - fclose(stream); - return result; -} - -static void free_loaded_files(LoadedFile *loaded_file) -{ - while (loaded_file) { - LoadedFile *next = loaded_file->next; - free(loaded_file); - loaded_file = next; - } -} - static int compile(WL_String path, WL_Program *program, WL_Arena *arena) { WL_Compiler *compiler = wl_compiler_init(arena); @@ -14180,7 +2220,7 @@ static int compile(WL_String path, WL_Program *program, WL_Arena *arena) for (int i = 0;; i++) { - LoadedFile *loaded_file = load_file(path); + LoadedFile *loaded_file = load_file((CWEB_String) { path.ptr, path.len }); if (loaded_file == NULL) { TRACE("Couldn't load file '%.*s'", path.len, path.ptr); free_loaded_files(loaded_file_head); @@ -14201,7 +2241,7 @@ static int compile(WL_String path, WL_Program *program, WL_Arena *arena) if (result.type == WL_ADD_LINK) break; - assert(result.type == WL_ADD_AGAIN); + ASSERT(result.type == WL_ADD_AGAIN); path = result.path; } @@ -14247,7 +2287,7 @@ static int query_routine(WL_Runtime *rt, SQLiteCache *dbcache) else if (wl_arg_str(rt, i, &str)) ret = sqlite3_bind_text(stmt, i, str.ptr, str.len, NULL); else { - assert(0); // TODO + ASSERT(0); // TODO } if (ret != SQLITE_OK) { @@ -14322,7 +2362,7 @@ static int query_routine(WL_Runtime *rt, SQLiteCache *dbcache) return 0; } -static void push_sysvar(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache, HTTP_String csrf, int user_id, int resource_id) +static void push_sysvar(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache, CWEB_String csrf, int user_id, int resource_id) { (void) dbcache; @@ -14362,7 +2402,7 @@ static int get_or_create_program(TemplateCache *cache, WL_String path, WL_Arena if (cache == NULL) return -1; - int i = lookup(cache, path); + int i = template_cache_lookup(cache, path); if (cache->pool[i].pathlen == -1) { WL_Program program; @@ -14386,13 +2426,22 @@ static int get_or_create_program(TemplateCache *cache, WL_String path, WL_Arena *program = cache->pool[i].program; return 0; } +#endif void cweb_respond_template(CWEB_Request *req, int status, CWEB_String template_file, int resource_id) { +#ifdef CWEB_ENABLE_TEMPLATE http_response_builder_status(req->builder, status); + int ret = set_auth_cookie_if_necessary(req); + if (ret < 0) { + http_response_builder_undo(req->builder); + http_response_builder_status(req->builder, -ret); + http_response_builder_done(req->builder); + return; + } WL_Program program; - int ret = get_or_create_program(req->cweb->tpcache, template_file, &req->arena, &program); + ret = get_or_create_program(req->cweb->tpcache, (WL_String) { template_file.ptr, template_file.len }, &req->arena, &program); if (ret < 0) { http_response_builder_undo(req->builder); http_response_builder_status(req->builder, 500); @@ -14443,4 +2492,8 @@ void cweb_respond_template(CWEB_Request *req, int status, CWEB_String template_f break; } } +#else + http_response_builder_status(req->builder, 500); + http_response_builder_done(req->builder); +#endif } diff --git a/cweb.h b/cweb.h index f12c7c4..a7352bb 100644 --- a/cweb.h +++ b/cweb.h @@ -10,6 +10,9 @@ #include #include +#define CWEB_ENABLE_DATABASE +#define CWEB_ENABLE_TEMPLATE + #define CWEB_STR(X) (CWEB_String) { (X), (int) sizeof(X)-1 } typedef struct { @@ -143,16 +146,16 @@ CWEB_VArg cweb_varg_from_ps (short *ps); CWEB_VArg cweb_varg_from_pi (int *pi); CWEB_VArg cweb_varg_from_pl (long *pl); CWEB_VArg cweb_varg_from_pll (long long *pll); -CWEB_VArg cweb_varg_from_psc (char *psc); -CWEB_VArg cweb_varg_from_pss (short *pss); -CWEB_VArg cweb_varg_from_psi (int *psi); -CWEB_VArg cweb_varg_from_psl (long *psl); -CWEB_VArg cweb_varg_from_psll (long long *psll); -CWEB_VArg cweb_varg_from_puc (char *puc); -CWEB_VArg cweb_varg_from_pus (short *pus); -CWEB_VArg cweb_varg_from_pui (int *pui); -CWEB_VArg cweb_varg_from_pul (long *pul); -CWEB_VArg cweb_varg_from_pull (long long *pull); +CWEB_VArg cweb_varg_from_psc (signed char *psc); +CWEB_VArg cweb_varg_from_pss (signed short *pss); +CWEB_VArg cweb_varg_from_psi (signed int *psi); +CWEB_VArg cweb_varg_from_psl (signed long *psl); +CWEB_VArg cweb_varg_from_psll (signed long long *psll); +CWEB_VArg cweb_varg_from_puc (unsigned char *puc); +CWEB_VArg cweb_varg_from_pus (unsigned short *pus); +CWEB_VArg cweb_varg_from_pui (unsigned int *pui); +CWEB_VArg cweb_varg_from_pul (unsigned long *pul); +CWEB_VArg cweb_varg_from_pull (unsigned long long *pull); CWEB_VArg cweb_varg_from_pf (float *pf); CWEB_VArg cweb_varg_from_pd (double *pd); CWEB_VArg cweb_varg_from_pb (bool *pb); @@ -230,51 +233,77 @@ void cweb_free(CWEB *cweb); int cweb_enable_database(CWEB *cweb, CWEB_String file); +// Pause execution until a request is available. +// TODO: When does this function return NULL? CWEB_Request *cweb_wait(CWEB *cweb); -////////////////////////////////////// -// Session +// Returns true iff the request matches the specified endpoint +bool cweb_match_endpoint(CWEB_Request *req, CWEB_String str); -CWEB_String cweb_get_session_csrf(CWEB_Request *req); -int cweb_get_session_user_id(CWEB_Request *req); -int cweb_set_session_user_id(CWEB_Request *req, int user_id); +// Returns the CSRF token associated to the current session +CWEB_String cweb_get_csrf(CWEB_Request *req); -////////////////////////////////////// -// Request +// Returns the user ID for the current session, or -1 if there is no session +int cweb_get_user_id(CWEB_Request *req); -bool cweb_match_endpoint(CWEB_Request *req, CWEB_String str); +// Sets the user ID for the current session (it must be a positive integer). +// If the ID is -1, the session is deleted. +int cweb_set_user_id(CWEB_Request *req, int user_id); + +// Returns the request parameter with the specified name +// If the request uses POST, the parameter is taken from the body, +// else it's taken from the URL. If the parameter is not present, +// an empty string is returned. CWEB_String cweb_get_param_s(CWEB_Request *req, CWEB_String name); -int cweb_get_param_i(CWEB_Request *req, CWEB_String name); -////////////////////////////////////// -// Response +// Like cweb_get_param_s, but also parser the argument as an integer. +// If parsing fails or the parameter is missing, -1 is returned. +int cweb_get_param_i(CWEB_Request *req, CWEB_String name); +// Create a string by evaluating a format. Memory is allocated from the arena of the request. +// If the arena is full, an empty string is returned. CWEB_String cweb_format_impl(CWEB_Request *req, char *fmt, CWEB_VArgs args); + +// Helper #define cweb_format(req, fmt, ...) cweb_format_impl((req), (fmt), CWEB_VARGS(__VA_ARGS__)) +// Responds to the specified request with the given status code and content void cweb_respond_basic(CWEB_Request *req, int status, CWEB_String content); + +// Responds to the request by redirecting the client to the given target void cweb_respond_redirect(CWEB_Request *req, CWEB_String target); + +// Responds to the request by evaluating a WL template file void cweb_respond_template(CWEB_Request *req, int status, CWEB_String template_file, int resource_id); -////////////////////////////////////// -// Database - -typedef struct { - void *handle; -} CWEB_QueryResult; - -int64_t cweb_database_insert_impl(CWEB *cweb, const char *fmt, CWEB_VArgs args); -CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, const char *fmt, CWEB_VArgs args); -int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args); -void cweb_free_query_result(CWEB_QueryResult *res); +// Evaluates an SQL INSERT statement and returns the ID of the last inserted row. On error -1 is returned +int64_t cweb_database_insert_impl(CWEB *cweb, char *fmt, CWEB_VArgs args); +// Helper #define cweb_database_insert(cweb, fmt, ...) cweb_database_insert_impl((cweb), (fmt), CWEB_VARGS(__VA_ARGS__)) + +// Iterator over database rows +typedef struct { void *handle; } CWEB_QueryResult; + +// Evaluates an SQL SELECT statement, returning a scanner over the returned rows. +// You don't have to check for errors with this function +CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, char *fmt, CWEB_VArgs args); + +// Helper #define cweb_database_select(cweb, fmt, ...) cweb_database_select_impl((cweb), (fmt), CWEB_VARGS(__VA_ARGS__)) -#define cweb_next_query_row(res, ...) cweb_next_query_row_impl((res), CWEB_VARGS(__VA_ARGS__)) -////////////////////////////////////// -// Password +// Returns the next row from the query result iterator. +int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args); +// Helper +#define cweb_next_query_row(res, ...) cweb_next_query_row_impl((res), CWEB_VARGS(__VA_ARGS__)) + +// Frees the result of a database query +void cweb_free_query_result(CWEB_QueryResult *res); + +// Calculates the bcrypt hash of the specified password int cweb_hash_password(char *pass, int passlen, int cost, CWEB_PasswordHash *hash); + +// Checks whether the password matches the given hash int cweb_check_password(char *pass, int passlen, CWEB_PasswordHash hash); #endif // CWEB_AMALGAMATION diff --git a/3p/chttp.c b/demo/chttp.c similarity index 99% rename from 3p/chttp.c rename to demo/chttp.c index 2bee284..364ac39 100644 --- a/3p/chttp.c +++ b/demo/chttp.c @@ -1648,15 +1648,10 @@ HTTP_String http_get_param(HTTP_String body, HTTP_String str, char *mem, int cap return HTTP_STR(""); } -static bool is_digit(char c) -{ - return c >= '0' && c <= '9'; -} - int http_get_param_i(HTTP_String body, HTTP_String str) { char buf[128]; - HTTP_String out = http_get_param(body, str, buf, SIZEOF(buf)); + HTTP_String out = http_get_param(body, str, buf, (int) sizeof(buf)); if (out.len == 0 || !is_digit(out.ptr[0])) return -1; diff --git a/3p/chttp.h b/demo/chttp.h similarity index 100% rename from 3p/chttp.h rename to demo/chttp.h diff --git a/demo/main.c b/demo/main.c index 4985e36..868354e 100644 --- a/demo/main.c +++ b/demo/main.c @@ -140,7 +140,7 @@ static int user_exists(CWEB *cweb, CWEB_String name, CWEB_String pass) static void endpoint_api_login(CWEB *cweb, CWEB_Request *req) { - if (cweb_get_session_user_id(req) != -1) { + if (cweb_get_user_id(req) != -1) { cweb_respond_redirect(req, CWEB_STR("/index")); return; } @@ -157,7 +157,7 @@ static void endpoint_api_login(CWEB *cweb, CWEB_Request *req) cweb_respond_basic(req, 200, CWEB_STR("
Invalid credentials
")); return; } - if (cweb_set_session_user_id(req, ret) < 0) { + if (cweb_set_user_id(req, ret) < 0) { // TODO } @@ -166,7 +166,7 @@ static void endpoint_api_login(CWEB *cweb, CWEB_Request *req) static void endpoint_api_signup(CWEB *cweb, CWEB_Request *req) { - if (cweb_get_session_user_id(req) != -1) { + if (cweb_get_user_id(req) != -1) { cweb_respond_redirect(req, CWEB_STR("/index")); return; } @@ -199,20 +199,22 @@ static void endpoint_api_signup(CWEB *cweb, CWEB_Request *req) cweb_respond_basic(req, 400, CWEB_STR("
Internal error
")); return; } - cweb_set_session_user_id(req, insert_id); + cweb_set_user_id(req, insert_id); cweb_respond_redirect(req, CWEB_STR("/index")); } static void endpoint_api_logout(CWEB *cweb, CWEB_Request *req) { - cweb_set_session_user_id(req, -1); + (void) cweb; + + cweb_set_user_id(req, -1); cweb_respond_redirect(req, CWEB_STR("/index")); } static void endpoint_api_post(CWEB *cweb, CWEB_Request *req) { - int user_id = cweb_get_session_user_id(req); + int user_id = cweb_get_user_id(req); if (user_id == -1) { cweb_respond_redirect(req, CWEB_STR("/index")); return; @@ -223,7 +225,7 @@ static void endpoint_api_post(CWEB *cweb, CWEB_Request *req) CWEB_String content = cweb_get_param_s(req, CWEB_STR("content")); CWEB_String csrf = cweb_get_param_s(req, CWEB_STR("csrf")); - if (!cweb_streq(cweb_get_session_csrf(req), csrf)) { + if (!cweb_streq(cweb_get_csrf(req), csrf)) { cweb_respond_basic(req, 400, CWEB_STR("Invalid request")); return; } @@ -264,7 +266,7 @@ static void endpoint_api_post(CWEB *cweb, CWEB_Request *req) static void endpoint_api_comment(CWEB *cweb, CWEB_Request *req) { - int user_id = cweb_get_session_user_id(req); + int user_id = cweb_get_user_id(req); if (user_id == -1) { cweb_respond_basic(req, 200, CWEB_STR("
You are not logged in
")); return; @@ -275,7 +277,7 @@ static void endpoint_api_comment(CWEB *cweb, CWEB_Request *req) CWEB_String content = cweb_get_param_s(req, CWEB_STR("content")); CWEB_String csrf2 = cweb_get_param_s(req, CWEB_STR("csrf")); - if (!cweb_streq(cweb_get_session_csrf(req), csrf2)) { + if (!cweb_streq(cweb_get_csrf(req), csrf2)) { cweb_respond_basic(req, 200, CWEB_STR("
Invalid request
")); return; } @@ -299,12 +301,16 @@ static void endpoint_api_comment(CWEB *cweb, CWEB_Request *req) static void endpoint_index(CWEB *cweb, CWEB_Request *req) { + (void) cweb; + cweb_respond_template(req, 200, CWEB_STR("pages/index.wl"), -1); } static void endpoint_write(CWEB *cweb, CWEB_Request *req) { - if (cweb_get_session_user_id(req) == -1) { + (void) cweb; + + if (cweb_get_user_id(req) == -1) { cweb_respond_redirect(req, CWEB_STR("/index")); return; } @@ -314,7 +320,9 @@ static void endpoint_write(CWEB *cweb, CWEB_Request *req) static void endpoint_login(CWEB *cweb, CWEB_Request *req) { - if (cweb_get_session_user_id(req) != -1) { + (void) cweb; + + if (cweb_get_user_id(req) != -1) { cweb_respond_redirect(req, CWEB_STR("/index")); return; } @@ -324,7 +332,9 @@ static void endpoint_login(CWEB *cweb, CWEB_Request *req) static void endpoint_signup(CWEB *cweb, CWEB_Request *req) { - if (cweb_get_session_user_id(req) != -1) { + (void) cweb; + + if (cweb_get_user_id(req) != -1) { cweb_respond_redirect(req, CWEB_STR("/index")); return; } @@ -340,8 +350,7 @@ static void endpoint_post(CWEB *cweb, CWEB_Request *req) return; } - CWEB_QueryResult res = cweb_database_select(cweb, - "SELECT COUNT(*) FROM Posts WHERE id=?", post_id); + CWEB_QueryResult res = cweb_database_select(cweb, "SELECT COUNT(*) FROM Posts WHERE id=?", post_id); int num; if (cweb_next_query_row(&res, &num) != 1) { @@ -366,6 +375,8 @@ static void endpoint_post(CWEB *cweb, CWEB_Request *req) static void endpoint_fallback(CWEB *cweb, CWEB_Request *req) { + (void) cweb; + cweb_respond_template(req, 404, CWEB_STR("pages/notfound.wl"), -1); } diff --git a/3p/wl.c b/demo/wl.c similarity index 100% rename from 3p/wl.c rename to demo/wl.c diff --git a/3p/wl.h b/demo/wl.h similarity index 100% rename from 3p/wl.h rename to demo/wl.h diff --git a/3p/crypt_blowfish.c b/src/crypt_blowfish.c similarity index 100% rename from 3p/crypt_blowfish.c rename to src/crypt_blowfish.c diff --git a/3p/crypt_blowfish.h b/src/crypt_blowfish.h similarity index 100% rename from 3p/crypt_blowfish.h rename to src/crypt_blowfish.h diff --git a/src/main.c b/src/main.c index 6419e77..9a5fe3a 100644 --- a/src/main.c +++ b/src/main.c @@ -1,7 +1,4 @@ -#include "sqlite3.h" -#include "wl.h" -#include "chttp.h" -#include "cweb.h" +#include #ifdef __linux__ #include @@ -12,6 +9,25 @@ #include #endif +#ifdef CWEB_ENABLE_DATABASE +#include "sqlite3.h" +#endif + +#ifdef CWEB_ENABLE_TEMPLATE +#include "wl.h" +#endif + +#include "chttp.h" +#include "cweb.h" + +#define MIN(X, Y) ((X) < (Y) ? (X) : (Y)) +#define MAX(X, Y) ((X) > (Y) ? (X) : (Y)) +#define ASSERT(X) {if (!(X)) { __builtin_trap(); }} +#define TRACE(X, ...) ((void) 0) +#define SIZEOF(X) (int) (sizeof(X)/sizeof((X)[0])) + +#ifdef CWEB_ENABLE_TEMPLATE + typedef struct { char path[1<<8]; int pathlen; @@ -27,12 +43,16 @@ typedef struct { static TemplateCache *template_cache_init(int capacity_log2); static void template_cache_free(TemplateCache *cache); +#endif + typedef struct SessionStorage SessionStorage; + +#ifdef CWEB_ENABLE_DATABASE + typedef struct SQLiteCache SQLiteCache; static SQLiteCache* sqlite_cache_init(sqlite3 *db, int capacity_log2); static void sqlite_cache_free(SQLiteCache *cache); -static sqlite3* sqlite_cache_getdb(SQLiteCache *cache); static int sqlite3utils_prepare(SQLiteCache *cache, sqlite3_stmt **pstmt, char *fmt, int fmtlen); @@ -43,26 +63,7 @@ static int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache, #define sqlite3utils_prepare_and_bind(cache, pstmt, fmt, ...) \ sqlite3utils_prepare_and_bind_impl((cache), (pstmt), (fmt), VARGS(__VA_ARGS__)) -struct CWEB { - - HTTP_Server *server; - int pool_cap; - char *pool; - - // Login - SessionStorage *session_storage; - - // Database - sqlite3 *db; - SQLiteCache *dbcache; - - // Template - TemplateCache *tpcache; - - bool allow_insecure_login; - - CWEB_Request req; -}; +#endif struct CWEB_Request { @@ -80,6 +81,29 @@ struct CWEB_Request { CWEB_String csrf; }; +struct CWEB { + + HTTP_Server *server; + int pool_cap; + char *pool; + + // Login + SessionStorage *session_storage; + +#ifdef CWEB_ENABLE_DATABASE + sqlite3 *db; + SQLiteCache *dbcache; +#endif + +#ifdef CWEB_ENABLE_TEMPLATE + TemplateCache *tpcache; +#endif + + bool allow_insecure_login; + + CWEB_Request req; +}; + /////////////////////////// bool cweb_streq(CWEB_String a, CWEB_String b) @@ -118,22 +142,94 @@ CWEB_VArg cweb_varg_from_ps (short *ps) { return (CWEB_VArg) { CWEB_VA CWEB_VArg cweb_varg_from_pi (int *pi) { return (CWEB_VArg) { CWEB_VARG_TYPE_PI, .pi=pi }; } CWEB_VArg cweb_varg_from_pl (long *pl) { return (CWEB_VArg) { CWEB_VARG_TYPE_PL, .pl=pl }; } CWEB_VArg cweb_varg_from_pll (long long *pll) { return (CWEB_VArg) { CWEB_VARG_TYPE_PLL, .pll=pll }; } -CWEB_VArg cweb_varg_from_psc (char *psc) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSC, .psc=psc }; } -CWEB_VArg cweb_varg_from_pss (short *pss) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSS, .pss=pss }; } -CWEB_VArg cweb_varg_from_psi (int *psi) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSI, .psi=psi }; } -CWEB_VArg cweb_varg_from_psl (long *psl) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSL, .psl=psl }; } -CWEB_VArg cweb_varg_from_psll (long long *psll) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSLL, .psll=psll }; } -CWEB_VArg cweb_varg_from_puc (char *puc) { return (CWEB_VArg) { CWEB_VARG_TYPE_PUC, .puc=puc }; } -CWEB_VArg cweb_varg_from_pus (short *pus) { return (CWEB_VArg) { CWEB_VARG_TYPE_PUS, .pus=pus }; } -CWEB_VArg cweb_varg_from_pui (int *pui) { return (CWEB_VArg) { CWEB_VARG_TYPE_PUI, .pui=pui }; } -CWEB_VArg cweb_varg_from_pul (long *pul) { return (CWEB_VArg) { CWEB_VARG_TYPE_PUL, .pul=pul }; } -CWEB_VArg cweb_varg_from_pull (long long *pull) { return (CWEB_VArg) { CWEB_VARG_TYPE_PULL, .pull=pull }; } +CWEB_VArg cweb_varg_from_psc (signed char *psc) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSC, .psc=psc }; } +CWEB_VArg cweb_varg_from_pss (signed short *pss) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSS, .pss=pss }; } +CWEB_VArg cweb_varg_from_psi (signed int *psi) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSI, .psi=psi }; } +CWEB_VArg cweb_varg_from_psl (signed long *psl) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSL, .psl=psl }; } +CWEB_VArg cweb_varg_from_psll (signed long long *psll) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSLL, .psll=psll }; } +CWEB_VArg cweb_varg_from_puc (unsigned char *puc) { return (CWEB_VArg) { CWEB_VARG_TYPE_PUC, .puc=puc }; } +CWEB_VArg cweb_varg_from_pus (unsigned short *pus) { return (CWEB_VArg) { CWEB_VARG_TYPE_PUS, .pus=pus }; } +CWEB_VArg cweb_varg_from_pui (unsigned int *pui) { return (CWEB_VArg) { CWEB_VARG_TYPE_PUI, .pui=pui }; } +CWEB_VArg cweb_varg_from_pul (unsigned long *pul) { return (CWEB_VArg) { CWEB_VARG_TYPE_PUL, .pul=pul }; } +CWEB_VArg cweb_varg_from_pull (unsigned long long *pull) { return (CWEB_VArg) { CWEB_VARG_TYPE_PULL, .pull=pull }; } CWEB_VArg cweb_varg_from_pf (float *pf) { return (CWEB_VArg) { CWEB_VARG_TYPE_PF, .pf=pf }; } CWEB_VArg cweb_varg_from_pd (double *pd) { return (CWEB_VArg) { CWEB_VARG_TYPE_PD, .pd=pd }; } CWEB_VArg cweb_varg_from_pb (bool *pb) { return (CWEB_VArg) { CWEB_VARG_TYPE_PB, .pb=pb }; } CWEB_VArg cweb_varg_from_pstr (CWEB_String *pstr) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSTR, .pstr=pstr }; } CWEB_VArg cweb_varg_from_phash(CWEB_PasswordHash *phash) { return (CWEB_VArg) { CWEB_VARG_TYPE_PHASH, .phash=phash }; } +///////////////////////////////////////////////////////////////// +// FILE SYSTEM +//////////////////////////////////////////////////////////////// + +typedef struct LoadedFile LoadedFile; +struct LoadedFile { + LoadedFile* next; + int len; + char data[]; +}; + +static LoadedFile *load_file(CWEB_String path) +{ + char buf[1<<10]; + if (path.len >= (int) sizeof(buf)) + return NULL; + memcpy(buf, path.ptr, path.len); + buf[path.len] = '\0'; + + FILE *stream = fopen(buf, "rb"); + if (stream == NULL) + return NULL; + + int ret = fseek(stream, 0, SEEK_END); + if (ret) { + fclose(stream); + return NULL; + } + + long tmp = ftell(stream); + if (tmp < 0 || tmp > INT_MAX) { + fclose(stream); + return NULL; + } + int len = (int) tmp; + + ret = fseek(stream, 0, SEEK_SET); + if (ret) { + fclose(stream); + return NULL; + } + + LoadedFile *result = malloc(sizeof(LoadedFile) + len + 1); + if (result == NULL) { + fclose(stream); + return NULL; + } + result->next = NULL; + result->len = len; + + int read_len = fread(result->data, 1, len+1, stream); + if (read_len != len || ferror(stream) || !feof(stream)) { + fclose(stream); + free(result); + return NULL; + } + + result->data[len] = '\0'; + + fclose(stream); + return result; +} + +static void free_loaded_files(LoadedFile *loaded_file) +{ + while (loaded_file) { + LoadedFile *next = loaded_file->next; + free(loaded_file); + loaded_file = next; + } +} + ///////////////////////////////////////////////////////////////// // RANDOM //////////////////////////////////////////////////////////////// @@ -242,7 +338,7 @@ static int hex_digit_to_int(char c) static void unpack_token(char *src, int srclen, char *dst, int dstlen) { - assert(2 * srclen == dstlen); + ASSERT(2 * srclen == dstlen); for (int i = 0; i < srclen; i++) { static const char table[] = "0123456789abcdef"; @@ -258,7 +354,7 @@ static int pack_token(char *src, int srclen, char *dst, int dstlen) if (srclen & 1) return -1; - assert(srclen == 2 * dstlen); + ASSERT(srclen == 2 * dstlen); for (int i = 0; i < srclen; i += 2) { int high = src[i+0]; @@ -381,7 +477,7 @@ static int delete_session(SessionStorage *storage, CWEB_String sess) Session *found = lookup_session_slot(storage, sess, false); if (found == NULL) return false; - assert(found->user >= 0); + ASSERT(found->user >= 0); found->user = -2; storage->count--; return 0; @@ -392,7 +488,7 @@ static int find_session(SessionStorage *storage, CWEB_String sess, CWEB_String * Session *found = lookup_session_slot(storage, sess, false); if (found == NULL) return -1; - assert(found->user >= 0); + ASSERT(found->user >= 0); *pcsrf = (CWEB_String) { found->csrf, CSRF_TOKEN_SIZE }; *puser = found->user; return 0; @@ -402,6 +498,8 @@ static int find_session(SessionStorage *storage, CWEB_String sess, CWEB_String * // DATABASE //////////////////////////////////////////////////////////////// +#ifdef CWEB_ENABLE_DATABASE + typedef struct { char *str; int len; @@ -443,11 +541,6 @@ static void sqlite_cache_free(SQLiteCache *cache) free(cache); } -static sqlite3 *sqlite_cache_getdb(SQLiteCache *cache) -{ - return cache->db; -} - static unsigned long djb2(char *src, int len) { char *ptr = src; @@ -460,7 +553,7 @@ static unsigned long djb2(char *src, int len) return hash; } -static int lookup(SQLiteCache *cache, char *fmt, int fmtlen) +static int sqlite_cache_lookup(SQLiteCache *cache, char *fmt, int fmtlen) { int mask = (1 << cache->capacity_log2) - 1; int hash = djb2(fmt, fmtlen); @@ -486,7 +579,7 @@ static int sqlite3utils_prepare(SQLiteCache *cache, sqlite3_stmt **pstmt, char * if (fmtlen < 0) fmtlen = strlen(fmt); - int i = lookup(cache, fmt, fmtlen); + int i = sqlite_cache_lookup(cache, fmt, fmtlen); if (cache->items[i].stmt == NULL) { sqlite3_stmt *stmt; @@ -543,6 +636,9 @@ static int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache, case CWEB_VARG_TYPE_D : ret = sqlite3_bind_double(stmt, i+1, arg.d); break; case CWEB_VARG_TYPE_B : ret = sqlite3_bind_int (stmt, i+1, arg.b); break; case CWEB_VARG_TYPE_STR: ret = sqlite3_bind_text (stmt, i+1, arg.str.ptr, arg.str.len, NULL); break; + default: + // TODO + break; } if (ret != SQLITE_OK) { sqlite3_reset(stmt); @@ -554,8 +650,11 @@ static int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache, return SQLITE_OK; } -int64_t cweb_database_insert_impl(CWEB *cweb, const char *fmt, CWEB_VArgs args) +#endif // CWEB_ENABLE_DATABASE + +int64_t cweb_database_insert_impl(CWEB *cweb, char *fmt, CWEB_VArgs args) { +#ifdef CWEB_ENABLE_DATABASE sqlite3_stmt *stmt; int ret = sqlite3utils_prepare_and_bind_impl(cweb->dbcache, &stmt, fmt, args); if (ret != SQLITE_OK) @@ -575,20 +674,28 @@ int64_t cweb_database_insert_impl(CWEB *cweb, const char *fmt, CWEB_VArgs args) sqlite3_reset(stmt); return insert_id; +#else + return -1; +#endif } -CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, const char *fmt, CWEB_VArgs args) +CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, char *fmt, CWEB_VArgs args) { +#ifdef CWEB_ENABLE_DATABASE sqlite3_stmt *stmt; int ret = sqlite3utils_prepare_and_bind_impl(cweb->dbcache, &stmt, fmt, args); if (ret != SQLITE_OK) return (CWEB_QueryResult) { NULL }; return (CWEB_QueryResult) { stmt }; +#else + return (CWEB_QueryResult) { NULL }; +#endif } int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args) { +#ifdef CWEB_ENABLE_DATABASE if (res->handle == NULL) return -1; @@ -674,19 +781,30 @@ int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args) } return 1; +#else + (void) res; + (void) args; + return -1; +#endif } void cweb_free_query_result(CWEB_QueryResult *res) { +#ifdef CWEB_ENABLE_DATABASE if (res->handle) { sqlite3_reset(res->handle); res->handle = NULL; } +#else + (void) res; +#endif } int cweb_global_init(void) { - http_global_init(); + if (http_global_init()) + return -1; + return 0; } void cweb_global_free(void) @@ -694,7 +812,7 @@ void cweb_global_free(void) http_global_free(); } -CWEB *web_init(CWEB_String addr, uint16_t port) +CWEB *cweb_init(CWEB_String addr, uint16_t port) { // If set, allows logins and signups over HTTP, which is highly insecure. // This allows compiling the application without TLS when developing. @@ -705,56 +823,68 @@ CWEB *web_init(CWEB_String addr, uint16_t port) CWEB *cweb = malloc(sizeof(CWEB)); if (cweb == NULL) - return -1; + return NULL; cweb->pool_cap = 1<<20; cweb->pool = malloc(cweb->pool_cap); if (cweb->pool == NULL) { free(cweb); - return -1; + return NULL; } +#ifdef CWEB_ENABLE_TEMPLATE cweb->tpcache = template_cache_init(4); if (cweb->tpcache == NULL) { free(cweb->pool); free(cweb); - return -1; + return NULL; } +#endif cweb->session_storage = session_storage_init(1024); if (cweb->session_storage == NULL) { +#ifdef CWEB_ENABLE_TEMPLATE template_cache_free(cweb->tpcache); +#endif free(cweb->pool); free(cweb); - return -1; + return NULL; } cweb->server = http_server_init((HTTP_String) { addr.ptr, addr.len }, port); if (cweb->server == NULL) { session_storage_free(cweb->session_storage); +#ifdef CWEB_ENABLE_TEMPLATE template_cache_free(cweb->tpcache); +#endif free(cweb->pool); free(cweb); - return -1; + return NULL; } +#ifdef CWEB_ENABLE_DATABASE cweb->db = NULL; cweb->dbcache = NULL; +#endif cweb->allow_insecure_login = false; - return 0; + return cweb; } void cweb_free(CWEB *cweb) { http_server_free(cweb->server); session_storage_free(cweb->session_storage); +#ifdef CWEB_ENABLE_TEMPLATE template_cache_free(cweb->tpcache); +#endif +#ifdef CWEB_ENABLE_DATABASE if (cweb->db) { sqlite_cache_free(cweb->dbcache); sqlite3_close(cweb->db); } +#endif free(cweb); } @@ -765,6 +895,7 @@ void cweb_version(void) int cweb_enable_database(CWEB *cweb, CWEB_String file) { +#ifdef CWEB_ENABLE_DATABASE if (cweb->db != NULL) return -1; // Already enabled @@ -781,23 +912,21 @@ int cweb_enable_database(CWEB *cweb, CWEB_String file) return -1; } - char *schema_data; - long schema_size; - ret = load_file(file_copy, &schema_data, &schema_size); - if (ret < 0) { + LoadedFile *schema = load_file(file); + if (schema == NULL) { sqlite3_close(cweb->db); cweb->db = NULL; return -1; } - ret = sqlite3_exec(cweb->db, schema_data, NULL, NULL, NULL); + ret = sqlite3_exec(cweb->db, schema->data, NULL, NULL, NULL); if (ret != SQLITE_OK) { sqlite3_close(cweb->db); cweb->db = NULL; return -1; } - free(schema_data); + free_loaded_files(schema); cweb->dbcache = sqlite_cache_init(cweb->db, 5); if (cweb->dbcache == NULL) { @@ -807,6 +936,9 @@ int cweb_enable_database(CWEB *cweb, CWEB_String file) } return 0; +#else + return -1; +#endif } CWEB_Request *cweb_wait(CWEB *cweb) @@ -814,12 +946,15 @@ CWEB_Request *cweb_wait(CWEB *cweb) CWEB_Request *req = &cweb->req; int ret = http_server_wait(cweb->server, &req->req, &req->builder); - if (ret < 0) return -1; + if (ret) return NULL; // Error or signal + HTTP_String session_token = http_get_cookie(req->req, HTTP_STR("sess_token")); + + req->cweb = cweb; req->arena = (WL_Arena) { cweb->pool, cweb->pool_cap, 0 }; - req->just_created_session = false; - req->sess = http_get_cookie(req->req, HTTP_STR("sess_token")); + req->sess = (CWEB_String) { session_token.ptr, session_token.len }; + if (find_session(cweb->session_storage, req->sess, &req->csrf, &req->user_id) < 0) { req->user_id = -1; req->sess = (CWEB_String) { NULL, 0 }; @@ -871,9 +1006,9 @@ static int set_auth_cookie_if_necessary(CWEB_Request *req) return 0; } -void cweb_respond(CWEB_Request *req, int status, CWEB_String content) +void cweb_respond_basic(CWEB_Request *req, int status, CWEB_String content) { - http_response_builder_status(req->builder, 200); + http_response_builder_status(req->builder, status); int ret = set_auth_cookie_if_necessary(req); if (ret < 0) { http_response_builder_undo(req->builder); @@ -909,6 +1044,11 @@ static void append_to_output_s64(StaticOutputBuffer *out, int64_t n) // TODO } +static void append_to_output_f64(StaticOutputBuffer *out, double n) +{ + // TODO +} + static void append_to_output_ptr(StaticOutputBuffer *out, void *p) { // TODO @@ -986,7 +1126,7 @@ static void evaluate_format(StaticOutputBuffer *out, CWEB_String format, CWEB_VA cur++; if (cur < len) { - assert(src[cur] == '}'); + ASSERT(src[cur] == '}'); cur++; } @@ -996,7 +1136,7 @@ static void evaluate_format(StaticOutputBuffer *out, CWEB_String format, CWEB_VA } } else { - assert(src[cur-1] == '\\'); + ASSERT(src[cur-1] == '\\'); if (cur < len) { append_to_output(out, &src[cur], 1); cur++; @@ -1065,9 +1205,15 @@ int cweb_get_user_id(CWEB_Request *req) return req->user_id; } +CWEB_String cweb_get_csrf(CWEB_Request *req) +{ + return req->csrf; +} + ///////////////////////////////////////////////////////////////// // TEMPLATE //////////////////////////////////////////////////////////////// +#ifdef CWEB_ENABLE_TEMPLATE static TemplateCache *template_cache_init(int capacity_log2) { @@ -1088,22 +1234,10 @@ static void template_cache_free(TemplateCache *cache) free(cache); } -static unsigned long djb2(WL_String str) -{ - char *ptr = str.ptr; - char *end = str.ptr + str.len; - - unsigned long hash = 5381; - int c; - while (ptr < end && (c = *ptr++)) - hash = ((hash << 5) + hash) + c; // hash * 33 + c - return hash; -} - -static int lookup(TemplateCache *cache, WL_String path) +static int template_cache_lookup(TemplateCache *cache, WL_String path) { int mask = (1 << cache->capacity_log2) - 1; - int hash = djb2(path); + int hash = djb2(path.ptr, path.len); int i = hash & mask; int perturb = hash; for (;;) { @@ -1121,72 +1255,6 @@ static int lookup(TemplateCache *cache, WL_String path) return -1; } -typedef struct LoadedFile LoadedFile; -struct LoadedFile { - LoadedFile* next; - int len; - char data[]; -}; - -static LoadedFile *load_file(WL_String path) -{ - char buf[1<<10]; - if (path.len >= (int) sizeof(buf)) - return NULL; - memcpy(buf, path.ptr, path.len); - buf[path.len] = '\0'; - - FILE *stream = fopen(buf, "rb"); - if (stream == NULL) - return NULL; - - int ret = fseek(stream, 0, SEEK_END); - if (ret) { - fclose(stream); - return NULL; - } - - long tmp = ftell(stream); - if (tmp < 0 || tmp > INT_MAX) { - fclose(stream); - return NULL; - } - int len = (int) tmp; - - ret = fseek(stream, 0, SEEK_SET); - if (ret) { - fclose(stream); - return NULL; - } - - LoadedFile *result = malloc(sizeof(LoadedFile) + len + 1); - if (result == NULL) { - fclose(stream); - return NULL; - } - result->next = NULL; - result->len = len; - - int read_len = fread(result->data, 1, len+1, stream); - if (read_len != len || ferror(stream) || !feof(stream)) { - fclose(stream); - free(result); - return NULL; - } - - fclose(stream); - return result; -} - -static void free_loaded_files(LoadedFile *loaded_file) -{ - while (loaded_file) { - LoadedFile *next = loaded_file->next; - free(loaded_file); - loaded_file = next; - } -} - static int compile(WL_String path, WL_Program *program, WL_Arena *arena) { WL_Compiler *compiler = wl_compiler_init(arena); @@ -1200,7 +1268,7 @@ static int compile(WL_String path, WL_Program *program, WL_Arena *arena) for (int i = 0;; i++) { - LoadedFile *loaded_file = load_file(path); + LoadedFile *loaded_file = load_file((CWEB_String) { path.ptr, path.len }); if (loaded_file == NULL) { TRACE("Couldn't load file '%.*s'", path.len, path.ptr); free_loaded_files(loaded_file_head); @@ -1221,7 +1289,7 @@ static int compile(WL_String path, WL_Program *program, WL_Arena *arena) if (result.type == WL_ADD_LINK) break; - assert(result.type == WL_ADD_AGAIN); + ASSERT(result.type == WL_ADD_AGAIN); path = result.path; } @@ -1267,7 +1335,7 @@ static int query_routine(WL_Runtime *rt, SQLiteCache *dbcache) else if (wl_arg_str(rt, i, &str)) ret = sqlite3_bind_text(stmt, i, str.ptr, str.len, NULL); else { - assert(0); // TODO + ASSERT(0); // TODO } if (ret != SQLITE_OK) { @@ -1382,7 +1450,7 @@ static int get_or_create_program(TemplateCache *cache, WL_String path, WL_Arena if (cache == NULL) return -1; - int i = lookup(cache, path); + int i = template_cache_lookup(cache, path); if (cache->pool[i].pathlen == -1) { WL_Program program; @@ -1406,9 +1474,11 @@ static int get_or_create_program(TemplateCache *cache, WL_String path, WL_Arena *program = cache->pool[i].program; return 0; } +#endif void cweb_respond_template(CWEB_Request *req, int status, CWEB_String template_file, int resource_id) { +#ifdef CWEB_ENABLE_TEMPLATE http_response_builder_status(req->builder, status); int ret = set_auth_cookie_if_necessary(req); if (ret < 0) { @@ -1419,7 +1489,7 @@ void cweb_respond_template(CWEB_Request *req, int status, CWEB_String template_f } WL_Program program; - int ret = get_or_create_program(req->cweb->tpcache, template_file, &req->arena, &program); + ret = get_or_create_program(req->cweb->tpcache, (WL_String) { template_file.ptr, template_file.len }, &req->arena, &program); if (ret < 0) { http_response_builder_undo(req->builder); http_response_builder_status(req->builder, 500); @@ -1470,4 +1540,8 @@ void cweb_respond_template(CWEB_Request *req, int status, CWEB_String template_f break; } } +#else + http_response_builder_status(req->builder, 500); + http_response_builder_done(req->builder); +#endif } diff --git a/src/main.h b/src/main.h index 568ff47..0f0df3d 100644 --- a/src/main.h +++ b/src/main.h @@ -1,6 +1,9 @@ #include #include +#define CWEB_ENABLE_DATABASE +#define CWEB_ENABLE_TEMPLATE + #define CWEB_STR(X) (CWEB_String) { (X), (int) sizeof(X)-1 } typedef struct { @@ -134,16 +137,16 @@ CWEB_VArg cweb_varg_from_ps (short *ps); CWEB_VArg cweb_varg_from_pi (int *pi); CWEB_VArg cweb_varg_from_pl (long *pl); CWEB_VArg cweb_varg_from_pll (long long *pll); -CWEB_VArg cweb_varg_from_psc (char *psc); -CWEB_VArg cweb_varg_from_pss (short *pss); -CWEB_VArg cweb_varg_from_psi (int *psi); -CWEB_VArg cweb_varg_from_psl (long *psl); -CWEB_VArg cweb_varg_from_psll (long long *psll); -CWEB_VArg cweb_varg_from_puc (char *puc); -CWEB_VArg cweb_varg_from_pus (short *pus); -CWEB_VArg cweb_varg_from_pui (int *pui); -CWEB_VArg cweb_varg_from_pul (long *pul); -CWEB_VArg cweb_varg_from_pull (long long *pull); +CWEB_VArg cweb_varg_from_psc (signed char *psc); +CWEB_VArg cweb_varg_from_pss (signed short *pss); +CWEB_VArg cweb_varg_from_psi (signed int *psi); +CWEB_VArg cweb_varg_from_psl (signed long *psl); +CWEB_VArg cweb_varg_from_psll (signed long long *psll); +CWEB_VArg cweb_varg_from_puc (unsigned char *puc); +CWEB_VArg cweb_varg_from_pus (unsigned short *pus); +CWEB_VArg cweb_varg_from_pui (unsigned int *pui); +CWEB_VArg cweb_varg_from_pul (unsigned long *pul); +CWEB_VArg cweb_varg_from_pull (unsigned long long *pull); CWEB_VArg cweb_varg_from_pf (float *pf); CWEB_VArg cweb_varg_from_pd (double *pd); CWEB_VArg cweb_varg_from_pb (bool *pb); @@ -229,14 +232,14 @@ CWEB_Request *cweb_wait(CWEB *cweb); bool cweb_match_endpoint(CWEB_Request *req, CWEB_String str); // Returns the CSRF token associated to the current session -CWEB_String cweb_get_session_csrf(CWEB_Request *req); +CWEB_String cweb_get_csrf(CWEB_Request *req); // Returns the user ID for the current session, or -1 if there is no session -int cweb_get_session_user_id(CWEB_Request *req); +int cweb_get_user_id(CWEB_Request *req); // Sets the user ID for the current session (it must be a positive integer). // If the ID is -1, the session is deleted. -int cweb_set_session_user_id(CWEB_Request *req, int user_id); +int cweb_set_user_id(CWEB_Request *req, int user_id); // Returns the request parameter with the specified name // If the request uses POST, the parameter is taken from the body, @@ -265,7 +268,7 @@ void cweb_respond_redirect(CWEB_Request *req, CWEB_String target); void cweb_respond_template(CWEB_Request *req, int status, CWEB_String template_file, int resource_id); // Evaluates an SQL INSERT statement and returns the ID of the last inserted row. On error -1 is returned -int64_t cweb_database_insert_impl(CWEB *cweb, const char *fmt, CWEB_VArgs args); +int64_t cweb_database_insert_impl(CWEB *cweb, char *fmt, CWEB_VArgs args); // Helper #define cweb_database_insert(cweb, fmt, ...) cweb_database_insert_impl((cweb), (fmt), CWEB_VARGS(__VA_ARGS__)) @@ -275,7 +278,7 @@ typedef struct { void *handle; } CWEB_QueryResult; // Evaluates an SQL SELECT statement, returning a scanner over the returned rows. // You don't have to check for errors with this function -CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, const char *fmt, CWEB_VArgs args); +CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, char *fmt, CWEB_VArgs args); // Helper #define cweb_database_select(cweb, fmt, ...) cweb_database_select_impl((cweb), (fmt), CWEB_VARGS(__VA_ARGS__))