From 288eccf43d21ce4d2164d06b256ef24274a1bc7f Mon Sep 17 00:00:00 2001 From: Francesco Cozzuto Date: Sat, 18 Oct 2025 11:53:21 +0200 Subject: [PATCH] Implement cweb_match_domain by wrapping http_match_host --- 3p/chttp.c | 10682 +++++++++++++++++++++---------------------- 3p/chttp.h | 1031 ++--- cweb.h | 524 +-- demo/main.c | 4 - demo/pages/post.wl | 4 +- src/main.c | 21 +- 6 files changed, 5689 insertions(+), 6577 deletions(-) diff --git a/3p/chttp.c b/3p/chttp.c index f8fd06f..13a0424 100644 --- a/3p/chttp.c +++ b/3p/chttp.c @@ -1,5559 +1,5123 @@ -#ifndef HTTP_NOINCLUDE -#include "chttp.h" -#endif - -//////////////////////////////////////////////////////////////////////////////////////// -// src/sec.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/sec.h" -#ifndef SEC_INCLUDED -#define SEC_INCLUDED - - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#endif - -#ifndef HTTPS_ENABLED - -typedef struct { -} SecureContext; - -#else - -#define MAX_CERTS 10 - -#include - -#include - -typedef struct { - char domain[128]; - SSL_CTX *ctx; -} CertData; - -typedef struct { - - bool is_server; - - SSL_CTX *ctx; - - // Only used when server - int num_certs; - CertData certs[MAX_CERTS]; - -} SecureContext; - -#endif - -void secure_context_global_init(void); -void secure_context_global_free(void); - -int secure_context_init_as_client(SecureContext *sec); - -int secure_context_init_as_server(SecureContext *sec, - HTTP_String cert_file, HTTP_String key_file); - -int secure_context_add_cert(SecureContext *sec, - HTTP_String domain, HTTP_String cert_file, - HTTP_String key_file); - -void secure_context_free(SecureContext *sec); - -#endif // SEC_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/socket_raw.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/socket_raw.h" -#ifndef SOCKET_RAW_INCLUDED -#define SOCKET_RAW_INCLUDED - -#include -#include - -#ifdef _WIN32 -#include -#define RAW_SOCKET SOCKET -#define BAD_SOCKET INVALID_SOCKET -#define POLL WSAPoll -#define CLOSE_SOCKET closesocket -#endif - -#ifdef __linux__ -#include -#include -#define RAW_SOCKET int -#define BAD_SOCKET -1 -#define POLL poll -#define CLOSE_SOCKET close -#endif - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#endif - -int socket_raw_global_init(void); -void socket_raw_global_free(void); - -int set_socket_blocking(RAW_SOCKET sock, bool value); - -RAW_SOCKET listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int backlog); - -#endif // SOCKET_RAW_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/socket.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/socket.h" -#ifndef SOCKET_INCLUDED -#define SOCKET_INCLUDED - -// This module implements the socket state machine to encapsulate -// the complexity of non-blocking TCP and TLS sockets. -// -// A socket is represented by the "Socket" structure, which may -// be in a number of states. As far as an user of the interface -// is concerned, the socket may be DIED, READY, or in an internal -// state that requires waiting for an event. Therefore, if the -// socket is not DIED or READY, the user needs to wait for the -// events specified in the [socket->events] field, then call the -// socket_update function. At some point the socket will become -// either READY or DIED. -// -// When the socket reaches the DIED state, the user must call -// socket_free. -// -// If the socket is ESTABLISHED_READY, the user may call socket_read, -// socket_write, or socket_close on it. - -#ifndef HTTP_AMALGAMATION -#include "sec.h" -#include "parse.h" -#include "socket_raw.h" -#endif - -typedef struct PendingConnect PendingConnect; - -// These should only be relevant to socket.c -typedef enum { - SOCKET_STATE_FREE, - SOCKET_STATE_DIED, - SOCKET_STATE_ESTABLISHED_WAIT, - SOCKET_STATE_ESTABLISHED_READY, - SOCKET_STATE_PENDING, - SOCKET_STATE_ACCEPTED, - SOCKET_STATE_CONNECTED, - SOCKET_STATE_CONNECTING, - SOCKET_STATE_SHUTDOWN, -} SocketState; - -typedef struct { - SocketState state; - - RAW_SOCKET raw; - int events; - - void *user_data; - PendingConnect *pending_connect; - -#ifdef HTTPS_ENABLED - SSL *ssl; -#endif - - SecureContext *sec; - -} Socket; - -void socket_connect(Socket *sock, SecureContext *sec, HTTP_String hostname, uint16_t port, void *user_data); -void socket_connect_ipv4(Socket *sock, SecureContext *sec, HTTP_IPv4 addr, uint16_t port, void *user_data); -void socket_connect_ipv6(Socket *sock, SecureContext *sec, HTTP_IPv6 addr, uint16_t port, void *user_data); -void socket_accept(Socket *sock, SecureContext *sec, RAW_SOCKET raw); -void socket_update(Socket *sock); -void socket_close(Socket *sock); -bool socket_ready(Socket *sock); -bool socket_died(Socket *sock); -int socket_read(Socket *sock, char *dst, int max); -int socket_write(Socket *sock, char *src, int len); -void socket_free(Socket *sock); -bool socket_secure(Socket *sock); -void socket_set_user_data(Socket *sock, void *user_data); -void* socket_get_user_data(Socket *sock); - -#endif // SOCKET_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/socket_pool.h -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/socket_pool.h" -#ifndef SOCKET_POOL_INCLUDED -#define SOCKET_POOL_INCLUDED - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#include "socket.h" -#include "socket_raw.h" -#endif - -typedef struct SocketPool SocketPool; - -typedef int SocketHandle; - -typedef enum { - SOCKET_EVENT_DIED, - SOCKET_EVENT_READY, - SOCKET_EVENT_ERROR, - SOCKET_EVENT_SIGNAL, -} SocketEventType; - -typedef struct { - SocketEventType type; - SocketHandle handle; - void *user_data; -} SocketEvent; - -int socket_pool_global_init(void); -void socket_pool_global_free(void); - -SocketPool *socket_pool_init(HTTP_String addr, - uint16_t port, uint16_t secure_port, int max_socks, - bool reuse_addr, int backlog, HTTP_String cert_file, - HTTP_String key_file); - -void socket_pool_free(SocketPool *pool); - -int socket_pool_add_cert(SocketPool *pool, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file); - -SocketEvent socket_pool_wait(SocketPool *pool); - -void socket_pool_set_user_data(SocketPool *pool, SocketHandle handle, void *user_data); - -void socket_pool_close(SocketPool *pool, SocketHandle handle); - -int socket_pool_connect(SocketPool *pool, bool secure, - HTTP_String addr, uint16_t port, void *user_data); - -int socket_pool_connect_ipv4(SocketPool *pool, bool secure, - HTTP_IPv4 addr, uint16_t port, void *user_data); - -int socket_pool_connect_ipv6(SocketPool *pool, bool secure, - HTTP_IPv6 addr, uint16_t port, void *user_data); - -int socket_pool_read(SocketPool *pool, SocketHandle handle, char *dst, int len); - -int socket_pool_write(SocketPool *pool, SocketHandle handle, char *src, int len); - -bool socket_pool_secure(SocketPool *pool, SocketHandle handle); - -#endif // SOCKET_POOL_INCLUDED - -//////////////////////////////////////////////////////////////////////////////////////// -// src/basic.c -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/basic.c" -#include -#include - -#ifndef HTTP_AMALGAMATION -#include "basic.h" -#endif - -bool http_streq(HTTP_String s1, HTTP_String s2) -{ - if (s1.len != s2.len) - return false; - - for (int i = 0; i < s1.len; i++) - if (s1.ptr[i] != s2.ptr[i]) - return false; - - return true; -} - -static char to_lower(char c) -{ - if (c >= 'A' && c <= 'Z') - return c - 'A' + 'a'; - return c; -} - -bool http_streqcase(HTTP_String s1, HTTP_String s2) -{ - if (s1.len != s2.len) - return false; - - for (int i = 0; i < s1.len; i++) - if (to_lower(s1.ptr[i]) != to_lower(s2.ptr[i])) - return false; - - return true; -} - -HTTP_String http_trim(HTTP_String s) -{ - int i = 0; - while (i < s.len && (s.ptr[i] == ' ' || s.ptr[i] == '\t')) - i++; - - if (i == s.len) { - s.ptr = NULL; - s.len = 0; - } else { - s.ptr += i; - s.len -= i; - while (s.ptr[s.len-1] == ' ' || s.ptr[s.len-1] == '\t') - s.len--; - } - - return s; -} - -static bool is_printable(char c) -{ - return c >= ' ' && c <= '~'; -} - -#include -void print_bytes(HTTP_String prefix, HTTP_String src) -{ - if (src.len == 0) - return; - - FILE *stream = stdout; - - bool new_line = true; - int cur = 0; - for (;;) { - int start = cur; - - while (cur < src.len && is_printable(src.ptr[cur])) - cur++; - - if (new_line) { - fwrite(prefix.ptr, 1, prefix.len, stream); - new_line = false; - } - - fwrite(src.ptr + start, 1, cur - start, stream); - - if (cur == src.len) - break; - - if (src.ptr[cur] == '\n') { - putc('\\', stream); - putc('n', stream); - putc('\n', stream); - new_line = true; - } else if (src.ptr[cur] == '\r') { - putc('\\', stream); - putc('r', stream); - } else { - putc('.', stream); - } - cur++; - } - putc('\n', stream); -} - -//////////////////////////////////////////////////////////////////////////////////////// -// src/parse.c -//////////////////////////////////////////////////////////////////////////////////////// - -#line 1 "src/parse.c" -#include -#include -#include -#include -#include -#include - -#ifndef HTTP_AMALGAMATION -#include "parse.h" -#include "basic.h" -#endif - -// From RFC 9112 -// request-target = origin-form -// / absolute-form -// / authority-form -// / asterisk-form -// origin-form = absolute-path [ "?" query ] -// absolute-form = absolute-URI -// authority-form = uri-host ":" port -// asterisk-form = "*" -// -// From RFC 9110 -// URI-reference = -// absolute-URI = -// relative-part = -// authority = -// uri-host = -// port = -// path-abempty = -// segment = -// query = -// -// absolute-path = 1*( "/" segment ) -// partial-URI = relative-part [ "?" query ] -// -// From RFC 3986: -// segment = *pchar -// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" -// pct-encoded = "%" HEXDIG HEXDIG -// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" -// / "*" / "+" / "," / ";" / "=" -// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" -// query = *( pchar / "/" / "?" ) -// absolute-URI = scheme ":" hier-part [ "?" query ] -// hier-part = "//" authority path-abempty -// / path-absolute -// / path-rootless -// / path-empty -// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) - -typedef struct { - char *src; - int len; - int cur; -} Scanner; - -static int is_digit(char c) -{ - return c >= '0' && c <= '9'; -} - -static int is_alpha(char c) -{ - return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); -} - -static int is_hex_digit(char c) -{ - return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); -} - -// From RFC 3986: -// sub-delims = "!" / "$" / "&" / "'" / "(" / ")" -// / "*" / "+" / "," / ";" / "=" -static int is_sub_delim(char c) -{ - return c == '!' || c == '$' || c == '&' || c == '\'' - || c == '(' || c == ')' || c == '*' || c == '+' - || c == ',' || c == ';' || c == '='; -} - -// From RFC 3986: -// unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" -static int is_unreserved(char c) -{ - return is_alpha(c) || is_digit(c) - || c == '-' || c == '.' - || c == '_' || c == '~'; -} - -// From RFC 3986: -// pchar = unreserved / pct-encoded / sub-delims / ":" / "@" -static int is_pchar(char c) -{ - return is_unreserved(c) || is_sub_delim(c) || c == ':' || c == '@'; -} - -static int is_tchar(char c) -{ - return is_digit(c) || is_alpha(c) - || c == '!' || c == '#' || c == '$' - || c == '%' || c == '&' || c == '\'' - || c == '*' || c == '+' || c == '-' - || c == '.' || c == '^' || c == '_' - || c == '~'; -} - -static int is_vchar(char c) -{ - return c >= ' ' && c <= '~'; -} - -#define CONSUME_OPTIONAL_SEQUENCE(scanner, func) \ - while ((scanner)->cur < (scanner)->len && (func)((scanner)->src[(scanner)->cur])) \ - (scanner)->cur++; - -static int -consume_absolute_path(Scanner *s) -{ - if (s->cur == s->len || s->src[s->cur] != '/') - return -1; // ERROR - s->cur++; - - for (;;) { - - CONSUME_OPTIONAL_SEQUENCE(s, is_pchar); - - if (s->cur == s->len || s->src[s->cur] != '/') - break; - s->cur++; - } - - return 0; -} - -// If abempty=1: -// path-abempty = *( "/" segment ) -// else: -// path-absolute = "/" [ segment-nz *( "/" segment ) ] -// path-rootless = segment-nz *( "/" segment ) -// path-empty = 0 -static int parse_path(Scanner *s, HTTP_String *path, int abempty) -{ - int start = s->cur; - - if (abempty) { - - // path-abempty - while (s->cur < s->len && s->src[s->cur] == '/') { - do - s->cur++; - while (s->cur < s->len && is_pchar(s->src[s->cur])); - } - - } else if (s->cur < s->len && (s->src[s->cur] == '/')) { - - // path-absolute - s->cur++; - if (s->cur < s->len && is_pchar(s->src[s->cur])) { - s->cur++; - for (;;) { - - CONSUME_OPTIONAL_SEQUENCE(s, is_pchar); - - if (s->cur == s->len || s->src[s->cur] != '/') - break; - s->cur++; - } - } - - } else if (s->cur < s->len && is_pchar(s->src[s->cur])) { - - // path-rootless - s->cur++; - for (;;) { - - CONSUME_OPTIONAL_SEQUENCE(s, is_pchar) - - if (s->cur == s->len || s->src[s->cur] != '/') - break; - s->cur++; - } - - } else { - // path->empty - // (do nothing) - } - - *path = (HTTP_String) { - s->src + start, - s->cur - start, - }; - if (path->len == 0) - path->ptr = NULL; - - return 0; -} - -// RFC 3986: -// query = *( pchar / "/" / "?" ) -static int is_query(char c) -{ - return is_pchar(c) || c == '/' || c == '?'; -} - -// RFC 3986: -// fragment = *( pchar / "/" / "?" ) -static int is_fragment(char c) -{ - return is_pchar(c) || c == '/' || c == '?'; -} - -static int little_endian(void) -{ - uint16_t x = 1; - return *((uint8_t*) &x); -} - -static void invert_bytes(void *p, int len) -{ - char *c = p; - for (int i = 0; i < len/2; i++) { - char tmp = c[i]; - c[i] = c[len-i-1]; - c[len-i-1] = tmp; - } -} - -static int parse_ipv4(Scanner *s, HTTP_IPv4 *ipv4) -{ - unsigned int out = 0; - int i = 0; - for (;;) { - - if (s->cur == s->len || !is_digit(s->src[s->cur])) - return -1; - - int b = 0; - do { - int x = s->src[s->cur++] - '0'; - if (b > (UINT8_MAX - x) / 10) - return -1; - b = b * 10 + x; - } while (s->cur < s->len && is_digit(s->src[s->cur])); - - out <<= 8; - out |= (unsigned char) b; - - i++; - if (i == 4) - break; - - if (s->cur == s->len || s->src[s->cur] != '.') - return -1; - s->cur++; - } - - if (little_endian()) - invert_bytes(&out, 4); - - ipv4->data = out; - return 0; -} - -static int hex_digit_to_int(char c) -{ - if (c >= 'a' && c <= 'f') return c - 'a' + 10; - if (c >= 'A' && c <= 'F') return c - 'A' + 10; - if (c >= '0' && c <= '9') return c - '0'; - return -1; -} - -static int parse_ipv6_comp(Scanner *s) -{ - unsigned short buf; - - if (s->cur == s->len || !is_hex_digit(s->src[s->cur])) - return -1; - buf = hex_digit_to_int(s->src[s->cur]); - s->cur++; - - if (s->cur == s->len || !is_hex_digit(s->src[s->cur])) - return buf; - buf <<= 4; - buf |= hex_digit_to_int(s->src[s->cur]); - s->cur++; - - if (s->cur == s->len || !is_hex_digit(s->src[s->cur])) - return buf; - buf <<= 4; - buf |= hex_digit_to_int(s->src[s->cur]); - s->cur++; - - if (s->cur == s->len || !is_hex_digit(s->src[s->cur])) - return buf; - buf <<= 4; - buf |= hex_digit_to_int(s->src[s->cur]); - s->cur++; - - return (int) buf; -} - -static int parse_ipv6(Scanner *s, HTTP_IPv6 *ipv6) -{ - unsigned short head[8]; - unsigned short tail[8]; - int head_len = 0; - int tail_len = 0; - - if (s->len - s->cur > 1 - && s->src[s->cur+0] == ':' - && s->src[s->cur+1] == ':') - s->cur += 2; - else { - - for (;;) { - - int ret = parse_ipv6_comp(s); - if (ret < 0) return ret; - - head[head_len++] = (unsigned short) ret; - if (head_len == 8) break; - - if (s->cur == s->len || s->src[s->cur] != ':') - return -1; - s->cur++; - - if (s->cur < s->len && s->src[s->cur] == ':') { - s->cur++; - break; - } - } - } - - if (head_len < 8) { - while (s->cur < s->len && is_hex_digit(s->src[s->cur])) { - - int ret = parse_ipv6_comp(s); - if (ret < 0) return ret; - - tail[tail_len++] = (unsigned short) ret; - if (head_len + tail_len == 8) break; - - if (s->cur == s->len || s->src[s->cur] != ':') - break; - s->cur++; - } - } - - for (int i = 0; i < head_len; i++) - ipv6->data[i] = head[i]; - - for (int i = 0; i < 8 - head_len - tail_len; i++) - ipv6->data[head_len + i] = 0; - - for (int i = 0; i < tail_len; i++) - ipv6->data[8 - tail_len + i] = tail[i]; - - if (little_endian()) - for (int i = 0; i < 8; i++) - invert_bytes(&ipv6->data[i], 2); - - return 0; -} - -// From RFC 3986: -// reg-name = *( unreserved / pct-encoded / sub-delims ) -static int is_regname(char c) -{ - return is_unreserved(c) || is_sub_delim(c); -} - -static int parse_regname(Scanner *s, HTTP_String *regname) -{ - if (s->cur == s->len || !is_regname(s->src[s->cur])) - return -1; - int start = s->cur; - do - s->cur++; - while (s->cur < s->len && is_regname(s->src[s->cur])); - regname->ptr = s->src + start; - regname->len = s->cur - start; - return 0; -} - -static int parse_host(Scanner *s, HTTP_Host *host) -{ - int ret; - if (s->cur < s->len && s->src[s->cur] == '[') { - - s->cur++; - - int start = s->cur; - HTTP_IPv6 ipv6; - ret = parse_ipv6(s, &ipv6); - if (ret < 0) return ret; - - host->mode = HTTP_HOST_MODE_IPV6; - host->ipv6 = ipv6; - host->text = (HTTP_String) { s->src + start, s->cur - start }; - - if (s->cur == s->len || s->src[s->cur] != ']') - return -1; - s->cur++; - - } else { - - int start = s->cur; - HTTP_IPv4 ipv4; - ret = parse_ipv4(s, &ipv4); - if (ret >= 0) { - host->mode = HTTP_HOST_MODE_IPV4; - host->ipv4 = ipv4; - } else { - s->cur = start; - - HTTP_String regname; - ret = parse_regname(s, ®name); - if (ret < 0) return ret; - - host->mode = HTTP_HOST_MODE_NAME; - host->name = regname; - } - host->text = (HTTP_String) { s->src + start, s->cur - start }; - } - - return 0; -} - -// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) -static int is_scheme_head(char c) -{ - return is_alpha(c); -} - -static int is_scheme_body(char c) -{ - return is_alpha(c) - || is_digit(c) - || c == '+' - || c == '-' - || c == '.'; -} - -// userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) -static int is_userinfo(char c) -{ - return is_unreserved(c) || is_sub_delim(c) || c == ':'; // TODO: PCT encoded -} - -// authority = [ userinfo "@" ] host [ ":" port ] -static int parse_authority(Scanner *s, HTTP_Authority *authority) -{ - HTTP_String userinfo; - { - int start = s->cur; - - CONSUME_OPTIONAL_SEQUENCE(s, is_userinfo); - - if (s->cur < s->len && s->src[s->cur] == '@') { - userinfo = (HTTP_String) { - s->src + start, - s->cur - start - }; - s->cur++; - } else { - // Rollback - s->cur = start; - userinfo = (HTTP_String) {NULL, 0}; - } - } - - HTTP_Host host; - { - int ret = parse_host(s, &host); - if (ret < 0) - return ret; - } - - int port = 0; - if (s->cur < s->len && s->src[s->cur] == ':') { - s->cur++; - if (s->cur < s->len && is_digit(s->src[s->cur])) { - port = s->src[s->cur++] - '0'; - while (s->cur < s->len && is_digit(s->src[s->cur])) { - int x = s->src[s->cur++] - '0'; - if (port > (UINT16_MAX - x) / 10) - return -1; // ERROR: Port too big - port = port * 10 + x; - } - } - } - - authority->userinfo = userinfo; - authority->host = host; - authority->port = port; - return 0; -} - -static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment) -{ - HTTP_String scheme = {0}; - { - int start = s->cur; - if (s->cur == s->len || !is_scheme_head(s->src[s->cur])) - return -1; // ERROR: Missing scheme - do - s->cur++; - while (s->cur < s->len && is_scheme_body(s->src[s->cur])); - scheme = (HTTP_String) { - s->src + start, - s->cur - start, - }; - - if (s->cur == s->len || s->src[s->cur] != ':') - return -1; // ERROR: Missing ':' after scheme - s->cur++; - } - - int abempty = 0; - HTTP_Authority authority = {0}; - if (s->len - s->cur > 1 - && s->src[s->cur+0] == '/' - && s->src[s->cur+1] == '/') { - - s->cur += 2; - - int ret = parse_authority(s, &authority); - if (ret < 0) return ret; - - abempty = 1; - } - - HTTP_String path; - int ret = parse_path(s, &path, abempty); - if (ret < 0) return ret; - - HTTP_String query = {0}; - if (s->cur < s->len && s->src[s->cur] == '?') { - int start = s->cur; - do - s->cur++; - while (s->cur < s->len && is_query(s->src[s->cur])); - query = (HTTP_String) { - s->src + start, - s->cur - start, - }; - } - - HTTP_String fragment = {0}; - if (allow_fragment && s->cur < s->len && s->src[s->cur] == '#') { - int start = s->cur; - do - s->cur++; - while (s->cur < s->len && is_fragment(s->src[s->cur])); - fragment = (HTTP_String) { - s->src + start, - s->cur - start, - }; - } - - url->scheme = scheme; - url->authority = authority; - url->path = path; - url->query = query; - url->fragment = fragment; - - return 1; -} - -// authority-form = host ":" port -// host = IP-literal / IPv4address / reg-name -// IP-literal = "[" ( IPv6address / IPvFuture ) "]" -// reg-name = *( unreserved / pct-encoded / sub-delims ) -static int parse_authority_form(Scanner *s, HTTP_Host *host, int *port) -{ - int ret; - - ret = parse_host(s, host); - if (ret < 0) return ret; - - // Default port value - *port = 0; - - if (s->cur == s->len || s->src[s->cur] != ':') - return 0; // No port - s->cur++; - - if (s->cur == s->len || !is_digit(s->src[s->cur])) - return 0; // No port - - int buf = 0; - do { - int x = s->src[s->cur++] - '0'; - if (buf > (UINT16_MAX - x) / 10) - return -1; // ERROR - buf = buf * 10 + x; - } while (s->cur < s->len && is_digit(s->src[s->cur])); - - *port = buf; - return 0; -} - -static int parse_origin_form(Scanner *s, HTTP_String *path, HTTP_String *query) -{ - int ret, start; - - start = s->cur; - ret = consume_absolute_path(s); - if (ret < 0) return ret; - *path = (HTTP_String) { s->src + start, s->cur - start }; - - if (s->cur < s->len && s->src[s->cur] == '?') { - start = s->cur; - do - s->cur++; - while (s->cur < s->len && is_query(s->src[s->cur])); - *query = (HTTP_String) { s->src + start, s->cur - start }; - } else - *query = (HTTP_String) { NULL, 0 }; - - return 0; -} - -static int parse_asterisk_form(Scanner *s) -{ - if (s->len - s->cur < 2 - || s->src[s->cur+0] != '*' - || s->src[s->cur+1] != ' ') - return -1; - s->cur++; - return 0; -} - -static int parse_request_target(Scanner *s, HTTP_URL *url) -{ - int ret; - - memset(url, 0, sizeof(HTTP_URL)); - - // asterisk-form - ret = parse_asterisk_form(s); - if (ret >= 0) return ret; - - ret = parse_uri(s, url, 0); - if (ret >= 0) return ret; - - ret = parse_authority_form(s, &url->authority.host, &url->authority.port); - if (ret >= 0) return ret; - - ret = parse_origin_form(s, &url->path, &url->query); - if (ret >= 0) return ret; - - return -1; -} - -bool consume_str(Scanner *scan, HTTP_String token) -{ - HTTP_ASSERT(token.len > 0); - - if (token.len > scan->len - scan->cur) - return false; - - for (int i = 0; i < token.len; i++) - if (scan->src[scan->cur + i] != token.ptr[i]) - return false; - - scan->cur += token.len; - return true; -} - -static int is_header_body(char c) -{ - return is_vchar(c) || c == ' ' || c == '\t'; -} - -static int parse_headers(Scanner *s, HTTP_Header *headers, int max_headers) -{ - int num_headers = 0; - while (!consume_str(s, HTTP_STR("\r\n"))) { - - // RFC 9112: - // field-line = field-name ":" OWS field-value OWS - // - // RFC 9110: - // field-value = *field-content - // field-content = field-vchar - // [ 1*( SP / HTAB / field-vchar ) field-vchar ] - // field-vchar = VCHAR / obs-text - // obs-text = %x80-FF - - int start; - - if (s->cur == s->len || !is_tchar(s->src[s->cur])) - return -1; // ERROR - start = s->cur; - do - s->cur++; - while (s->cur < s->len && is_tchar(s->src[s->cur])); - HTTP_String name = { s->src + start, s->cur - start }; - - if (s->cur == s->len || s->src[s->cur] != ':') - return -1; // ERROR - s->cur++; - - start = s->cur; - CONSUME_OPTIONAL_SEQUENCE(s, is_header_body); - HTTP_String body = { s->src + start, s->cur - start }; - body = http_trim(body); - - if (num_headers < max_headers) - headers[num_headers++] = (HTTP_Header) { name, body }; - - if (!consume_str(s, HTTP_STR("\r\n"))) { - return -1; - } - } - - return num_headers; -} - -typedef enum { - TRANSFER_ENCODING_OPTION_CHUNKED, - TRANSFER_ENCODING_OPTION_COMPRESS, - TRANSFER_ENCODING_OPTION_DEFLATE, - TRANSFER_ENCODING_OPTION_GZIP, -} TransferEncodingOption; - -static bool is_space(char c) -{ - return c == ' ' || c == '\t'; -} - -static int -parse_transfer_encoding(HTTP_String src, TransferEncodingOption *dst, int max) -{ - Scanner s = { src.ptr, src.len, 0 }; - - int num = 0; - for (;;) { - - CONSUME_OPTIONAL_SEQUENCE(&s, is_space); - - TransferEncodingOption opt; - if (0) {} - else if (consume_str(&s, HTTP_STR("chunked"))) opt = TRANSFER_ENCODING_OPTION_CHUNKED; - else if (consume_str(&s, HTTP_STR("compress"))) opt = TRANSFER_ENCODING_OPTION_COMPRESS; - else if (consume_str(&s, HTTP_STR("deflate"))) opt = TRANSFER_ENCODING_OPTION_DEFLATE; - else if (consume_str(&s, HTTP_STR("gzip"))) opt = TRANSFER_ENCODING_OPTION_GZIP; - else return -1; // Invalid option - - if (num == max) - return -1; // Too many options - dst[num++] = opt; - - CONSUME_OPTIONAL_SEQUENCE(&s, is_space); - - if (s.cur == s.len) - break; - - if (s.src[s.cur] != ',') - return -1; // Missing comma separator - } - - return num; -} - -static int -parse_content_length(const char *src, int len, uint64_t *out) -{ - int cur = 0; - while (cur < len && (src[cur] == ' ' || src[cur] == '\t')) - cur++; - - if (cur == len || !is_digit(src[cur])) - return -1; - - uint64_t buf = 0; - do { - int d = src[cur++] - '0'; - if (buf > (UINT64_MAX - d) / 10) - return -1; - buf = buf * 10 + d; - } while (cur < len && is_digit(src[cur])); - - *out = buf; - return 0; -} - -static int parse_body(Scanner *s, - HTTP_Header *headers, int num_headers, - HTTP_String *body, bool body_expected) -{ - - // RFC 9112 section 6: - // The presence of a message body in a request is signaled by a Content-Length or - // Transfer-Encoding header field. Request message framing is independent of method - // semantics. - - int header_index = http_find_header(headers, num_headers, HTTP_STR("Transfer-Encoding")); - if (header_index != -1) { - - // RFC 9112 section 6.1: - // A server MAY reject a request that contains both Content-Length and Transfer-Encoding - // or process such a request in accordance with the Transfer-Encoding alone. Regardless, - // the server MUST close the connection after responding to such a request to avoid the - // potential attacks. - if (http_find_header(headers, num_headers, HTTP_STR("Content-Length")) != -1) - return -1; - - HTTP_String value = headers[header_index].value; - - // RFC 9112 section 6.1: - // If any transfer coding other than chunked is applied to a request's content, the - // sender MUST apply chunked as the final transfer coding to ensure that the message - // is properly framed. If any transfer coding other than chunked is applied to a - // response's content, the sender MUST either apply chunked as the final transfer - // coding or terminate the message by closing the connection. - - TransferEncodingOption opts[8]; - int num = parse_transfer_encoding(value, opts, HTTP_COUNT(opts)); - if (num != 1 || opts[0] != TRANSFER_ENCODING_OPTION_CHUNKED) - return -1; - - HTTP_String chunks_maybe[128]; - HTTP_String *chunks = chunks_maybe; - int num_chunks = 0; - int max_chunks = HTTP_COUNT(chunks_maybe); - - #define FREE_CHUNK_LIST \ - if (chunks != chunks_maybe) \ - free(chunks); - - char *content_start = s->src + s->cur; - - for (;;) { - - // RFC 9112 section 7.1: - // The chunked transfer coding wraps content in order to transfer it as a series of chunks, - // each with its own size indicator, followed by an OPTIONAL trailer section containing - // trailer fields. - - if (s->cur == s->len) { - FREE_CHUNK_LIST - return 0; // Incomplete request - } - - if (!is_hex_digit(s->src[s->cur])) { - FREE_CHUNK_LIST - return -1; - } - - int chunk_len = 0; - - do { - char c = s->src[s->cur++]; - int n = hex_digit_to_int(c); - if (chunk_len > (INT_MAX - n) / 16) { - FREE_CHUNK_LIST - return -1; // overflow - } - chunk_len = chunk_len * 16 + n; - } while (s->cur < s->len && is_hex_digit(s->src[s->cur])); - - if (s->cur == s->len) { - FREE_CHUNK_LIST - return 0; // Incomplete request - } - if (s->src[s->cur] != '\r') { - FREE_CHUNK_LIST - return -1; - } - s->cur++; - - if (s->cur == s->len) { - FREE_CHUNK_LIST - return 0; - } - if (s->src[s->cur] != '\n') { - FREE_CHUNK_LIST - return -1; - } - s->cur++; - - char *chunk_ptr = s->src + s->cur; - - if (chunk_len > s->len - s->cur) { - FREE_CHUNK_LIST - return 0; // Incomplete request - } - s->cur += chunk_len; - - if (s->cur == s->len) - return 0; // Incomplete request - if (s->src[s->cur] != '\r') { - FREE_CHUNK_LIST - return -1; - } - s->cur++; - - if (s->cur == s->len) { - FREE_CHUNK_LIST - return 0; // Incomplete request - } - if (s->src[s->cur] != '\n') { - FREE_CHUNK_LIST - return -1; - } - s->cur++; - - if (chunk_len == 0) - break; - - if (num_chunks == max_chunks) { - - max_chunks *= 2; - - HTTP_String *new_chunks = malloc(max_chunks * sizeof(HTTP_String)); - if (new_chunks == NULL) { - if (chunks != chunks_maybe) - free(chunks); - return -1; - } - - for (int i = 0; i < num_chunks; i++) - new_chunks[i] = chunks[i]; - - if (chunks != chunks_maybe) - free(chunks); - - chunks = new_chunks; - } - chunks[num_chunks++] = (HTTP_String) { chunk_ptr, chunk_len }; - } - - char *content_ptr = content_start; - for (int i = 0; i < num_chunks; i++) { - memmove(content_ptr, chunks[i].ptr, chunks[i].len); - content_ptr += chunks[i].len; - } - - *body = (HTTP_String) { - content_start, - content_ptr - content_start - }; - - if (chunks != chunks_maybe) - free(chunks); - - return 1; - } - - // RFC 9112 section 6.3: - // If a valid Content-Length header field is present without Transfer-Encoding, - // its decimal value defines the expected message body length in octets. - - header_index = http_find_header(headers, num_headers, HTTP_STR("Content-Length")); - if (header_index != -1) { - - // Have Content-Length - HTTP_String value = headers[header_index].value; - - uint64_t tmp; - if (parse_content_length(value.ptr, value.len, &tmp) < 0) - return -1; - if (tmp > INT_MAX) - return -1; - int len = (int) tmp; - - if (len > s->len - s->cur) - return 0; // Incomplete request - - *body = (HTTP_String) { s->src + s->cur, len }; - - s->cur += len; - return 1; - } - - // No Content-Length or Transfer-Encoding - if (body_expected) return -1; - - *body = (HTTP_String) { NULL, 0 }; - return 1; -} - -static int contains_head(char *src, int len) -{ - int cur = 0; - while (len - cur > 3) { - if (src[cur+0] == '\r' && - src[cur+1] == '\n' && - src[cur+2] == '\r' && - src[cur+3] == '\n') - return 1; - cur++; - } - return 0; -} - -static int parse_request(Scanner *s, HTTP_Request *req) -{ - if (!contains_head(s->src + s->cur, s->len - s->cur)) - return 0; - - req->secure = false; - - if (0) {} - else if (consume_str(s, HTTP_STR("GET "))) req->method = HTTP_METHOD_GET; - else if (consume_str(s, HTTP_STR("POST "))) req->method = HTTP_METHOD_POST; - else if (consume_str(s, HTTP_STR("PUT "))) req->method = HTTP_METHOD_PUT; - else if (consume_str(s, HTTP_STR("HEAD "))) req->method = HTTP_METHOD_HEAD; - else if (consume_str(s, HTTP_STR("DELETE "))) req->method = HTTP_METHOD_DELETE; - else if (consume_str(s, HTTP_STR("CONNECT "))) req->method = HTTP_METHOD_CONNECT; - else if (consume_str(s, HTTP_STR("OPTIONS "))) req->method = HTTP_METHOD_OPTIONS; - else if (consume_str(s, HTTP_STR("TRACE "))) req->method = HTTP_METHOD_TRACE; - else if (consume_str(s, HTTP_STR("PATCH "))) req->method = HTTP_METHOD_PATCH; - else return -1; - - { - Scanner s2 = *s; - int peek = s->cur; - while (peek < s->len && s->src[peek] != ' ') - peek++; - if (peek == s->len) - return -1; - s2.len = peek; - - int ret = parse_request_target(&s2, &req->url); - if (ret < 0) return ret; - - s->cur = s2.cur; - } - - if (consume_str(s, HTTP_STR(" HTTP/1.1\r\n"))) { - req->minor = 1; - } else if (consume_str(s, HTTP_STR(" HTTP/1.0\r\n")) || consume_str(s, HTTP_STR(" HTTP/1\r\n"))) { - req->minor = 0; - } else { - return -1; - } - - int num_headers = parse_headers(s, req->headers, HTTP_MAX_HEADERS); - if (num_headers < 0) - return num_headers; - req->num_headers = num_headers; - - bool body_expected = true; - if (req->method == HTTP_METHOD_GET || req->method == HTTP_METHOD_DELETE) // TODO: maybe other methods? - body_expected = false; - - return parse_body(s, req->headers, req->num_headers, &req->body, body_expected); -} - -int http_find_header(HTTP_Header *headers, int num_headers, HTTP_String name) -{ - for (int i = 0; i < num_headers; i++) - if (http_streqcase(name, headers[i].name)) - return i; - return -1; -} - -static int parse_response(Scanner *s, HTTP_Response *res) -{ - if (!contains_head(s->src + s->cur, s->len - s->cur)) - return 0; - - if (consume_str(s, HTTP_STR("HTTP/1.1 "))) { - res->minor = 1; - } else if (consume_str(s, HTTP_STR("HTTP/1.0 ")) || consume_str(s, HTTP_STR("HTTP/1 "))) { - res->minor = 0; - } else { - return -1; - } - - if (s->len - s->cur < 5 - || s->src[s->cur+0] != ' ' - || !is_digit(s->src[s->cur+1]) - || !is_digit(s->src[s->cur+2]) - || !is_digit(s->src[s->cur+3]) - || s->src[s->cur+4] != ' ') - return -1; - s->cur += 5; - - res->status = - (s->src[s->cur-2] - '0') * 1 + - (s->src[s->cur-3] - '0') * 10 + - (s->src[s->cur-4] - '0') * 100; - - while (s->cur < s->len && ( - s->src[s->cur] == '\t' || - s->src[s->cur] == ' ' || - is_vchar(s->src[s->cur]))) // TODO: obs-text - s->cur++; - - if (s->len - s->cur < 2 - || s->src[s->cur+0] != '\r' - || s->src[s->cur+1] != '\n') - return -1; - s->cur += 2; - - int num_headers = parse_headers(s, res->headers, HTTP_MAX_HEADERS); - if (num_headers < 0) - return num_headers; - res->num_headers = num_headers; - - bool body_expected = true; // TODO - - return parse_body(s, res->headers, res->num_headers, &res->body, body_expected); -} - -int http_parse_ipv4(char *src, int len, HTTP_IPv4 *ipv4) -{ - Scanner s = {src, len, 0}; - int ret = parse_ipv4(&s, ipv4); - if (ret < 0) return ret; - return s.cur; -} - -int http_parse_ipv6(char *src, int len, HTTP_IPv6 *ipv6) -{ - Scanner s = {src, len, 0}; - int ret = parse_ipv6(&s, ipv6); - if (ret < 0) return ret; - return s.cur; -} - -int http_parse_url(char *src, int len, HTTP_URL *url) -{ - Scanner s = {src, len, 0}; - int ret = parse_uri(&s, url, 1); - if (ret == 1) - return s.cur; - return ret; -} - -int http_parse_request(char *src, int len, HTTP_Request *req) -{ - Scanner s = {src, len, 0}; - int ret = parse_request(&s, req); - if (ret == 1) - return s.cur; - return ret; -} - -int http_parse_response(char *src, int len, HTTP_Response *res) -{ - Scanner s = {src, len, 0}; - int ret = parse_response(&s, res); - if (ret == 1) - return s.cur; - return ret; -} - -HTTP_String http_get_cookie(HTTP_Request *req, HTTP_String name) -{ - // TODO: best-effort implementation - - for (int i = 0; i < req->num_headers; i++) { - - if (!http_streqcase(req->headers[i].name, HTTP_STR("Cookie"))) - continue; - - char *src = req->headers[i].value.ptr; - int len = req->headers[i].value.len; - int cur = 0; - - // Cookie: name1=value1; name2=value2; name3=value3 - - for (;;) { - - while (cur < len && src[cur] == ' ') - cur++; - - int off = cur; - while (cur < len && src[cur] != '=') - cur++; - - HTTP_String cookie_name = { src + off, cur - off }; - - if (cur == len) - break; - cur++; - - off = cur; - while (cur < len && src[cur] != ';') - cur++; - - HTTP_String cookie_value = { src + off, cur - off }; - - if (http_streq(name, cookie_name)) - return cookie_value; - - if (cur == len) - break; - cur++; - } - } - - return HTTP_STR(""); -} - -HTTP_String http_get_param(HTTP_String body, HTTP_String str, char *mem, int cap) -{ - // This is just a best-effort implementation - - char *src = body.ptr; - int len = body.len; - int cur = 0; - - if (cur < len && src[cur] == '?') - cur++; - - while (cur < len) { - - HTTP_String name; - { - int off = cur; - while (cur < len && src[cur] != '=' && src[cur] != '&') - cur++; - name = (HTTP_String) { src + off, cur - off }; - } - - HTTP_String body = HTTP_STR(""); - if (cur < len) { - cur++; - if (src[cur-1] == '=') { - int off = cur; - while (cur < len && src[cur] != '&') - cur++; - body = (HTTP_String) { src + off, cur - off }; - - if (cur < len) - cur++; - } - } - - if (http_streq(str, name)) { - - bool percent_encoded = false; - for (int i = 0; i < body.len; i++) - if (body.ptr[i] == '+' || body.ptr[i] == '%') { - percent_encoded = true; - break; - } - - if (!percent_encoded) - return body; - - if (body.len > cap) - return (HTTP_String) { NULL, 0 }; - - HTTP_String decoded = { mem, 0 }; - for (int i = 0; i < body.len; i++) { - - char c = body.ptr[i]; - if (c == '+') - c = ' '; - else { - if (body.ptr[i] == '%') { - if (body.len - i < 3 - || !is_hex_digit(body.ptr[i+1]) - || !is_hex_digit(body.ptr[i+2])) - return (HTTP_String) { NULL, 0 }; - - int h = hex_digit_to_int(body.ptr[i+1]); - int l = hex_digit_to_int(body.ptr[i+2]); - c = (h << 4) | l; - - i += 2; - } - } - - decoded.ptr[decoded.len++] = c; - } - - return decoded; - } - } - - return HTTP_STR(""); -} - -int http_get_param_i(HTTP_String body, HTTP_String str) -{ - char buf[128]; - HTTP_String out = http_get_param(body, str, buf, (int) 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; - if (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; -} +#ifndef HTTP_NOINCLUDE +#include "chttp.h" +#endif + +//////////////////////////////////////////////////////////////////////////////////////// +// src/sec.h +//////////////////////////////////////////////////////////////////////////////////////// + +#line 1 "src/sec.h" +#ifndef SEC_INCLUDED +#define SEC_INCLUDED + + +#ifndef HTTP_AMALGAMATION +#include "basic.h" +#endif + +#ifndef HTTPS_ENABLED + +typedef struct { +} SecureContext; + +#else + +#define MAX_CERTS 10 + +#include + +#include + +typedef struct { + char domain[128]; + SSL_CTX *ctx; +} CertData; + +typedef struct { + + bool is_server; + + SSL_CTX *ctx; + + // Only used when server + int num_certs; + CertData certs[MAX_CERTS]; + +} SecureContext; + +#endif + +void secure_context_global_init(void); +void secure_context_global_free(void); + +int secure_context_init_as_client(SecureContext *sec); + +int secure_context_init_as_server(SecureContext *sec, + HTTP_String cert_file, HTTP_String key_file); + +int secure_context_add_cert(SecureContext *sec, + HTTP_String domain, HTTP_String cert_file, + HTTP_String key_file); + +void secure_context_free(SecureContext *sec); + +#endif // SEC_INCLUDED + +//////////////////////////////////////////////////////////////////////////////////////// +// src/socket_raw.h +//////////////////////////////////////////////////////////////////////////////////////// + +#line 1 "src/socket_raw.h" +#ifndef SOCKET_RAW_INCLUDED +#define SOCKET_RAW_INCLUDED + +#include +#include + +#ifdef _WIN32 +#include +#define RAW_SOCKET SOCKET +#define BAD_SOCKET INVALID_SOCKET +#define POLL WSAPoll +#define CLOSE_SOCKET closesocket +#endif + +#ifdef __linux__ +#include +#include +#define RAW_SOCKET int +#define BAD_SOCKET -1 +#define POLL poll +#define CLOSE_SOCKET close +#endif + +#ifndef HTTP_AMALGAMATION +#include "basic.h" +#endif + +int socket_raw_global_init(void); +void socket_raw_global_free(void); + +int set_socket_blocking(RAW_SOCKET sock, bool value); + +RAW_SOCKET listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int backlog); + +#endif // SOCKET_RAW_INCLUDED + +//////////////////////////////////////////////////////////////////////////////////////// +// src/socket.h +//////////////////////////////////////////////////////////////////////////////////////// + +#line 1 "src/socket.h" +#ifndef SOCKET_INCLUDED +#define SOCKET_INCLUDED + +// This module implements the socket state machine to encapsulate +// the complexity of non-blocking TCP and TLS sockets. +// +// A socket is represented by the "Socket" structure, which may +// be in a number of states. As far as an user of the interface +// is concerned, the socket may be DIED, READY, or in an internal +// state that requires waiting for an event. Therefore, if the +// socket is not DIED or READY, the user needs to wait for the +// events specified in the [socket->events] field, then call the +// socket_update function. At some point the socket will become +// either READY or DIED. +// +// When the socket reaches the DIED state, the user must call +// socket_free. +// +// If the socket is ESTABLISHED_READY, the user may call socket_read, +// socket_write, or socket_close on it. + +#ifndef HTTP_AMALGAMATION +#include "sec.h" +#include "parse.h" +#include "socket_raw.h" +#endif + +typedef struct PendingConnect PendingConnect; + +// These should only be relevant to socket.c +typedef enum { + SOCKET_STATE_FREE, + SOCKET_STATE_DIED, + SOCKET_STATE_ESTABLISHED_WAIT, + SOCKET_STATE_ESTABLISHED_READY, + SOCKET_STATE_PENDING, + SOCKET_STATE_ACCEPTED, + SOCKET_STATE_CONNECTED, + SOCKET_STATE_CONNECTING, + SOCKET_STATE_SHUTDOWN, +} SocketState; + +typedef struct { + SocketState state; + + RAW_SOCKET raw; + int events; + + void *user_data; + PendingConnect *pending_connect; + +#ifdef HTTPS_ENABLED + SSL *ssl; +#endif + + SecureContext *sec; + +} Socket; + +void socket_connect(Socket *sock, SecureContext *sec, HTTP_String hostname, uint16_t port, void *user_data); +void socket_connect_ipv4(Socket *sock, SecureContext *sec, HTTP_IPv4 addr, uint16_t port, void *user_data); +void socket_connect_ipv6(Socket *sock, SecureContext *sec, HTTP_IPv6 addr, uint16_t port, void *user_data); +void socket_accept(Socket *sock, SecureContext *sec, RAW_SOCKET raw); +void socket_update(Socket *sock); +void socket_close(Socket *sock); +bool socket_ready(Socket *sock); +bool socket_died(Socket *sock); +int socket_read(Socket *sock, char *dst, int max); +int socket_write(Socket *sock, char *src, int len); +void socket_free(Socket *sock); +bool socket_secure(Socket *sock); +void socket_set_user_data(Socket *sock, void *user_data); +void* socket_get_user_data(Socket *sock); + +#endif // SOCKET_INCLUDED + +//////////////////////////////////////////////////////////////////////////////////////// +// src/socket_pool.h +//////////////////////////////////////////////////////////////////////////////////////// + +#line 1 "src/socket_pool.h" +#ifndef SOCKET_POOL_INCLUDED +#define SOCKET_POOL_INCLUDED + +#ifndef HTTP_AMALGAMATION +#include "basic.h" +#include "socket.h" +#include "socket_raw.h" +#endif + +typedef struct SocketPool SocketPool; + +typedef int SocketHandle; + +typedef enum { + SOCKET_EVENT_DIED, + SOCKET_EVENT_READY, + SOCKET_EVENT_ERROR, + SOCKET_EVENT_SIGNAL, +} SocketEventType; + +typedef struct { + SocketEventType type; + SocketHandle handle; + void *user_data; +} SocketEvent; + +int socket_pool_global_init(void); +void socket_pool_global_free(void); + +SocketPool *socket_pool_init(HTTP_String addr, + uint16_t port, uint16_t secure_port, int max_socks, + bool reuse_addr, int backlog, HTTP_String cert_file, + HTTP_String key_file); + +void socket_pool_free(SocketPool *pool); + +int socket_pool_add_cert(SocketPool *pool, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file); + +SocketEvent socket_pool_wait(SocketPool *pool); + +void socket_pool_set_user_data(SocketPool *pool, SocketHandle handle, void *user_data); + +void socket_pool_close(SocketPool *pool, SocketHandle handle); + +int socket_pool_connect(SocketPool *pool, bool secure, + HTTP_String addr, uint16_t port, void *user_data); + +int socket_pool_connect_ipv4(SocketPool *pool, bool secure, + HTTP_IPv4 addr, uint16_t port, void *user_data); + +int socket_pool_connect_ipv6(SocketPool *pool, bool secure, + HTTP_IPv6 addr, uint16_t port, void *user_data); + +int socket_pool_read(SocketPool *pool, SocketHandle handle, char *dst, int len); + +int socket_pool_write(SocketPool *pool, SocketHandle handle, char *src, int len); + +bool socket_pool_secure(SocketPool *pool, SocketHandle handle); + +#endif // SOCKET_POOL_INCLUDED + +//////////////////////////////////////////////////////////////////////////////////////// +// src/basic.c +//////////////////////////////////////////////////////////////////////////////////////// + +#line 1 "src/basic.c" +#include +#include + +#ifndef HTTP_AMALGAMATION +#include "basic.h" +#endif + +bool http_streq(HTTP_String s1, HTTP_String s2) +{ + if (s1.len != s2.len) + return false; + + for (int i = 0; i < s1.len; i++) + if (s1.ptr[i] != s2.ptr[i]) + return false; + + return true; +} + +static char to_lower(char c) +{ + if (c >= 'A' && c <= 'Z') + return c - 'A' + 'a'; + return c; +} + +bool http_streqcase(HTTP_String s1, HTTP_String s2) +{ + if (s1.len != s2.len) + return false; + + for (int i = 0; i < s1.len; i++) + if (to_lower(s1.ptr[i]) != to_lower(s2.ptr[i])) + return false; + + return true; +} + +HTTP_String http_trim(HTTP_String s) +{ + int i = 0; + while (i < s.len && (s.ptr[i] == ' ' || s.ptr[i] == '\t')) + i++; + + if (i == s.len) { + s.ptr = NULL; + s.len = 0; + } else { + s.ptr += i; + s.len -= i; + while (s.ptr[s.len-1] == ' ' || s.ptr[s.len-1] == '\t') + s.len--; + } + + return s; +} + +static bool is_printable(char c) +{ + return c >= ' ' && c <= '~'; +} + +#include +void print_bytes(HTTP_String prefix, HTTP_String src) +{ + if (src.len == 0) + return; + + FILE *stream = stdout; + + bool new_line = true; + int cur = 0; + for (;;) { + int start = cur; + + while (cur < src.len && is_printable(src.ptr[cur])) + cur++; + + if (new_line) { + fwrite(prefix.ptr, 1, prefix.len, stream); + new_line = false; + } + + fwrite(src.ptr + start, 1, cur - start, stream); + + if (cur == src.len) + break; + + if (src.ptr[cur] == '\n') { + putc('\\', stream); + putc('n', stream); + putc('\n', stream); + new_line = true; + } else if (src.ptr[cur] == '\r') { + putc('\\', stream); + putc('r', stream); + } else { + putc('.', stream); + } + cur++; + } + putc('\n', stream); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// src/parse.c +//////////////////////////////////////////////////////////////////////////////////////// + +#line 1 "src/parse.c" +#include // snprintf +#include +#include +#include +#include +#include +#include + +#ifndef HTTP_AMALGAMATION +#include "parse.h" +#include "basic.h" +#endif + +// From RFC 9112 +// request-target = origin-form +// / absolute-form +// / 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(""); +} + +int http_get_param_i(HTTP_String body, HTTP_String str) +{ + char buf[128]; + HTTP_String out = http_get_param(body, str, buf, (int) 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; +} + +bool http_match_host(HTTP_Request *req, HTTP_String domain, int port) +{ + int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Host")); + HTTP_ASSERT(idx != -1); // Requests without the host header are always rejected + + char tmp[1<<8]; + if (port > -1 && port != 80) { + int ret = snprintf(tmp, sizeof(tmp), "%.*s:%d", domain.len, domain.ptr, port); + HTTP_ASSERT(ret > 0); + domain = (HTTP_String) { tmp, ret }; + } + + HTTP_String host = req->headers[idx].value; + return http_streq(host, domain); +} + +//////////////////////////////////////////////////////////////////////////////////////// +// 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; + if (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); +} diff --git a/3p/chttp.h b/3p/chttp.h index d716825..0c8548e 100644 --- a/3p/chttp.h +++ b/3p/chttp.h @@ -1,526 +1,505 @@ -#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 +#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); + +// Checks whether the request was meant for the host with the given +// domain an port. If port is -1, the default value of 80 is assumed. +bool http_match_host(HTTP_Request *req, HTTP_String domain, int port); + +#endif // PARSE_INCLUDED + +//////////////////////////////////////////////////////////////////////////////////////// +// src/engine.h +//////////////////////////////////////////////////////////////////////////////////////// + +#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 +#endif // HTTP_AMALGAMATION diff --git a/cweb.h b/cweb.h index b175ba4..1e48962 100644 --- a/cweb.h +++ b/cweb.h @@ -1,8 +1,8 @@ /* -Copyright © 2025 Francesco Cozzuto +Copyright 2025 Francesco Cozzuto Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the “Software”), +copy of this software and associated documentation files (the Software), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the @@ -11,7 +11,7 @@ Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS +THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR @@ -569,6 +569,9 @@ 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); +// Checks whether the request was meant for the host with the given +// domain an port. If port is -1, the default value of 80 is assumed. +bool http_match_host(HTTP_Request *req, HTTP_String domain, int port); #endif // PARSE_INCLUDED @@ -875,30 +878,6 @@ 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 //////////////////////////////////////////////////////////////////////////////////////// @@ -1273,6 +1252,7 @@ void print_bytes(HTTP_String prefix, HTTP_String src) //////////////////////////////////////////////////////////////////////////////////////// #line 1 "src/parse.c" +#include // snprintf #include #include #include @@ -2575,6 +2555,22 @@ int http_get_param_i(HTTP_String body, HTTP_String str) return res; } +bool http_match_host(HTTP_Request *req, HTTP_String domain, int port) +{ + int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Host")); + HTTP_ASSERT(idx != -1); // Requests without the host header are always rejected + + char tmp[1<<8]; + if (port > -1 && port != 80) { + int ret = snprintf(tmp, sizeof(tmp), "%.*s:%d", domain.len, domain.ptr, port); + HTTP_ASSERT(ret > 0); + domain = (HTTP_String) { tmp, ret }; + } + + HTTP_String host = req->headers[idx].value; + return http_streq(host, domain); +} + //////////////////////////////////////////////////////////////////////////////////////// // src/engine.c //////////////////////////////////////////////////////////////////////////////////////// @@ -6012,459 +6008,6 @@ void http_response_builder_done(HTTP_ResponseBuilder res) 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; -} #undef MIN #undef MAX #undef ASSERT @@ -13638,6 +13181,9 @@ struct CWEB { int pool_cap; char *pool; + uint16_t port; + uint16_t secure_port; + // Login SessionStorage *session_storage; @@ -14674,6 +14220,8 @@ CWEB *cweb_init(CWEB_String addr, uint16_t port, uint16_t secure_port, free(cweb); return NULL; } + cweb->port = port; + cweb->secure_port = secure_port; #ifdef CWEB_ENABLE_DATABASE cweb->db = NULL; @@ -14826,10 +14374,20 @@ CWEB_Request *cweb_wait(CWEB *cweb) bool cweb_match_domain(CWEB_Request *req, CWEB_String str) { - int idx = http_find_header(req->req->headers, req->req->num_headers, HTTP_STR("host")); - if (idx < 0) return false; + uint16_t port = req->cweb->port; + uint16_t secure_port = req->cweb->secure_port; - return http_streq((HTTP_String) { str.ptr, str.len }, req->req->headers[idx].value); + HTTP_String str2 = { str.ptr, str.len }; + + if (port > 0 + && http_match_host(req->req, str2, port)) + return true; + + if (secure_port > 0 + && http_match_host(req->req, str2, secure_port)) + return true; + + return false; } bool cweb_match_endpoint(CWEB_Request *req, CWEB_String str) diff --git a/demo/main.c b/demo/main.c index 6073657..ef5592c 100644 --- a/demo/main.c +++ b/demo/main.c @@ -55,24 +55,20 @@ static int user_exists(CWEB *cweb, CWEB_String name, CWEB_String pass) CWEB_PasswordHash hash; int ret = cweb_next_query_row(&res, &user_id, &hash); if (ret < 0) { - printf("here %s:%d\n", __FILE__, __LINE__); // TODO: remove cweb_free_query_result(&res); return -1; } if (ret == 0) { - printf("here %s:%d\n", __FILE__, __LINE__); // TODO: remove cweb_free_query_result(&res); return 0; } ret = cweb_check_password(pass, hash); if (ret < 0) { - printf("here %s:%d\n", __FILE__, __LINE__); // TODO: remove cweb_free_query_result(&res); return -1; } if (ret > 0) { - printf("here %s:%d\n", __FILE__, __LINE__); // TODO: remove cweb_free_query_result(&res); return 0; } diff --git a/demo/pages/post.wl b/demo/pages/post.wl index 3e3022c..fc7e586 100644 --- a/demo/pages/post.wl +++ b/demo/pages/post.wl @@ -1,7 +1,7 @@ include "page.wl" -let post_id = $args(0) -let posts = $query("SELECT U.username, P.title, P.content FROM Posts as P, Users as U WHERE P.id=? AND U.id=P.author", post_id) +let post_id = $args(0) +let posts = $query("SELECT U.username, P.title, P.content FROM Posts as P, Users as U WHERE P.id=? AND U.id=P.author", post_id) let comments = $query("SELECT C.id, U.username, C.content, C.parent_post, C.parent_comment FROM Comments as C, Users as U WHERE C.parent_post=? AND U.id=C.author", post_id) let lookup = {} diff --git a/src/main.c b/src/main.c index 6786bae..96f5bc4 100644 --- a/src/main.c +++ b/src/main.c @@ -90,6 +90,9 @@ struct CWEB { int pool_cap; char *pool; + uint16_t port; + uint16_t secure_port; + // Login SessionStorage *session_storage; @@ -1126,6 +1129,8 @@ CWEB *cweb_init(CWEB_String addr, uint16_t port, uint16_t secure_port, free(cweb); return NULL; } + cweb->port = port; + cweb->secure_port = secure_port; #ifdef CWEB_ENABLE_DATABASE cweb->db = NULL; @@ -1278,10 +1283,20 @@ CWEB_Request *cweb_wait(CWEB *cweb) bool cweb_match_domain(CWEB_Request *req, CWEB_String str) { - int idx = http_find_header(req->req->headers, req->req->num_headers, HTTP_STR("host")); - if (idx < 0) return false; + uint16_t port = req->cweb->port; + uint16_t secure_port = req->cweb->secure_port; - return http_streq((HTTP_String) { str.ptr, str.len }, req->req->headers[idx].value); + HTTP_String str2 = { str.ptr, str.len }; + + if (port > 0 + && http_match_host(req->req, str2, port)) + return true; + + if (secure_port > 0 + && http_match_host(req->req, str2, secure_port)) + return true; + + return false; } bool cweb_match_endpoint(CWEB_Request *req, CWEB_String str)