#include "cweb.h" //////////////////////////////////////////////////////////////////////////////////////// // 3p/chttp.h //////////////////////////////////////////////////////////////////////////////////////// #line 1 "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 //////////////////////////////////////////////////////////////////////////////////////// #line 1 "3p/chttp.c" #include "chttp.h" //////////////////////////////////////////////////////////////////////////////////////// // 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 //////////////////////////////////////////////////////////////////////////////////////// #line 1 "3p/crypt_blowfish.h" /* * Written by Solar Designer in 2000-2011. * No copyright is claimed, and the software is hereby placed in the public * domain. In case this attempt to disclaim copyright and place the software * in the public domain is deemed null and void, then the software is * Copyright (c) 2000-2011 Solar Designer and it is hereby released to the * general public under the following terms: * * Redistribution and use in source and binary forms, with or without * modification, are permitted. * * There's ABSOLUTELY NO WARRANTY, express or implied. * * See crypt_blowfish.c for more information. */ #ifndef _CRYPT_BLOWFISH_H #define _CRYPT_BLOWFISH_H extern int _crypt_output_magic(const char *setting, char *output, int size); extern char *_crypt_blowfish_rn(const char *key, const char *setting, char *output, int size); extern char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, const char *input, int size, char *output, int output_size); #endif //////////////////////////////////////////////////////////////////////////////////////// // 3p/crypt_blowfish.c //////////////////////////////////////////////////////////////////////////////////////// #line 1 "3p/crypt_blowfish.c" /* * The crypt_blowfish homepage is: * * http://www.openwall.com/crypt/ * * This code comes from John the Ripper password cracker, with reentrant * and crypt(3) interfaces added, but optimizations specific to password * cracking removed. * * Written by Solar Designer in 1998-2014. * No copyright is claimed, and the software is hereby placed in the public * domain. In case this attempt to disclaim copyright and place the software * in the public domain is deemed null and void, then the software is * Copyright (c) 1998-2014 Solar Designer and it is hereby released to the * general public under the following terms: * * Redistribution and use in source and binary forms, with or without * modification, are permitted. * * There's ABSOLUTELY NO WARRANTY, express or implied. * * It is my intent that you should be able to use this on your system, * as part of a software package, or anywhere else to improve security, * ensure compatibility, or for any other purpose. I would appreciate * it if you give credit where it is due and keep your modifications in * the public domain as well, but I don't require that in order to let * you place this code and any modifications you make under a license * of your choice. * * This implementation is fully compatible with OpenBSD's bcrypt.c for prefix * "$2b$", originally by Niels Provos , and it uses * some of his ideas. The password hashing algorithm was designed by David * Mazieres . For information on the level of * compatibility for bcrypt hash prefixes other than "$2b$", please refer to * the comments in BF_set_key() below and to the included crypt(3) man page. * * There's a paper on the algorithm that explains its design decisions: * * http://www.usenix.org/events/usenix99/provos.html * * Some of the tricks in BF_ROUND might be inspired by Eric Young's * Blowfish library (I can't be sure if I would think of something if I * hadn't seen his code). */ #include #include #ifndef __set_errno #define __set_errno(val) errno = (val) #endif /* Just to make sure the prototypes match the actual definitions */ #include "crypt_blowfish.h" #ifdef __i386__ #define BF_ASM 1 #define BF_SCALE 1 #elif defined(__x86_64__) || defined(__alpha__) || defined(__hppa__) #define BF_ASM 0 #define BF_SCALE 1 #else #define BF_ASM 0 #define BF_SCALE 0 #endif typedef unsigned int BF_word; typedef signed int BF_word_signed; /* Number of Blowfish rounds, this is also hardcoded into a few places */ #define BF_N 16 typedef BF_word BF_key[BF_N + 2]; typedef struct { BF_word S[4][0x100]; BF_key P; } BF_ctx; /* * Magic IV for 64 Blowfish encryptions that we do at the end. * The string is "OrpheanBeholderScryDoubt" on big-endian. */ static BF_word BF_magic_w[6] = { 0x4F727068, 0x65616E42, 0x65686F6C, 0x64657253, 0x63727944, 0x6F756274 }; /* * P-box and S-box tables initialized with digits of Pi. */ static BF_ctx BF_init_state = { { { 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a }, { 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7 }, { 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0 }, { 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6 } }, { 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, 0x9216d5d9, 0x8979fb1b } }; static unsigned char BF_itoa64[64 + 1] = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; static unsigned char BF_atoi64[0x60] = { 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 0, 1, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 64, 64, 64, 64, 64, 64, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 64, 64, 64, 64, 64, 64, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 64, 64, 64, 64, 64 }; #define BF_safe_atoi64(dst, src) \ { \ tmp = (unsigned char)(src); \ if ((unsigned int)(tmp -= 0x20) >= 0x60) return -1; \ tmp = BF_atoi64[tmp]; \ if (tmp > 63) return -1; \ (dst) = tmp; \ } static int BF_decode(BF_word *dst, const char *src, int size) { unsigned char *dptr = (unsigned char *)dst; unsigned char *end = dptr + size; const unsigned char *sptr = (const unsigned char *)src; unsigned int tmp, c1, c2, c3, c4; do { BF_safe_atoi64(c1, *sptr++); BF_safe_atoi64(c2, *sptr++); *dptr++ = (c1 << 2) | ((c2 & 0x30) >> 4); if (dptr >= end) break; BF_safe_atoi64(c3, *sptr++); *dptr++ = ((c2 & 0x0F) << 4) | ((c3 & 0x3C) >> 2); if (dptr >= end) break; BF_safe_atoi64(c4, *sptr++); *dptr++ = ((c3 & 0x03) << 6) | c4; } while (dptr < end); return 0; } static void BF_encode(char *dst, const BF_word *src, int size) { const unsigned char *sptr = (const unsigned char *)src; const unsigned char *end = sptr + size; unsigned char *dptr = (unsigned char *)dst; unsigned int c1, c2; do { c1 = *sptr++; *dptr++ = BF_itoa64[c1 >> 2]; c1 = (c1 & 0x03) << 4; if (sptr >= end) { *dptr++ = BF_itoa64[c1]; break; } c2 = *sptr++; c1 |= c2 >> 4; *dptr++ = BF_itoa64[c1]; c1 = (c2 & 0x0f) << 2; if (sptr >= end) { *dptr++ = BF_itoa64[c1]; break; } c2 = *sptr++; c1 |= c2 >> 6; *dptr++ = BF_itoa64[c1]; *dptr++ = BF_itoa64[c2 & 0x3f]; } while (sptr < end); } static void BF_swap(BF_word *x, int count) { static int endianness_check = 1; char *is_little_endian = (char *)&endianness_check; BF_word tmp; if (*is_little_endian) do { tmp = *x; tmp = (tmp << 16) | (tmp >> 16); *x++ = ((tmp & 0x00FF00FF) << 8) | ((tmp >> 8) & 0x00FF00FF); } while (--count); } #if BF_SCALE /* Architectures which can shift addresses left by 2 bits with no extra cost */ #define BF_ROUND(L, R, N) \ tmp1 = L & 0xFF; \ tmp2 = L >> 8; \ tmp2 &= 0xFF; \ tmp3 = L >> 16; \ tmp3 &= 0xFF; \ tmp4 = L >> 24; \ tmp1 = data.ctx.S[3][tmp1]; \ tmp2 = data.ctx.S[2][tmp2]; \ tmp3 = data.ctx.S[1][tmp3]; \ tmp3 += data.ctx.S[0][tmp4]; \ tmp3 ^= tmp2; \ R ^= data.ctx.P[N + 1]; \ tmp3 += tmp1; \ R ^= tmp3; #else /* Architectures with no complicated addressing modes supported */ #define BF_INDEX(S, i) \ (*((BF_word *)(((unsigned char *)S) + (i)))) #define BF_ROUND(L, R, N) \ tmp1 = L & 0xFF; \ tmp1 <<= 2; \ tmp2 = L >> 6; \ tmp2 &= 0x3FC; \ tmp3 = L >> 14; \ tmp3 &= 0x3FC; \ tmp4 = L >> 22; \ tmp4 &= 0x3FC; \ tmp1 = BF_INDEX(data.ctx.S[3], tmp1); \ tmp2 = BF_INDEX(data.ctx.S[2], tmp2); \ tmp3 = BF_INDEX(data.ctx.S[1], tmp3); \ tmp3 += BF_INDEX(data.ctx.S[0], tmp4); \ tmp3 ^= tmp2; \ R ^= data.ctx.P[N + 1]; \ tmp3 += tmp1; \ R ^= tmp3; #endif /* * Encrypt one block, BF_N is hardcoded here. */ #define BF_ENCRYPT \ L ^= data.ctx.P[0]; \ BF_ROUND(L, R, 0); \ BF_ROUND(R, L, 1); \ BF_ROUND(L, R, 2); \ BF_ROUND(R, L, 3); \ BF_ROUND(L, R, 4); \ BF_ROUND(R, L, 5); \ BF_ROUND(L, R, 6); \ BF_ROUND(R, L, 7); \ BF_ROUND(L, R, 8); \ BF_ROUND(R, L, 9); \ BF_ROUND(L, R, 10); \ BF_ROUND(R, L, 11); \ BF_ROUND(L, R, 12); \ BF_ROUND(R, L, 13); \ BF_ROUND(L, R, 14); \ BF_ROUND(R, L, 15); \ tmp4 = R; \ R = L; \ L = tmp4 ^ data.ctx.P[BF_N + 1]; #if BF_ASM #define BF_body() \ _BF_body_r(&data.ctx); #else #define BF_body() \ L = R = 0; \ ptr = data.ctx.P; \ do { \ ptr += 2; \ BF_ENCRYPT; \ *(ptr - 2) = L; \ *(ptr - 1) = R; \ } while (ptr < &data.ctx.P[BF_N + 2]); \ \ ptr = data.ctx.S[0]; \ do { \ ptr += 2; \ BF_ENCRYPT; \ *(ptr - 2) = L; \ *(ptr - 1) = R; \ } while (ptr < &data.ctx.S[3][0xFF]); #endif static void BF_set_key(const char *key, BF_key expanded, BF_key initial, unsigned char flags) { const char *ptr = key; unsigned int bug, i, j; BF_word safety, sign, diff, tmp[2]; /* * There was a sign extension bug in older revisions of this function. While * we would have liked to simply fix the bug and move on, we have to provide * a backwards compatibility feature (essentially the bug) for some systems and * a safety measure for some others. The latter is needed because for certain * multiple inputs to the buggy algorithm there exist easily found inputs to * the correct algorithm that produce the same hash. Thus, we optionally * deviate from the correct algorithm just enough to avoid such collisions. * While the bug itself affected the majority of passwords containing * characters with the 8th bit set (although only a percentage of those in a * collision-producing way), the anti-collision safety measure affects * only a subset of passwords containing the '\xff' character (not even all of * those passwords, just some of them). This character is not found in valid * UTF-8 sequences and is rarely used in popular 8-bit character encodings. * Thus, the safety measure is unlikely to cause much annoyance, and is a * reasonable tradeoff to use when authenticating against existing hashes that * are not reliably known to have been computed with the correct algorithm. * * We use an approach that tries to minimize side-channel leaks of password * information - that is, we mostly use fixed-cost bitwise operations instead * of branches or table lookups. (One conditional branch based on password * length remains. It is not part of the bug aftermath, though, and is * difficult and possibly unreasonable to avoid given the use of C strings by * the caller, which results in similar timing leaks anyway.) * * For actual implementation, we set an array index in the variable "bug" * (0 means no bug, 1 means sign extension bug emulation) and a flag in the * variable "safety" (bit 16 is set when the safety measure is requested). * Valid combinations of settings are: * * Prefix "$2a$": bug = 0, safety = 0x10000 * Prefix "$2b$": bug = 0, safety = 0 * Prefix "$2x$": bug = 1, safety = 0 * Prefix "$2y$": bug = 0, safety = 0 */ bug = (unsigned int)flags & 1; safety = ((BF_word)flags & 2) << 15; sign = diff = 0; for (i = 0; i < BF_N + 2; i++) { tmp[0] = tmp[1] = 0; for (j = 0; j < 4; j++) { tmp[0] <<= 8; tmp[0] |= (unsigned char)*ptr; /* correct */ tmp[1] <<= 8; tmp[1] |= (BF_word_signed)(signed char)*ptr; /* bug */ /* * Sign extension in the first char has no effect - nothing to overwrite yet, * and those extra 24 bits will be fully shifted out of the 32-bit word. For * chars 2, 3, 4 in each four-char block, we set bit 7 of "sign" if sign * extension in tmp[1] occurs. Once this flag is set, it remains set. */ if (j) sign |= tmp[1] & 0x80; if (!*ptr) ptr = key; else ptr++; } diff |= tmp[0] ^ tmp[1]; /* Non-zero on any differences */ expanded[i] = tmp[bug]; initial[i] = BF_init_state.P[i] ^ tmp[bug]; } /* * At this point, "diff" is zero iff the correct and buggy algorithms produced * exactly the same result. If so and if "sign" is non-zero, which indicates * that there was a non-benign sign extension, this means that we have a * collision between the correctly computed hash for this password and a set of * passwords that could be supplied to the buggy algorithm. Our safety measure * is meant to protect from such many-buggy to one-correct collisions, by * deviating from the correct algorithm in such cases. Let's check for this. */ diff |= diff >> 16; /* still zero iff exact match */ diff &= 0xffff; /* ditto */ diff += 0xffff; /* bit 16 set iff "diff" was non-zero (on non-match) */ sign <<= 9; /* move the non-benign sign extension flag to bit 16 */ sign &= ~diff & safety; /* action needed? */ /* * If we have determined that we need to deviate from the correct algorithm, * flip bit 16 in initial expanded key. (The choice of 16 is arbitrary, but * let's stick to it now. It came out of the approach we used above, and it's * not any worse than any other choice we could make.) * * It is crucial that we don't do the same to the expanded key used in the main * Eksblowfish loop. By doing it to only one of these two, we deviate from a * state that could be directly specified by a password to the buggy algorithm * (and to the fully correct one as well, but that's a side-effect). */ initial[0] ^= sign; } static const unsigned char flags_by_subtype[26] = {2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 0}; static char *BF_crypt(const char *key, const char *setting, char *output, int size, BF_word min) { #if BF_ASM extern void _BF_body_r(BF_ctx *ctx); #endif struct { BF_ctx ctx; BF_key expanded_key; union { BF_word salt[4]; BF_word output[6]; } binary; } data; BF_word L, R; BF_word tmp1, tmp2, tmp3, tmp4; BF_word *ptr; BF_word count; int i; if (size < 7 + 22 + 31 + 1) { __set_errno(ERANGE); return NULL; } if (setting[0] != '$' || setting[1] != '2' || setting[2] < 'a' || setting[2] > 'z' || !flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a'] || setting[3] != '$' || setting[4] < '0' || setting[4] > '3' || setting[5] < '0' || setting[5] > '9' || (setting[4] == '3' && setting[5] > '1') || setting[6] != '$') { __set_errno(EINVAL); return NULL; } count = (BF_word)1 << ((setting[4] - '0') * 10 + (setting[5] - '0')); if (count < min || BF_decode(data.binary.salt, &setting[7], 16)) { __set_errno(EINVAL); return NULL; } BF_swap(data.binary.salt, 4); BF_set_key(key, data.expanded_key, data.ctx.P, flags_by_subtype[(unsigned int)(unsigned char)setting[2] - 'a']); memcpy(data.ctx.S, BF_init_state.S, sizeof(data.ctx.S)); L = R = 0; for (i = 0; i < BF_N + 2; i += 2) { L ^= data.binary.salt[i & 2]; R ^= data.binary.salt[(i & 2) + 1]; BF_ENCRYPT; data.ctx.P[i] = L; data.ctx.P[i + 1] = R; } ptr = data.ctx.S[0]; do { ptr += 4; L ^= data.binary.salt[(BF_N + 2) & 3]; R ^= data.binary.salt[(BF_N + 3) & 3]; BF_ENCRYPT; *(ptr - 4) = L; *(ptr - 3) = R; L ^= data.binary.salt[(BF_N + 4) & 3]; R ^= data.binary.salt[(BF_N + 5) & 3]; BF_ENCRYPT; *(ptr - 2) = L; *(ptr - 1) = R; } while (ptr < &data.ctx.S[3][0xFF]); do { int done; for (i = 0; i < BF_N + 2; i += 2) { data.ctx.P[i] ^= data.expanded_key[i]; data.ctx.P[i + 1] ^= data.expanded_key[i + 1]; } done = 0; do { BF_body(); if (done) break; done = 1; tmp1 = data.binary.salt[0]; tmp2 = data.binary.salt[1]; tmp3 = data.binary.salt[2]; tmp4 = data.binary.salt[3]; for (i = 0; i < BF_N; i += 4) { data.ctx.P[i] ^= tmp1; data.ctx.P[i + 1] ^= tmp2; data.ctx.P[i + 2] ^= tmp3; data.ctx.P[i + 3] ^= tmp4; } data.ctx.P[16] ^= tmp1; data.ctx.P[17] ^= tmp2; } while (1); } while (--count); for (i = 0; i < 6; i += 2) { L = BF_magic_w[i]; R = BF_magic_w[i + 1]; count = 64; do { BF_ENCRYPT; } while (--count); data.binary.output[i] = L; data.binary.output[i + 1] = R; } memcpy(output, setting, 7 + 22 - 1); output[7 + 22 - 1] = BF_itoa64[(int) BF_atoi64[(int)setting[7 + 22 - 1] - 0x20] & 0x30]; /* This has to be bug-compatible with the original implementation, so * only encode 23 of the 24 bytes. :-) */ BF_swap(data.binary.output, 6); BF_encode(&output[7 + 22], data.binary.output, 23); output[7 + 22 + 31] = '\0'; return output; } int _crypt_output_magic(const char *setting, char *output, int size) { if (size < 3) return -1; output[0] = '*'; output[1] = '0'; output[2] = '\0'; if (setting[0] == '*' && setting[1] == '0') output[1] = '1'; return 0; } /* * Please preserve the runtime self-test. It serves two purposes at once: * * 1. We really can't afford the risk of producing incompatible hashes e.g. * when there's something like gcc bug 26587 again, whereas an application or * library integrating this code might not also integrate our external tests or * it might not run them after every build. Even if it does, the miscompile * might only occur on the production build, but not on a testing build (such * as because of different optimization settings). It is painful to recover * from incorrectly-computed hashes - merely fixing whatever broke is not * enough. Thus, a proactive measure like this self-test is needed. * * 2. We don't want to leave sensitive data from our actual password hash * computation on the stack or in registers. Previous revisions of the code * would do explicit cleanups, but simply running the self-test after hash * computation is more reliable. * * The performance cost of this quick self-test is around 0.6% at the "$2a$08" * setting. */ char *_crypt_blowfish_rn(const char *key, const char *setting, char *output, int size) { const char *test_key = "8b \xd0\xc1\xd2\xcf\xcc\xd8"; const char *test_setting = "$2a$00$abcdefghijklmnopqrstuu"; static const char * const test_hashes[2] = {"i1D709vfamulimlGcq0qq3UvuUasvEa\0\x55", /* 'a', 'b', 'y' */ "VUrPmXD6q/nVSSp7pNDhCR9071IfIRe\0\x55"}; /* 'x' */ const char *test_hash = test_hashes[0]; char *retval; const char *p; int save_errno, ok; struct { char s[7 + 22 + 1]; char o[7 + 22 + 31 + 1 + 1 + 1]; } buf; /* Hash the supplied password */ _crypt_output_magic(setting, output, size); retval = BF_crypt(key, setting, output, size, 16); save_errno = errno; /* * Do a quick self-test. It is important that we make both calls to BF_crypt() * from the same scope such that they likely use the same stack locations, * which makes the second call overwrite the first call's sensitive data on the * stack and makes it more likely that any alignment related issues would be * detected by the self-test. */ memcpy(buf.s, test_setting, sizeof(buf.s)); if (retval) { unsigned int flags = flags_by_subtype[ (unsigned int)(unsigned char)setting[2] - 'a']; test_hash = test_hashes[flags & 1]; buf.s[2] = setting[2]; } memset(buf.o, 0x55, sizeof(buf.o)); buf.o[sizeof(buf.o) - 1] = 0; p = BF_crypt(test_key, buf.s, buf.o, sizeof(buf.o) - (1 + 1), 1); ok = (p == buf.o && !memcmp(p, buf.s, 7 + 22) && !memcmp(p + (7 + 22), test_hash, 31 + 1 + 1 + 1)); { const char *k = "\xff\xa3" "34" "\xff\xff\xff\xa3" "345"; BF_key ae, ai, ye, yi; BF_set_key(k, ae, ai, 2); /* $2a$ */ BF_set_key(k, ye, yi, 4); /* $2y$ */ ai[0] ^= 0x10000; /* undo the safety (for comparison) */ ok = ok && ai[0] == 0xdb9c59bc && ye[17] == 0x33343500 && !memcmp(ae, ye, sizeof(ae)) && !memcmp(ai, yi, sizeof(ai)); } __set_errno(save_errno); if (ok) return retval; /* Should not happen */ _crypt_output_magic(setting, output, size); __set_errno(EINVAL); /* pretend we don't support this hash type */ return NULL; } char *_crypt_gensalt_blowfish_rn(const char *prefix, unsigned long count, const char *input, int size, char *output, int output_size) { if (size < 16 || output_size < 7 + 22 + 1 || (count && (count < 4 || count > 31)) || prefix[0] != '$' || prefix[1] != '2' || (prefix[2] != 'a' && prefix[2] != 'b' && prefix[2] != 'y')) { if (output_size > 0) output[0] = '\0'; __set_errno((output_size < 7 + 22 + 1) ? ERANGE : EINVAL); return NULL; } if (!count) count = 5; output[0] = '$'; output[1] = '2'; output[2] = prefix[2]; output[3] = '$'; output[4] = '0' + count / 10; output[5] = '0' + count % 10; output[6] = '$'; BF_encode(&output[7], (const BF_word *)input, 16); output[7 + 22] = '\0'; return output; } //////////////////////////////////////////////////////////////////////////////////////// // 3p/wl.h //////////////////////////////////////////////////////////////////////////////////////// #line 1 "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 //////////////////////////////////////////////////////////////////////////////////////// #line 1 "3p/wl.c" #include #include #include #include #include #include "wl.h" ///////////////////////////////////////////////////////////////////////// // 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 //////////////////////////////////////////////////////////////////////////////////////// #line 1 "src/main.c" #include "sqlite3.h" #include "cweb.h" #ifdef __linux__ #include #include #endif #ifdef _WIN32 #include #endif typedef struct { char path[1<<8]; int pathlen; WL_Program program; } CachedProgram; typedef struct { int count; int capacity_log2; CachedProgram pool[]; } TemplateCache; static TemplateCache *template_cache_init(int capacity_log2); static void template_cache_free(TemplateCache *cache); typedef struct SessionStorage SessionStorage; 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); static int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache, sqlite3_stmt **pstmt, char *fmt, CWEB_VArgs args); #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 *tcpcache; bool allow_insecure_login; }; /////////////////////////// bool cweb_streq(CWEB_String a, CWEB_String b) { return http_streq((HTTP_String) { a.ptr, a.len }, (HTTP_String) { b.ptr, b.len }); } CWEB_String cweb_trim(CWEB_String s) { HTTP_String s2 = http_trim((HTTP_String) { s.ptr, s.len }); return (CWEB_String) { s2.ptr, s2.len }; } CWEB_VArg cweb_varg_from_c (char c) { return (CWEB_VArg) { CWEB_VARG_TYPE_C, .c=c }; } CWEB_VArg cweb_varg_from_s (short s) { return (CWEB_VArg) { CWEB_VARG_TYPE_S, .s=s }; } CWEB_VArg cweb_varg_from_i (int i) { return (CWEB_VArg) { CWEB_VARG_TYPE_I, .i=i }; } CWEB_VArg cweb_varg_from_l (long l) { return (CWEB_VArg) { CWEB_VARG_TYPE_L, .l=l }; } CWEB_VArg cweb_varg_from_ll (long long ll) { return (CWEB_VArg) { CWEB_VARG_TYPE_LL, .ll=ll }; } CWEB_VArg cweb_varg_from_sc (char sc) { return (CWEB_VArg) { CWEB_VARG_TYPE_SC, .sc=sc }; } CWEB_VArg cweb_varg_from_ss (short ss) { return (CWEB_VArg) { CWEB_VARG_TYPE_SS, .ss=ss }; } CWEB_VArg cweb_varg_from_si (int si) { return (CWEB_VArg) { CWEB_VARG_TYPE_SI, .si=si }; } CWEB_VArg cweb_varg_from_sl (long sl) { return (CWEB_VArg) { CWEB_VARG_TYPE_SL, .sl=sl }; } CWEB_VArg cweb_varg_from_sll (long long sll) { return (CWEB_VArg) { CWEB_VARG_TYPE_SLL, .sll=sll }; } CWEB_VArg cweb_varg_from_uc (char uc) { return (CWEB_VArg) { CWEB_VARG_TYPE_UC, .uc=uc }; } CWEB_VArg cweb_varg_from_us (short us) { return (CWEB_VArg) { CWEB_VARG_TYPE_US, .us=us }; } CWEB_VArg cweb_varg_from_ui (int ui) { return (CWEB_VArg) { CWEB_VARG_TYPE_UI, .ui=ui }; } CWEB_VArg cweb_varg_from_ul (long ul) { return (CWEB_VArg) { CWEB_VARG_TYPE_UL, .ul=ul }; } CWEB_VArg cweb_varg_from_ull (long long ull) { return (CWEB_VArg) { CWEB_VARG_TYPE_ULL, .ull=ull }; } CWEB_VArg cweb_varg_from_f (float f) { return (CWEB_VArg) { CWEB_VARG_TYPE_F, .f=f }; } CWEB_VArg cweb_varg_from_d (double d) { return (CWEB_VArg) { CWEB_VARG_TYPE_D, .d=d }; } CWEB_VArg cweb_varg_from_b (bool b) { return (CWEB_VArg) { CWEB_VARG_TYPE_B, .b=b }; } CWEB_VArg cweb_varg_from_str (HTTP_String str) { return (CWEB_VArg) { CWEB_VARG_TYPE_STR, .str=str }; } CWEB_VArg cweb_varg_from_pc (char *pc) { return (CWEB_VArg) { CWEB_VARG_TYPE_PC, .pc=pc }; } CWEB_VArg cweb_varg_from_ps (short *ps) { return (CWEB_VArg) { CWEB_VARG_TYPE_PS, .ps=ps }; } 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_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 (HTTP_String *pstr) { return (CWEB_VArg) { CWEB_VARG_TYPE_PSTR, .pstr=pstr }; } ///////////////////////////////////////////////////////////////// // RANDOM //////////////////////////////////////////////////////////////// static int generate_random_bytes(char *dst, int cap) { #ifdef __linux__ int copied = 0; while (copied < cap) { int ret = getrandom(dst, (size_t) cap, 0); if (ret < 0) { if (errno == EINTR) continue; return -1; } copied += ret; } return 0; #endif #ifdef _WIN32 NTSTATUS status = BCryptGenRandom(NULL, (unsigned char*) dst, (ULONG) cap, BCRYPT_USE_SYSTEM_PREFERRED_RNG); return BCRYPT_SUCCESS(status) ? 0 : -1; #endif } //////////////////////////////////////////////////////////////// // PASSWORD //////////////////////////////////////////////////////////////// int cweb_hash_password(char *pass, int passlen, int cost, CWEB_PasswordHash *hash) { char passzt[128]; if (passlen >= (int) sizeof(passzt)) return -1; memcpy(passzt, pass, passlen); passzt[passlen] = '\0'; char random[16]; int ret = generate_random_bytes(random, (int) sizeof(random)); if (ret) return -1; char salt[30]; if (_crypt_gensalt_blowfish_rn("$2b$", cost, random, sizeof(random), salt, sizeof(salt)) == NULL) return -1; if (_crypt_blowfish_rn(passzt, salt, hash->data, (int) sizeof(hash->data)) == NULL) return -1; return 0; } int cweb_check_password(char *pass, int passlen, CWEB_PasswordHash hash) { char passzt[128]; if (passlen >= (int) sizeof(passzt)) return -1; memcpy(passzt, pass, passlen); passzt[passlen] = '\0'; CWEB_PasswordHash new_hash; if (_crypt_blowfish_rn(passzt, hash.data, new_hash.data, sizeof(new_hash.data)) == NULL) return -1; if (strcmp(hash.data, new_hash.data)) // TODO: should be constant-time return 1; return 0; } ///////////////////////////////////////////////////////////////// // SESSION //////////////////////////////////////////////////////////////// #define CSRF_RAW_TOKEN_SIZE 32 #define SESS_RAW_TOKEN_SIZE 32 #define CSRF_TOKEN_SIZE (2 * CSRF_RAW_TOKEN_SIZE) #define SESS_TOKEN_SIZE (2 * SESS_RAW_TOKEN_SIZE) typedef struct { int user; char csrf[CSRF_TOKEN_SIZE]; char sess[SESS_TOKEN_SIZE]; } Session; struct SessionStorage { int count; int capacity; Session items[]; }; static bool is_hex_digit(char c) { return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } 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 void unpack_token(char *src, int srclen, char *dst, int dstlen) { assert(2 * srclen == dstlen); for (int i = 0; i < srclen; i++) { static const char table[] = "0123456789abcdef"; int low = (src[i] & 0x0F) >> 0; int high = (src[i] & 0xF0) >> 4; dst[(i << 1) | 0] = table[high]; dst[(i << 1) | 1] = table[low]; } } static int pack_token(char *src, int srclen, char *dst, int dstlen) { if (srclen & 1) return -1; assert(srclen == 2 * dstlen); for (int i = 0; i < srclen; i += 2) { int high = src[i+0]; int low = src[i+1]; if (!is_hex_digit(high) || !is_hex_digit(low)) return -1; dst[i] = (hex_digit_to_int(high) << 4) | (hex_digit_to_int(low) << 0); } return 0; } static SessionStorage *session_storage_init(int max_sessions) { int capacity = 2 * max_sessions; SessionStorage *storage = malloc(sizeof(SessionStorage) + capacity * sizeof(Session)); if (storage == NULL) return NULL; storage->count = 0; storage->capacity = capacity; for (int i = 0; i < capacity; i++) storage->items[i].user = -1; return storage; } static void session_storage_free(SessionStorage *storage) { free(storage); } static Session *lookup_session_slot(SessionStorage *storage, HTTP_String sess, bool find_unused) { if (find_unused && 2 * storage->count + 2 > storage->capacity) return NULL; if (sess.len != SESS_TOKEN_SIZE) return NULL; uint64_t key; if (sess.len < (int) (2 * sizeof(key))) return NULL; for (int i = 0; i < (int) sizeof(key); i++) { int high = sess.ptr[(i << 1) | 0]; int low = sess.ptr[(i << 1) | 1]; if (!is_hex_digit(sess.ptr[i+0]) || !is_hex_digit(sess.ptr[i+1])) return NULL; key <<= 4; key |= hex_digit_to_int(high); key <<= 4; key |= hex_digit_to_int(low); } int i = key % storage->capacity; for (int j = 0; j < storage->capacity; j++) { if (find_unused) { if (storage->items[i].user < 0) return &storage->items[i]; // Unused slot } else { if (storage->items[i].user == -1) return NULL; if (storage->items[i].user != -2) if (!memcmp(storage->items[i].sess, sess.ptr, SESS_TOKEN_SIZE)) return &storage->items[i]; } i++; if (i == storage->capacity) i = 0; } return NULL; } static int create_session(SessionStorage *storage, int user, HTTP_String *psess, HTTP_String *pcsrf) { int ret; char raw_sess[SESS_RAW_TOKEN_SIZE]; char raw_csrf[CSRF_RAW_TOKEN_SIZE]; ret = generate_random_bytes(raw_sess, SESS_RAW_TOKEN_SIZE); if (ret) return -1; ret = generate_random_bytes(raw_csrf, CSRF_RAW_TOKEN_SIZE); if (ret) return -1; char sess[SESS_TOKEN_SIZE]; char csrf[CSRF_TOKEN_SIZE]; unpack_token(raw_sess, SESS_RAW_TOKEN_SIZE, sess, SESS_TOKEN_SIZE); unpack_token(raw_csrf, CSRF_RAW_TOKEN_SIZE, csrf, CSRF_TOKEN_SIZE); Session *found = lookup_session_slot(storage, (HTTP_String) { sess, SESS_TOKEN_SIZE }, true); if (found == NULL) return -1; found->user = user; memcpy(found->sess, sess, SESS_TOKEN_SIZE); memcpy(found->csrf, csrf, CSRF_TOKEN_SIZE); *psess = (HTTP_String) { found->sess, SESS_TOKEN_SIZE }; *pcsrf = (HTTP_String) { found->csrf, CSRF_TOKEN_SIZE }; storage->count++; return 0; } static int delete_session(SessionStorage *storage, HTTP_String sess) { char raw_sess[SESS_RAW_TOKEN_SIZE]; if (sess.len != SESS_TOKEN_SIZE || pack_token(sess.ptr, sess.len, raw_sess, (int) sizeof(raw_sess)) < 0) return -1; Session *found = lookup_session_slot(storage, sess, false); if (found == NULL) return false; assert(found->user >= 0); found->user = -2; storage->count--; return 0; } static int find_session(SessionStorage *storage, HTTP_String sess, HTTP_String *pcsrf, int *puser) { Session *found = lookup_session_slot(storage, sess, false); if (found == NULL) return -1; assert(found->user >= 0); *pcsrf = (HTTP_String) { found->csrf, CSRF_TOKEN_SIZE }; *puser = found->user; return 0; } ///////////////////////////////////////////////////////////////// // DATABASE //////////////////////////////////////////////////////////////// typedef struct { char *str; int len; sqlite3_stmt *stmt; } Prepped; struct SQLiteCache { sqlite3 *db; int count; int capacity_log2; Prepped items[]; }; static SQLiteCache *sqlite_cache_init(sqlite3 *db, int capacity_log2) { SQLiteCache *cache = malloc(sizeof(SQLiteCache) + (1 << capacity_log2) * sizeof(Prepped)); if (cache == NULL) return NULL; cache->db = db; cache->count = 0; cache->capacity_log2 = capacity_log2; for (int i = 0; i < (1 << capacity_log2); i++) cache->items[i].stmt = NULL; return cache; } static void sqlite_cache_free(SQLiteCache *cache) { for (int i = 0; i < (1 << cache->capacity_log2); i++) { sqlite3_stmt *stmt = cache->items[i].stmt; if (stmt) { free(cache->items[i].str); sqlite3_finalize(stmt); } } free(cache); } static sqlite3 *sqlite_cache_getdb(SQLiteCache *cache) { return cache->db; } static unsigned long djb2(char *src, int len) { char *ptr = src; char *end = src + 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(SQLiteCache *cache, char *fmt, int fmtlen) { int mask = (1 << cache->capacity_log2) - 1; int hash = djb2(fmt, fmtlen); int i = hash & mask; int perturb = hash; for (;;) { if (cache->items[i].stmt == NULL) return i; if (cache->items[i].len == fmtlen && !memcmp(cache->items[i].str, fmt, fmtlen)) return i; perturb >>= 5; i = (i * 5 + 1 + perturb) & mask; } return -1; } static int sqlite3utils_prepare(SQLiteCache *cache, sqlite3_stmt **pstmt, char *fmt, int fmtlen) { if (fmtlen < 0) fmtlen = strlen(fmt); int i = lookup(cache, fmt, fmtlen); if (cache->items[i].stmt == NULL) { sqlite3_stmt *stmt; int ret = sqlite3_prepare_v2(cache->db, fmt, -1, &stmt, NULL); if (ret != SQLITE_OK) { //fprintf(stderr, "Failed to prepare statement: %s (%s:%d)\n", sqlite3_errmsg(db), __FILE__, __LINE__); return ret; } char *cpy = malloc(fmtlen); if (cpy == NULL) { sqlite3_finalize(stmt); return SQLITE_NOMEM; } memcpy(cpy, fmt, fmtlen); cache->items[i].str = cpy; cache->items[i].len = fmtlen; cache->items[i].stmt = stmt; } sqlite3_stmt *stmt = cache->items[i].stmt; *pstmt = stmt; return SQLITE_OK; } static int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache, sqlite3_stmt **pstmt, char *fmt, VArgs args) { sqlite3_stmt *stmt; int ret = sqlite3utils_prepare(cache, &stmt, fmt, strlen(fmt)); if (ret != SQLITE_OK) return ret; for (int i = 0; i < args.len; i++) { CWEB_VArg arg = args.ptr[i]; switch (arg.type) { case CWEB_VARG_TYPE_C : ret = sqlite3_bind_text (stmt, i+1, &arg.c, 1, NULL); break; case CWEB_VARG_TYPE_S : ret = sqlite3_bind_int (stmt, i+1, arg.s); break; case CWEB_VARG_TYPE_I : ret = sqlite3_bind_int (stmt, i+1, arg.i); break; case CWEB_VARG_TYPE_L : ret = sqlite3_bind_int64 (stmt, i+1, arg.l); break; case CWEB_VARG_TYPE_LL : ret = sqlite3_bind_int64 (stmt, i+1, arg.ll); break; case CWEB_VARG_TYPE_SC : ret = sqlite3_bind_int (stmt, i+1, arg.sc); break; case CWEB_VARG_TYPE_SS : ret = sqlite3_bind_int (stmt, i+1, arg.ss); break; case CWEB_VARG_TYPE_SI : ret = sqlite3_bind_int (stmt, i+1, arg.si); break; case CWEB_VARG_TYPE_SL : ret = sqlite3_bind_int64 (stmt, i+1, arg.sl); break; case CWEB_VARG_TYPE_SLL: ret = sqlite3_bind_int (stmt, i+1, arg.sll); break; case CWEB_VARG_TYPE_UC : ret = sqlite3_bind_int (stmt, i+1, arg.uc); break; case CWEB_VARG_TYPE_US : ret = sqlite3_bind_int (stmt, i+1, arg.us); break; case CWEB_VARG_TYPE_UI : ret = sqlite3_bind_int64 (stmt, i+1, arg.ui); break; case CWEB_VARG_TYPE_UL : ret = sqlite3_bind_int64 (stmt, i+1, arg.ul); break; case CWEB_VARG_TYPE_ULL: ret = sqlite3_bind_int64 (stmt, i+1, arg.ull); break; case CWEB_VARG_TYPE_F : ret = sqlite3_bind_double(stmt, i+1, arg.f); break; 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; } if (ret != SQLITE_OK) { sqlite3_reset(stmt); return ret; } } *pstmt = stmt; return SQLITE_OK; } int64_t cweb_database_insert_impl(CWEB *cweb, const char *fmt, VArgs args) { sqlite3_stmt *stmt; int ret = sqlite3utils_prepare_and_bind_impl(cweb->dbcache, &stmt, fmt, args); if (ret != SQLITE_OK) return -1; ret = sqlite3_step(stmt); if (ret != SQLITE_DONE) { sqlite3_reset(stmt); return -1; } int64_t insert_id = sqlite3_last_insert_rowid(cweb->db); if (insert_id < 0) { sqlite3_reset(stmt); return -1; } sqlite3_reset(stmt); return insert_id; } CWEB_QueryResult cweb_database_select_impl(CWEB *cweb, const char *fmt, CWEB_VArgs args) { 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 }; } int cweb_next_query_row_impl(CWEB_QueryResult *res, CWEB_VArgs args) { if (res->handle == NULL) return -1; int ret = sqlite3_step(res->handle); if (ret == SQLITE_DONE) return 0; if (ret != SQLITE_ROW) { sqlite3_reset(res->handle); res->handle = NULL; return -1; } if (sqlite3_column_count(res->handle) != args.len) { sqlite3_reset(res->handle); res->handle = NULL; return -1; } for (int i = 0; i < args.len; i++) { switch (args.ptr[i].type) { case CWEB_VARG_TYPE_PI: { int64_t x = sqlite3_column_int64(res->handle, i); if (x < INT_MIN || x > INT_MAX) { sqlite3_reset(res->handle); res->handle = NULL; return -1; } *args.ptr[i].pi = (int) x; } break; case CWEB_VARG_TYPE_PL: { int64_t x = sqlite3_column_int64(res->handle, i); if (x < LONG_MIN || x > LONG_MAX) { sqlite3_reset(res->handle); res->handle = NULL; return -1; } *args.ptr[i].pl = (long) x; } break; case CWEB_VARG_TYPE_PLL: { int64_t x = sqlite3_column_int64(res->handle, i); *args.ptr[i].pll = (int) x; } break; case CWEB_VARG_TYPE_PF: { double x = sqlite3_column_double(res->handle, i); *args.ptr[i].pf = (float) x; } break; case CWEB_VARG_TYPE_PD: { double x = sqlite3_column_double(res->handle, i); *args.ptr[i].pf = (float) x; } break; case CWEB_VARG_TYPE_PSTR: { *args.ptr[i].pstr = (HTTP_String) { sqlite3_column_text(res->handle, i), sqlite3_column_bytes(res->handle, i), }; } break; default: sqlite3_reset(res->handle); res->handle = NULL; return -1; } } return 1; } void cweb_free_query_result(CWEB_QueryResult *res) { if (res->handle) { sqlite3_reset(res->handle); res->handle = NULL; } } int cweb_global_init(void) { http_global_init(); } void cweb_global_free(void) { http_global_free(); } int cweb_init(CWEB *cweb, 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. bool allow_insecure_login = true; if (allow_insecure_login) printf("WARNING: allow_insecure_login is true\n"); cweb->pool_cap = 1<<20; cweb->pool = malloc(cweb->pool_cap); if (cweb->pool == NULL) return -1; cweb->tpcache = template_cache_init(4); if (cweb->tpcache == NULL) { free(cweb->pool); return -1; } cweb->session_storage = session_storage_init(1024); if (cweb->session_storage == NULL) { template_cache_free(cweb->tcpcache); free(cweb->pool); return -1; } cweb->server = http_server_init(addr, port); if (cweb->server == NULL) { session_storage_free(cweb->session_storage); template_cache_free(cweb->tcpcache); free(cweb->pool); return -1; } cweb->db = NULL; cweb->dbcache = NULL; cweb->allow_insecure_login = false; return 0; } void cweb_free(CWEB *cweb) { http_server_free(cweb->server); session_storage_free(cweb->session_storage); template_cache_free(cweb->tpcache); if (cweb->db) { sqlite_cache_free(cweb->dbcache); sqlite3_close(cweb->db); } } void cweb_version(void) { printf("%s\n", sqlite3_libversion()); } int cweb_enable_database(CWEB *cweb, CWEB_String file) { if (cweb->db != NULL) return -1; // Already enabled char file_copy[1<<12]; if (file.len >= SIZEOF(file_copy)) return -1; memcpy(file_copy, file.ptr, file.len); file_copy[file.len] = '\0'; int ret = sqlite3_open(file_copy, &cweb->db); if (ret != SQLITE_OK) { sqlite3_close(cweb->db); cweb->db = NULL; return -1; } char *schema_data; long schema_size; ret = load_file(file_copy, &schema_data, &schema_size); if (ret < 0) { sqlite3_close(cweb->db); cweb->db = NULL; return -1; } 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); cweb->dbcache = sqlite_cache_init(cweb->db, 5); if (cweb->dbcache == NULL) { sqlite3_close(cweb->db); cweb->db = NULL; return -1; } return 0; } CWEB_Request cweb_wait(CWEB *cweb) { CWEB_Request req; int ret = http_server_wait(cweb->server, &req.req, &req.builder); if (ret < 0) return -1; req.arena = (WL_Arena) { cweb->pool, cweb->pool_cap, 0 }; int user_id; HTTP_String sess; HTTP_String csrf; req.just_created_session = false; req.sess = http_getcookie(req.req, HTTP_STR("sess_token")); if (find_session(cweb->session_storage, sess, &csrf, &user_id) < 0) { req.user_id = -1; req.sess = (HTTP_String) { NULL, 0 }; req.csrf = (HTTP_String) { NULL, 0 }; } return req; } CWEB_String cweb_get_param_s(CWEB_Request *req, CWEB_String name) { HTTP_String res = http_get_param(req->req->body, (HTTP_String) { name.ptr, name.len }, req->arena.ptr + req->arena.cur, req->arena.len - req->arena.cur ); if (res.ptr >= req->arena.ptr && res.ptr < req->arena.ptr + req->arena.len) req->arena.cur += res.len; return (CWEB_String) { res.ptr, res.len }; } 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 }); } static int set_auth_cookie_if_necessary(CWEB_Request *req) { if (req->just_created_session) { char cookie[1<<9]; int cookie_len = snprintf(cookie, sizeof(cookie), "Set-Cookie: sess_token=%.*s; Path=/; HttpOnly; Secure", req->sess.len, req->sess.ptr); if (cookie_len < 0 || cookie_len >= (int) sizeof(cookie)) return -500; http_response_builder_header(req->builder, (HTTP_String) { cookie, cookie_len }); } return 0; } void cweb_respond(CWEB_Request *req, int status, CWEB_String content) { http_response_builder_status(req->builder, 200); 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; } http_response_builder_body(req->builder, (HTTP_String) { content.ptr, content.len }); http_response_builder_done(req->builder); } void cweb_respond_redirect(CWEB_Request *req, CWEB_String target) { char location[128]; int ret = snprintf(location, sizeof(location), "Location: %.*s", target.len, target.ptr); if (ret < 0 || ret >= SIZEOF(location)) { // TODO } http_response_builder_status(req->builder, 303); 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; } http_response_builder_header(req->builder, (HTTP_String) { location, ret }); http_response_builder_done(req->builder); } int cweb_set_user_id(CWEB_Request *req, int user_id) { if (user_id != req->user_id) { if (!req->req->secure && !req->cweb->allow_insecure_login) return -1; int ret; if (user_id == -1) ret = delete_session(req->cweb->session_storage, req->sess); else ret = create_session(req->cweb->session_storage, user_id, &req->sess, &req->csrf); if (ret < 0) return -1; req->just_created_session = true; } return 0; } int cweb_get_user_id(CWEB_Request *req) { return req->user_id; } ///////////////////////////////////////////////////////////////// // TEMPLATE //////////////////////////////////////////////////////////////// static TemplateCache *template_cache_init(int capacity_log2) { TemplateCache *cache = malloc(sizeof(TemplateCache) + (1 << capacity_log2) * sizeof(CachedProgram)); if (cache == NULL) return NULL; cache->count = 0; cache->capacity_log2 = capacity_log2; for (int i = 0; i < (1 << capacity_log2); i++) cache->pool[i].pathlen = -1; return cache; } 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) { int mask = (1 << cache->capacity_log2) - 1; int hash = djb2(path); int i = hash & mask; int perturb = hash; for (;;) { if (cache->pool[i].pathlen == -1) return i; if (wl_streq(path, cache->pool[i].path, cache->pool[i].pathlen)) return i; perturb >>= 5; i = (i * 5 + 1 + perturb) & mask; } 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); if (compiler == NULL) { TRACE("Couldn't initialize WL compiler object"); return -1; } LoadedFile *loaded_file_head = NULL; LoadedFile **loaded_file_tail = &loaded_file_head; for (int i = 0;; i++) { LoadedFile *loaded_file = load_file(path); if (loaded_file == NULL) { TRACE("Couldn't load file '%.*s'", path.len, path.ptr); free_loaded_files(loaded_file_head); return -1; } *loaded_file_tail = loaded_file; loaded_file_tail = &loaded_file->next; WL_String content = { loaded_file->data, loaded_file->len }; WL_AddResult result = wl_compiler_add(compiler, content); if (result.type == WL_ADD_ERROR) { TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr); free_loaded_files(loaded_file_head); return -1; } if (result.type == WL_ADD_LINK) break; assert(result.type == WL_ADD_AGAIN); path = result.path; } int ret = wl_compiler_link(compiler, program); if (ret < 0) { TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr); return -1; } free_loaded_files(loaded_file_head); TRACE("Compilation succeded"); return 0; } static int query_routine(WL_Runtime *rt, SQLiteCache *dbcache) { int num_args = wl_arg_count(rt); if (num_args == 0) return 0; WL_String format; if (!wl_arg_str(rt, 0, &format)) return -1; sqlite3_stmt *stmt; int ret = sqlite3utils_prepare(dbcache, &stmt, format.ptr, format.len); if (ret != SQLITE_OK) return -1; for (int i = 1; i < num_args; i++) { int64_t ival; double fval; WL_String str; if (wl_arg_none(rt, i)) ret = sqlite3_bind_null(stmt, i); else if (wl_arg_s64(rt, i, &ival)) ret = sqlite3_bind_int64(stmt, i, ival); else if (wl_arg_f64(rt, i, &fval)) ret = sqlite3_bind_double(stmt, i, fval); else if (wl_arg_str(rt, i, &str)) ret = sqlite3_bind_text(stmt, i, str.ptr, str.len, NULL); else { assert(0); // TODO } if (ret != SQLITE_OK) { sqlite3_reset(stmt); return -1; } } wl_push_array(rt, 0); while (sqlite3_step(stmt) == SQLITE_ROW) { int num_cols = sqlite3_column_count(stmt); if (num_cols < 0) { sqlite3_reset(stmt); return -1; } wl_push_map(rt, num_cols); for (int i = 0; i < num_cols; i++) { ret = sqlite3_column_type(stmt, i); switch (ret) { case SQLITE_INTEGER: { int64_t x = sqlite3_column_int64(stmt, i); wl_push_s64(rt, x); } break; case SQLITE_FLOAT: { double x = sqlite3_column_double(stmt, i); wl_push_f64(rt, x); } break; case SQLITE_TEXT: { const void *x = sqlite3_column_text(stmt, i); int n = sqlite3_column_bytes(stmt, i); wl_push_str(rt, (WL_String) { (char*) x, n }); } break; case SQLITE_BLOB: { const void *x = sqlite3_column_blob(stmt, i); int n = sqlite3_column_bytes(stmt, i); wl_push_str(rt, (WL_String) { (char*) x, n }); } break; case SQLITE_NULL: { wl_push_none(rt); } break; } const char *name = sqlite3_column_name(stmt, i); wl_push_str(rt, (WL_String) { (char*) name, strlen(name) }); wl_insert(rt); } wl_append(rt); } sqlite3_reset(stmt); return 0; } static void push_sysvar(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache, HTTP_String csrf, int user_id, int resource_id) { (void) dbcache; if (wl_streq(name, "login_user_id", -1)) { if (user_id < 0) wl_push_none(rt); else wl_push_s64(rt, user_id); } else if (wl_streq(name, "resource_id", -1)) { if (resource_id < 0) wl_push_none(rt); else wl_push_s64(rt, resource_id); } else if (wl_streq(name, "csrf", -1)) { if (csrf.len == 0) wl_push_none(rt); else wl_push_str(rt, (WL_String) { csrf.ptr, csrf.len }); } } static void push_syscall(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache) { if (wl_streq(name, "query", -1)) { query_routine(rt, dbcache); return; } } static int get_or_create_program(TemplateCache *cache, WL_String path, WL_Arena *arena, WL_Program *program) { if (cache == NULL) return -1; int i = lookup(cache, path); if (cache->pool[i].pathlen == -1) { WL_Program program; int ret = compile(path, &program, arena); if (ret < 0) return -1; void *p = malloc(program.len); if (p == NULL) return -1; memcpy(p, program.ptr, program.len); program.ptr = p; if ((int) sizeof(cache->pool->path) <= path.len) return -1; memcpy(cache->pool[i].path, path.ptr, path.len); cache->pool[i].path[path.len] = '\0'; cache->pool[i].pathlen = path.len; cache->pool[i].program = program; } *program = cache->pool[i].program; return 0; } void cweb_respond_template(CWEB_Request *req, int status, CWEB_String template_file, int resource_id) { http_response_builder_status(req->builder, status); WL_Program program; int ret = get_or_create_program(req->cweb->tpcache, template_file, &req->arena, &program); if (ret < 0) { http_response_builder_undo(req->builder); http_response_builder_status(req->builder, 500); http_response_builder_done(req->builder); return; } //wl_dump_program(program); WL_Runtime *rt = wl_runtime_init(&req->arena, program); if (rt == NULL) { http_response_builder_undo(req->builder); http_response_builder_status(req->builder, 500); http_response_builder_done(req->builder); return; } for (bool done = false; !done; ) { WL_EvalResult result = wl_runtime_eval(rt); switch (result.type) { case WL_EVAL_DONE: http_response_builder_done(req->builder); done = true; break; case WL_EVAL_ERROR: // wl_runtime_error(rt) http_response_builder_undo(req->builder); http_response_builder_status(req->builder, 500); http_response_builder_done(req->builder); return; case WL_EVAL_SYSVAR: push_sysvar(rt, result.str, req->cweb->dbcache, req->csrf, req->user_id, resource_id); break; case WL_EVAL_SYSCALL: push_syscall(rt, result.str, req->cweb->dbcache); break; case WL_EVAL_OUTPUT: http_response_builder_body(req->builder, (HTTP_String) { result.str.ptr, result.str.len }); break; default: break; } } }