diff --git a/src/client.c b/src/client.c index 4c054a6..ad88a59 100644 --- a/src/client.c +++ b/src/client.c @@ -2235,6 +2235,7 @@ int toasty_wait_result(ToastyFS *toasty, ToastyHandle handle, ToastyResult *resu if (ret < 0) return -1; + // TODO: Detect when this returns WAKEUP num_polled = toasty_process_events(toasty, contexts, polled, num_polled); if (num_polled < 0) return -1; diff --git a/web/chttp.c b/web/chttp.c new file mode 100644 index 0000000..13a0424 --- /dev/null +++ b/web/chttp.c @@ -0,0 +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 // 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/web/chttp.h b/web/chttp.h new file mode 100644 index 0000000..0c8548e --- /dev/null +++ b/web/chttp.h @@ -0,0 +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); + +// 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/web/main.c b/web/main.c new file mode 100644 index 0000000..6a3d8b1 --- /dev/null +++ b/web/main.c @@ -0,0 +1,317 @@ +#include +#include + +#include +#include + +#define MAX_WAITING (1<<10) +#define MAX_PENDING (1<<9) + +typedef enum { + OPERATION_FREE, + OPERATION_CREATE_DIR, + OPERATION_CREATE_FILE, + OPERATION_DELETE, + OPERATION_READ_DIR, + OPERATION_READ_FILE, + OPERATION_WRITE, +} OperationType; + +typedef struct { + OperationType type; + + // Number of bytes read/written + int transferred; + + HTTP_Request *request; // TODO: is it okay to store this pointer? + HTTP_ResponseBuilder builder; + + ToastyHandle handle; +} Operation; + +static int waiting_head; +static int num_waiting; +static int num_pending; +static Operation waiting[MAX_WAITING]; +static Operation pending[MAX_PENDING]; + +void worker(Worker *w) +{ + for (;;) { + + ToastyResult result; + int ret = toasty_wait_result(toasty, TOASTY_INVALID, &result, -1); + // TODO: check return value + + // First, process completed requests. This frees up + // space for new ones. + // + // TODO: What if there was a WAKEUP request and no pending operations + // are present? + + int i = 0; + while (pending[i].handle != result.handle) { + i++; + assert(i < MAX_PENDING_OPERATIONS); + } + + bool incomplete = false; + switch (pending[i].type) { + + int cap; + int mincap; + char *dst; + ToastyString path; + + case OPERATION_CREATE_DIR: + case OPERATION_CREATE_FILE: + if (result.type == TOASTY_RESULT_CREATE_SUCCESS) { + http_response_builder_status(pending[i].builder, 204); // TODO: What is the proper response to a CREATE request? + http_response_builder_done(pending[i].builder); + } else { + http_response_builder_undo(pending[i].builder); + http_response_builder_status(pending[i].builder, 500); + http_response_builder_done(pending[i].builder); + } + break; + + case OPERATION_DELETE: + if (result.type == TOASTY_RESULT_DELETE_SUCCESS) { + http_response_builder_status(pending[i].builder, 204); // TODO: What is the proper response to a DELETE request? + http_response_builder_done(pending[i].builder); + } else { + http_response_builder_undo(pending[i].builder); + http_response_builder_status(pending[i].builder, 500); + http_response_builder_done(pending[i].builder); + } + break; + + case OPERATION_READ_DIR: + + // If the listing failed, abort the request + if (result.type != TOASTY_RESULT_READ_SUCCESS) { + http_response_builder_status(pending[i].builder, 500); + http_response_builder_done(pending[i].builder); + break; + } + + http_response_builder_status(pending[i].builder, 500); + for (int i = 0; i < result.listing.count; i++) + http_response_builder_body(pending[i].builder, (HTTP_String) { + result.listing.items[i].name, + result.listing.items[i].name_len + }); + http_response_builder_done(pending[i].builder); + + toasty_free_listing(&result.listing); + break; + + case OPERATION_READ_FILE: + + // If the read failed, abort the request + if (result.type != TOASTY_RESULT_READ_SUCCESS) { + http_response_builder_bodyack(pending[i].builder, 0); + http_response_builder_undo(pending[i].builder); + http_response_builder_status(pending[i].builder, 500); + http_response_builder_done(pending[i].builder); + break; + } + + // First, ACK the byte we just read, even if it's + // just 0 bytes (every bodybuf must by paired with + // a bodyack). + http_response_builder_bodyack(pending[i].builder, result.count); + pending[i].transferred += result.count; + + // If we read 0 bytes, there is no more to read. + if (result.count == 0) { + http_response_builder_done(pending[i].builder); + break; + } + + // There is more to read, so we need to start a + // new read. + + // Make sure there is some free space in the buffer + mincap = 1<<10; // TODO: Choose based on overall file size + http_response_builder_bodycap(pending[i].builder, mincap); + + // Get the location of that buffer + dst = http_response_builder_bodybuf(pending[i].builder, &cap); + if (dst == NULL) { + http_response_builder_done(pending[i].builder); + break; + } + + // Begin the read. On error, abort. + path = (ToastyString) { pending[i].request->path.ptr, pending[i].request.path.len }; + pending[i].handle = toasty_begin_read(toasty, path, off, dst, cap); + if (pending[i].handle == TOASTY_INVALID) { + http_response_builder_undo(pending[i].builder); + http_response_builder_status(pending[i].builder, 500); + http_response_builder_done(pending[i].builder); + break; + } + + // Most of the time this switch will complete the pending + // operation, but not for reads. + incomplete = true; + break; + + case OPERATION_WRITE: + if (pending[i].type != TOASTY_RESULT_WRITE_SUCCESS) { + http_response_builder_undo(pending[i].builder); + http_response_builder_status(pending[i].builder, 500); + http_response_builder_done(pending[i].builder); + break; + } + assert(pending[i].transferred == result.count); + http_response_builder_done(pending[i].builder); + break; + + default: + assert(0); // TODO + } + + if (!incomplete) { + // Free the pending structure + pending[i].type = OPERATION_FREE; + num_pending--; + } + + // Now accept operations + for (Operation req; num_pending < MAX_PENDING_OPERATIONS + && wait_queue_pop(&wait_queue, &req); ) { + + int i = 0; + while (pending[i].type != PENDING_OPERATION_FREE) { + i++; + assert(i < MAX_PENDING_OPERATIONS); + } + pending[i] = req; + + ToastyString path = { pending[i].request->path.ptr, pending[i].request.path.len }; + ToastyString body = { pending[i].request->body.ptr, pending[i].request.body.len }; + switch (pending[i].type) { + int cap; + int mincap; + char *dst; + uint32_t chunk_size; + case OPERATION_CREATE_DIR, + pending[i].handle = toasty_begin_create_dir(toasty, path); + break; + case OPERATION_CREATE_FILE, + chunk_size = 1<<10; // TODO: determine a better chunk size + pending[i].handle = toasty_begin_create_file(toasty, path, chunk_size); + break; + case OPERATION_DELETE, + pending[i].handle = toasty_begin_delete(toasty, path); + break; + case OPERATION_READ, + http_response_builder_status(pending[i].builder, 200); // TODO: Is statis 200 correct? + + mincap = 1<<10; // TODO: do something smart to choose this + http_response_builder_bodycap(pending[i].builder, mincap); + + dst = http_response_builder_bodybuf(pending[i].builder, &cap); + if (dst == NULL) + break; + + pending[i].handle = toasty_begin_read(toasty, path, off, dst, cap); + pending[i].transferred = 0; + break; + case OPERATION_WRITE: + pending[i].handle = toasty_begin_write(toasty, body.ptr, body.len); + pending[i].transferred = 0; + break; + } + + if (pending[i].handle == TOASTY_INVALID) { + http_response_builder_undo(pending[i].builder); + http_response_builder_status(pending[i].builder, 500); + http_response_builder_done(pending[i].builder); + pending[i].type = OPERATION_FREE; + } + num_pending++; + } + } +} + +int main(void) +{ + http_global_init(); + + HTTP_String addr = HTTP_STR("127.0.0.1"); + uint16_t port = 8080; + + ToastyString backend_addr = TOASTY_STR("127.0.0.1"); + uint16_t backend_port = 9000; + + HTTP_Server *server = http_server_init(addr, port); + if (server == NULL) + return -1; + + ToastyFS *toasty = toasty_connect(backend_addr, backend_port); + if (toasty == NULL) + return -1; + + for (;;) { + + HTTP_Request *request; + HTTP_ResponseBuilder builder; + int ret = http_server_wait(server, &req, &builder); + if (ret < 0) + return -1; + + ToastyString path = { + req->url.path.ptr, + req->url.path.len + }; + + Operation op; + op.type = OPERATION_FREE; + + switch (req->method) { + + case HTTP_METHOD_GET: + // TODO + break; + + case HTTP_METHOD_HEAD: + // TODO + break; + + case HTTP_METHOD_PUT: + // TODO + break; + + case HTTP_METHOD_PATCH: + // TODO + break; + + case HTTP_METHOD_DELETE: + // TODO + break; + + case HTTP_METHOD_OPTIONS: + http_response_builder_status(builder, 200); + http_response_builder_header(builder, HTTP_STR("Allow: GET, HEAD, PUT, PATCH, DELETE, OPTIONS")); + http_response_builder_done(builder); + break; + + default: + http_response_builder_status(builder, 200); // TODO: use the status code for invalid methods + http_response_builder_header(builder, HTTP_STR("Allow: GET, HEAD, PUT, PATCH, DELETE, OPTIONS")); + http_response_builder_done(builder); + break; + } + + if (op.type != OPERATION_FREE) { + assert(0); // TODO: Append to the queue + } + } + + http_server_free(server); + http_global_free(); + return 0; +}