#include "chttp.h" //////////////////////////////////////////////////////////////////////////////////////// // src/sec.h //////////////////////////////////////////////////////////////////////////////////////// #line 1 "src/sec.h" #ifndef SEC_INCLUDED #define SEC_INCLUDED #ifndef HTTP_AMALGAMATION #include "basic.h" #endif #ifndef HTTPS_ENABLED typedef struct { } SecureContext; #else #define MAX_CERTS 10 #include #include typedef struct { char domain[128]; SSL_CTX *ctx; } CertData; typedef struct { bool is_server; SSL_CTX *ctx; // Only used when server int num_certs; CertData certs[MAX_CERTS]; } SecureContext; #endif void secure_context_global_init(void); void secure_context_global_free(void); int secure_context_init_as_client(SecureContext *sec); int secure_context_init_as_server(SecureContext *sec, HTTP_String cert_file, HTTP_String key_file); int secure_context_add_cert(SecureContext *sec, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file); void secure_context_free(SecureContext *sec); #endif // SEC_INCLUDED //////////////////////////////////////////////////////////////////////////////////////// // src/socket_raw.h //////////////////////////////////////////////////////////////////////////////////////// #line 1 "src/socket_raw.h" #ifndef SOCKET_RAW_INCLUDED #define SOCKET_RAW_INCLUDED #include #include #ifdef _WIN32 #include #define RAW_SOCKET SOCKET #define BAD_SOCKET INVALID_SOCKET #define POLL WSAPoll #define CLOSE_SOCKET closesocket #endif #ifdef __linux__ #include #include #define RAW_SOCKET int #define BAD_SOCKET -1 #define POLL poll #define CLOSE_SOCKET close #endif #ifndef HTTP_AMALGAMATION #include "basic.h" #endif int socket_raw_global_init(void); void socket_raw_global_free(void); int set_socket_blocking(RAW_SOCKET sock, bool value); RAW_SOCKET listen_socket(HTTP_String addr, uint16_t port, bool reuse_addr, int backlog); #endif // SOCKET_RAW_INCLUDED //////////////////////////////////////////////////////////////////////////////////////// // src/socket.h //////////////////////////////////////////////////////////////////////////////////////// #line 1 "src/socket.h" #ifndef SOCKET_INCLUDED #define SOCKET_INCLUDED // This module implements the socket state machine to encapsulate // the complexity of non-blocking TCP and TLS sockets. // // A socket is represented by the "Socket" structure, which may // be in a number of states. As far as an user of the interface // is concerned, the socket may be DIED, READY, or in an internal // state that requires waiting for an event. Therefore, if the // socket is not DIED or READY, the user needs to wait for the // events specified in the [socket->events] field, then call the // socket_update function. At some point the socket will become // either READY or DIED. // // When the socket reaches the DIED state, the user must call // socket_free. // // If the socket is ESTABLISHED_READY, the user may call socket_read, // socket_write, or socket_close on it. #ifndef HTTP_AMALGAMATION #include "sec.h" #include "parse.h" #include "socket_raw.h" #endif typedef struct PendingConnect PendingConnect; // These should only be relevant to socket.c typedef enum { SOCKET_STATE_FREE, SOCKET_STATE_DIED, SOCKET_STATE_ESTABLISHED_WAIT, SOCKET_STATE_ESTABLISHED_READY, SOCKET_STATE_PENDING, SOCKET_STATE_ACCEPTED, SOCKET_STATE_CONNECTED, SOCKET_STATE_CONNECTING, SOCKET_STATE_SHUTDOWN, } SocketState; typedef struct { SocketState state; RAW_SOCKET raw; int events; void *user_data; PendingConnect *pending_connect; #ifdef HTTPS_ENABLED SSL *ssl; #endif SecureContext *sec; } Socket; void socket_connect(Socket *sock, SecureContext *sec, HTTP_String hostname, uint16_t port, void *user_data); void socket_connect_ipv4(Socket *sock, SecureContext *sec, HTTP_IPv4 addr, uint16_t port, void *user_data); void socket_connect_ipv6(Socket *sock, SecureContext *sec, HTTP_IPv6 addr, uint16_t port, void *user_data); void socket_accept(Socket *sock, SecureContext *sec, RAW_SOCKET raw); void socket_update(Socket *sock); void socket_close(Socket *sock); bool socket_ready(Socket *sock); bool socket_died(Socket *sock); int socket_read(Socket *sock, char *dst, int max); int socket_write(Socket *sock, char *src, int len); void socket_free(Socket *sock); bool socket_secure(Socket *sock); void socket_set_user_data(Socket *sock, void *user_data); void* socket_get_user_data(Socket *sock); #endif // SOCKET_INCLUDED //////////////////////////////////////////////////////////////////////////////////////// // src/socket_pool.h //////////////////////////////////////////////////////////////////////////////////////// #line 1 "src/socket_pool.h" #ifndef SOCKET_POOL_INCLUDED #define SOCKET_POOL_INCLUDED #ifndef HTTP_AMALGAMATION #include "basic.h" #include "socket.h" #include "socket_raw.h" #endif typedef struct SocketPool SocketPool; typedef int SocketHandle; typedef enum { SOCKET_EVENT_DIED, SOCKET_EVENT_READY, SOCKET_EVENT_ERROR, SOCKET_EVENT_SIGNAL, } SocketEventType; typedef struct { SocketEventType type; SocketHandle handle; void *user_data; } SocketEvent; int socket_pool_global_init(void); void socket_pool_global_free(void); SocketPool *socket_pool_init(HTTP_String addr, uint16_t port, uint16_t secure_port, int max_socks, bool reuse_addr, int backlog, HTTP_String cert_file, HTTP_String key_file); void socket_pool_free(SocketPool *pool); int socket_pool_add_cert(SocketPool *pool, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file); SocketEvent socket_pool_wait(SocketPool *pool); void socket_pool_set_user_data(SocketPool *pool, SocketHandle handle, void *user_data); void socket_pool_close(SocketPool *pool, SocketHandle handle); int socket_pool_connect(SocketPool *pool, bool secure, HTTP_String addr, uint16_t port, void *user_data); int socket_pool_connect_ipv4(SocketPool *pool, bool secure, HTTP_IPv4 addr, uint16_t port, void *user_data); int socket_pool_connect_ipv6(SocketPool *pool, bool secure, HTTP_IPv6 addr, uint16_t port, void *user_data); int socket_pool_read(SocketPool *pool, SocketHandle handle, char *dst, int len); int socket_pool_write(SocketPool *pool, SocketHandle handle, char *src, int len); #endif // SOCKET_POOL_INCLUDED //////////////////////////////////////////////////////////////////////////////////////// // src/basic.c //////////////////////////////////////////////////////////////////////////////////////// #line 1 "src/basic.c" #include #include #ifndef HTTP_AMALGAMATION #include "basic.h" #endif bool http_streq(HTTP_String s1, HTTP_String s2) { if (s1.len != s2.len) return false; for (int i = 0; i < s1.len; i++) if (s1.ptr[i] != s2.ptr[i]) return false; return true; } static char to_lower(char c) { if (c >= 'A' && c <= 'Z') return c - 'A' + 'a'; return c; } bool http_streqcase(HTTP_String s1, HTTP_String s2) { if (s1.len != s2.len) return false; for (int i = 0; i < s1.len; i++) if (to_lower(s1.ptr[i]) != to_lower(s2.ptr[i])) return false; return true; } HTTP_String http_trim(HTTP_String s) { int i = 0; while (i < s.len && (s.ptr[i] == ' ' || s.ptr[i] == '\t')) i++; if (i == s.len) { s.ptr = NULL; s.len = 0; } else { s.ptr += i; s.len -= i; while (s.ptr[s.len-1] == ' ' || s.ptr[s.len-1] == '\t') s.len--; } return s; } static bool is_printable(char c) { return c >= ' ' && c <= '~'; } #include void print_bytes(HTTP_String prefix, HTTP_String src) { if (src.len == 0) return; FILE *stream = stdout; bool new_line = true; int cur = 0; for (;;) { int start = cur; while (cur < src.len && is_printable(src.ptr[cur])) cur++; if (new_line) { fwrite(prefix.ptr, 1, prefix.len, stream); new_line = false; } fwrite(src.ptr + start, 1, cur - start, stream); if (cur == src.len) break; if (src.ptr[cur] == '\n') { putc('\\', stream); putc('n', stream); putc('\n', stream); new_line = true; } else if (src.ptr[cur] == '\r') { putc('\\', stream); putc('r', stream); } else { putc('.', stream); } cur++; } putc('\n', stream); } //////////////////////////////////////////////////////////////////////////////////////// // src/parse.c //////////////////////////////////////////////////////////////////////////////////////// #line 1 "src/parse.c" #include #include #include #include #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 <= '~'; } static int consume_absolute_path(Scanner *s) { if (s->cur == s->len || s->src[s->cur] != '/') return -1; // ERROR s->cur++; for (;;) { while (s->cur < s->len && is_pchar(s->src[s->cur])) s->cur++; 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 (;;) { while (s->cur < s->len && is_pchar(s->src[s->cur])) s->cur++; 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 (;;) { while (s->cur < s->len && is_pchar(s->src[s->cur])) s->cur++; 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; while (s->cur < s->len && is_userinfo(s->src[s->cur])) s->cur++; 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; } 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; for (;;) { if (s->len - s->cur > 1 && s->src[s->cur+0] == '\r' && s->src[s->cur+1] == '\n') { s->cur += 2; break; } // 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; while (s->cur < s->len && is_header_body(s->src[s->cur])) s->cur++; HTTP_String body = { s->src + start, s->cur - start }; body = http_trim(body); if (s->len - s->cur < 2 || s->src[s->cur+0] != '\r' || s->src[s->cur+1] != '\n') return -1; // ERROR s->cur += 2; if (num_headers < max_headers) headers[num_headers++] = (HTTP_Header) { name, body }; } return num_headers; } static int parse_content_length(const char *src, int len, unsigned long long *out) { int cur = 0; while (cur < len && (src[cur] == ' ' || src[cur] == '\t')) cur++; if (cur == len || !is_digit(src[cur])) return -1; unsigned long long 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 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; if (s->len - s->cur >= 3 && s->src[s->cur+0] == 'G' && s->src[s->cur+1] == 'E' && s->src[s->cur+2] == 'T') { s->cur += 3; req->method = HTTP_METHOD_GET; } else if (s->len - s->cur >= 4 && s->src[s->cur+0] == 'P' && s->src[s->cur+1] == 'O' && s->src[s->cur+2] == 'S' && s->src[s->cur+3] == 'T') { s->cur += 4; req->method = HTTP_METHOD_POST; } else if (s->len - s->cur >= 3 && s->src[s->cur+0] == 'P' && s->src[s->cur+1] == 'U' && s->src[s->cur+2] == 'T') { s->cur += 3; req->method = HTTP_METHOD_PUT; } else if (s->len - s->cur >= 4 && s->src[s->cur+0] == 'H' && s->src[s->cur+1] == 'E' && s->src[s->cur+2] == 'A' && s->src[s->cur+3] == 'D') { s->cur += 4; req->method = HTTP_METHOD_HEAD; } else if (s->len - s->cur >= 6 && s->src[s->cur+0] == 'D' && s->src[s->cur+1] == 'E' && s->src[s->cur+2] == 'L' && s->src[s->cur+3] == 'E' && s->src[s->cur+4] == 'T' && s->src[s->cur+5] == 'E') { s->cur += 6; req->method = HTTP_METHOD_DELETE; } else if (s->len - s->cur >= 7 && s->src[s->cur+0] == 'C' && s->src[s->cur+1] == 'O' && s->src[s->cur+2] == 'N' && s->src[s->cur+3] == 'N' && s->src[s->cur+4] == 'E' && s->src[s->cur+5] == 'C' && s->src[s->cur+6] == 'T') { s->cur += 7; req->method = HTTP_METHOD_CONNECT; } else if (s->len - s->cur >= 7 && s->src[s->cur+0] == 'O' && s->src[s->cur+1] == 'P' && s->src[s->cur+2] == 'T' && s->src[s->cur+3] == 'I' && s->src[s->cur+4] == 'O' && s->src[s->cur+5] == 'N' && s->src[s->cur+6] == 'S') { s->cur += 7; req->method = HTTP_METHOD_OPTIONS; } else if (s->len - s->cur >= 5 && s->src[s->cur+0] == 'T' && s->src[s->cur+1] == 'R' && s->src[s->cur+2] == 'A' && s->src[s->cur+3] == 'C' && s->src[s->cur+4] == 'E') { s->cur += 5; req->method = HTTP_METHOD_TRACE; } else if (s->len - s->cur >= 5 && s->src[s->cur+0] == 'P' && s->src[s->cur+1] == 'A' && s->src[s->cur+2] == 'T' && s->src[s->cur+3] == 'C' && s->src[s->cur+4] == 'H') { s->cur += 5; req->method = HTTP_METHOD_PATCH; } else { return -1; } if (s->cur == s->len || s->src[s->cur] != ' ') return -1; s->cur++; { 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 (s->len - s->cur < 7 || s->src[s->cur+0] != ' ' || s->src[s->cur+1] != 'H' || s->src[s->cur+2] != 'T' || s->src[s->cur+3] != 'T' || s->src[s->cur+4] != 'P' || s->src[s->cur+5] != '/' || s->src[s->cur+6] != '1') return -1; // ERROR s->cur += 7; if (s->cur == s->len || s->src[s->cur] != '.') req->minor = 0; else { s->cur++; if (s->cur == s->len || !is_digit(s->src[s->cur])) return -1; // ERROR; req->minor = s->src[s->cur] - '0'; s->cur++; } if (s->len - s->cur < 2 || s->src[s->cur+0] != '\r' || s->src[s->cur+1] != '\n') return -1; // ERROR s->cur += 2; } int num_headers = parse_headers(s, req->headers, HTTP_MAX_HEADERS); if (num_headers < 0) return num_headers; req->num_headers = num_headers; // TODO return 1; } 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 (s->len - s->cur < 6 || s->src[s->cur+0] != 'H' || s->src[s->cur+1] != 'T' || s->src[s->cur+2] != 'T' || s->src[s->cur+3] != 'P' || s->src[s->cur+4] != '/' || s->src[s->cur+5] != '1') return -1; // ERROR s->cur += 6; if (s->cur == s->len || s->src[s->cur] != '.') res->minor = 0; else { s->cur++; if (s->cur == s->len || !is_digit(s->src[s->cur])) return -1; // ERROR res->minor = s->src[s->cur] - '0'; s->cur++; } 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; int content_length_index = http_find_header( res->headers, res->num_headers, HTTP_STR("Content-Length")); if (content_length_index == -1) { res->body.ptr = NULL; res->body.len = 0; return 1; } // TODO: transfer-encoding HTTP_String content_length_str = res->headers[content_length_index].value; unsigned long long content_length; if (parse_content_length(content_length_str.ptr, content_length_str.len, &content_length) < 0) { HTTP_ASSERT(0); // TODO } if (content_length > 1<<20) { HTTP_ASSERT(0); // TODO } if (content_length > (unsigned long long) (s->len - s->cur)) return 0; res->body.ptr = s->src + s->cur; res->body.len = content_length; return 1; } 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; } //////////////////////////////////////////////////////////////////////////////////////// // 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); va_end(args); 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); va_end(args); } 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 *pkey; // Create the context for key generation ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); if (!ctx) return NULL; if (EVP_PKEY_keygen_init(ctx) <= 0) { EVP_PKEY_CTX_free(ctx); return NULL; } if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, key_bits) <= 0) { EVP_PKEY_CTX_free(ctx); return NULL; } if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { EVP_PKEY_CTX_free(ctx); return NULL; } EVP_PKEY_CTX_free(ctx); return pkey; } static X509 *create_certificate(EVP_PKEY *pkey, HTTP_String C, HTTP_String O, HTTP_String CN, int days) { X509 *x509 = X509_new(); if (!x509) return NULL; // Set version (version 3) X509_set_version(x509, 2); // Set serial number ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); // Set validity period X509_gmtime_adj(X509_get_notBefore(x509), 0); X509_gmtime_adj(X509_get_notAfter(x509), 31536000L * days); // days * seconds_per_year // Set public key X509_set_pubkey(x509, pkey); // Set subject name X509_NAME *name = X509_get_subject_name(x509); X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char*) C.ptr, C.len, -1, 0); X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char*) O.ptr, O.len, -1, 0); X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*) CN.ptr, CN.len, -1, 0); // Set issuer name (same as subject for self-signed) X509_set_issuer_name(x509, name); if (!X509_sign(x509, pkey, EVP_sha256())) { X509_free(x509); return NULL; } return x509; } static int save_private_key(EVP_PKEY *pkey, 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); static 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 static 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); static 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'; static 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 BAD_SOCKET; #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) { printf("ret=%d\n", ret); // TODO: remove 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; } 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); 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); } //////////////////////////////////////////////////////////////////////////////////////// // 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 *builder2conn(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 = builder2conn(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 = builder2conn(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 = builder2conn(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 = builder2conn(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 = builder2conn(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 = builder2conn(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) { 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_GET, 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_ResponseHandle *handle) { while (server->ready_count == 0) { SocketEvent event = socket_pool_wait(server->socket_pool); switch (event.type) { case SOCKET_EVENT_DIED: { Connection *conn = event.user_data; HTTP_ASSERT(conn); http_engine_free(&conn->engine); conn->used = false; conn->gen++; server->num_conns--; } break; case SOCKET_EVENT_READY: { Connection *conn = event.user_data; if (conn == NULL) { // Connection was just accepted if (server->num_conns == MAX_CONNS) { socket_pool_close(server->socket_pool, event.handle); break; } int i = 0; while (server->conns[i].used) i++; conn = &server->conns[i]; conn->used = true; conn->sock = event.handle; http_engine_init(&conn->engine, 0, server_memfunc, NULL); socket_pool_set_user_data(server->socket_pool, event.handle, conn); server->num_conns++; } switch (http_engine_state(&conn->engine)) { int len; char *buf; case HTTP_ENGINE_STATE_SERVER_RECV_BUF: buf = http_engine_recvbuf(&conn->engine, &len); if (buf) { int ret = socket_pool_read(server->socket_pool, conn->sock, buf, len); http_engine_recvack(&conn->engine, ret); } break; case HTTP_ENGINE_STATE_SERVER_SEND_BUF: buf = http_engine_sendbuf(&conn->engine, &len); if (buf) { int ret = socket_pool_write(server->socket_pool, conn->sock, buf, len); http_engine_sendack(&conn->engine, ret); } break; default: break; } switch (http_engine_state(&conn->engine)) { int tail; case HTTP_ENGINE_STATE_SERVER_PREP_STATUS: tail = (server->ready_head + server->ready_count) % MAX_CONNS; server->ready[tail] = conn - server->conns; server->ready_count++; break; case HTTP_ENGINE_STATE_SERVER_CLOSED: socket_pool_close(server->socket_pool, conn->sock); break; default: break; } } break; case SOCKET_EVENT_ERROR: return -1; case SOCKET_EVENT_SIGNAL: return 1; } } int index = server->ready[server->ready_head]; server->ready_head = (server->ready_head + 1) % MAX_CONNS; server->ready_count--; *req = http_engine_getreq(&server->conns[index].engine); *handle = (HTTP_ResponseHandle) { server, index, server->conns[index].gen }; return 0; } static Connection* handle2conn(HTTP_ResponseHandle handle) { HTTP_Server *server = handle.data0; if (handle.data1 >= MAX_CONNS) return NULL; Connection *conn = &server->conns[handle.data1]; if (conn->gen != handle.data2) return NULL; return conn; } void http_response_status(HTTP_ResponseHandle res, int status) { Connection *conn = handle2conn(res); if (conn == NULL) return; http_engine_status(&conn->engine, status); } void http_response_header(HTTP_ResponseHandle res, HTTP_String str) { Connection *conn = handle2conn(res); if (conn == NULL) return; http_engine_header(&conn->engine, str); } void http_response_body(HTTP_ResponseHandle res, HTTP_String str) { Connection *conn = handle2conn(res); if (conn == NULL) return; http_engine_body(&conn->engine, str); } void http_response_bodycap(HTTP_ResponseHandle res, int mincap) { Connection *conn = handle2conn(res); if (conn == NULL) return; http_engine_bodycap(&conn->engine, mincap); } char *http_response_bodybuf(HTTP_ResponseHandle res, int *cap) { Connection *conn = handle2conn(res); if (conn == NULL) { *cap = 0; return NULL; } return http_engine_bodybuf(&conn->engine, cap); } void http_response_bodyack(HTTP_ResponseHandle res, int num) { Connection *conn = handle2conn(res); if (conn == NULL) return; http_engine_bodyack(&conn->engine, num); } void http_response_undo(HTTP_ResponseHandle res) { Connection *conn = handle2conn(res); if (conn == NULL) return; http_engine_undo(&conn->engine); } void http_response_done(HTTP_ResponseHandle res) { HTTP_Server *server = res.data0; Connection *conn = handle2conn(res); if (conn == NULL) return; http_engine_done(&conn->engine); conn->gen++; if (conn->gen == 0 || conn->gen == UINT16_MAX) conn->gen = 1; HTTP_EngineState state = http_engine_state(&conn->engine); if (state == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) { int tail = (server->ready_head + server->ready_count) % MAX_CONNS; server->ready[tail] = res.data1; server->ready_count++; } if (state == HTTP_ENGINE_STATE_SERVER_CLOSED) socket_pool_close(server->socket_pool, conn->sock); } //////////////////////////////////////////////////////////////////////////////////////// // src/router.c //////////////////////////////////////////////////////////////////////////////////////// #line 1 "src/router.c" #include #include #include #ifdef _WIN32 #include #endif #ifdef __linux__ #include #include #include #include #endif #ifndef HTTP_AMALGAMATION #include "router.h" #endif #ifndef HTTP_AMALGAMATION bool is_alpha(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } bool is_digit(char c) { return c >= '0' && c <= '9'; } #endif // HTTP_AMALGAMATION typedef enum { ROUTE_STATIC_DIR, ROUTE_DYNAMIC, } RouteType; typedef struct { RouteType type; HTTP_String endpoint; HTTP_String path; HTTP_RouterFunc func; void *ptr; } Route; struct HTTP_Router { int num_routes; int max_routes; Route routes[]; }; HTTP_Router *http_router_init(void) { int max_routes = 32; HTTP_Router *router = malloc(max_routes * sizeof(HTTP_Router)); if (router == NULL) return NULL; router->max_routes = max_routes; router->num_routes = 0; return router; } void http_router_free(HTTP_Router *router) { free(router); } void http_router_dir(HTTP_Router *router, HTTP_String endpoint, HTTP_String path) { if (router->num_routes == router->max_routes) abort(); Route *route = &router->routes[router->num_routes++]; route->type = ROUTE_STATIC_DIR; route->endpoint = endpoint; route->path = path; } void http_router_func(HTTP_Router *router, HTTP_Method method, HTTP_String endpoint, HTTP_RouterFunc func, void *ptr) { if (router->num_routes == router->max_routes) abort(); Route *route = &router->routes[router->num_routes++]; (void) method; // TODO: Don't ignore the method route->type = ROUTE_DYNAMIC; route->endpoint = endpoint; route->func = func; route->ptr = ptr; } static int valid_component_char(char c) { return is_alpha(c) || is_digit(c) || c == '-' || c == '_' || c == '.'; // TODO } static int parse_and_sanitize_path(HTTP_String path, HTTP_String *comps, int max_comps) { // We treat relative and absolute paths the same if (path.len > 0 && path.ptr[0] == '/') { path.ptr++; path.len--; if (path.len == 0) return 0; } int num = 0; int cur = 0; for (;;) { if (cur == path.len || !valid_component_char(path.ptr[cur])) return -1; // Empty component int start = cur; do cur++; while (cur < path.len && valid_component_char(path.ptr[cur])); HTTP_String comp = { path.ptr + start, cur - start }; if (http_streq(comp, HTTP_STR(".."))) { if (num == 0) return -1; num--; } else if (!http_streq(comp, HTTP_STR("."))) { if (num == max_comps) return -1; comps[num++] = comp; } if (cur < path.len) { if (path.ptr[cur] != '/') return -1; cur++; } if (cur == path.len) break; } return num; } static int serialize_parsed_path(HTTP_String *comps, int num_comps, char *dst, int max) { int len = 0; for (int i = 0; i < num_comps; i++) len += comps[i].len + 1; if (len >= max) return -1; int copied = 0; for (int i = 0; i < num_comps; i++) { if (i > 0) dst[copied++] = '/'; memcpy(dst + copied, comps[i].ptr, comps[i].len); copied += comps[i].len; } dst[copied] = '\0'; return copied; } #define MAX_COMPS 32 static int sanitize_path(HTTP_String path, char *dst, int max) { HTTP_String comps[MAX_COMPS]; int num_comps = parse_and_sanitize_path(path, comps, MAX_COMPS); if (num_comps < 0) return -1; return serialize_parsed_path(comps, num_comps, dst, max); } static int swap_parents(HTTP_String original_parent_path, HTTP_String new_parent_path, HTTP_String path, char *mem, int max) { int num_original_parent_path_comps; HTTP_String original_parent_path_comps[MAX_COMPS]; int num_new_parent_path_comps; HTTP_String new_parent_path_comps[MAX_COMPS]; int num_path_comps; HTTP_String path_comps[MAX_COMPS]; num_original_parent_path_comps = parse_and_sanitize_path(original_parent_path, original_parent_path_comps, MAX_COMPS); num_new_parent_path_comps = parse_and_sanitize_path(new_parent_path, new_parent_path_comps, MAX_COMPS); num_path_comps = parse_and_sanitize_path(path, path_comps, MAX_COMPS); if (num_original_parent_path_comps < 0 || num_new_parent_path_comps < 0 || num_path_comps < 0) return -1; int match = 1; if (num_path_comps < num_original_parent_path_comps) match = 0; else { for (int i = 0; i < num_original_parent_path_comps; i++) if (!http_streq(original_parent_path_comps[i], path_comps[i])) { match = 0; break; } } if (!match) return 0; int num_result_comps = num_new_parent_path_comps + num_path_comps - num_original_parent_path_comps; if (num_result_comps < 0 || num_result_comps > MAX_COMPS) return -1; HTTP_String result_comps[MAX_COMPS]; for (int i = 0; i < num_new_parent_path_comps; i++) result_comps[i] = new_parent_path_comps[i]; for (int i = 0; i < num_path_comps; i++) result_comps[num_new_parent_path_comps + i] = path_comps[num_original_parent_path_comps + i]; return serialize_parsed_path(result_comps, num_result_comps, mem, max); } #if _WIN32 typedef HANDLE File; #else typedef int File; #endif static int file_open(const char *path, File *handle, int *size) { #ifdef _WIN32 *handle = CreateFileA( path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (*handle == INVALID_HANDLE_VALUE) { DWORD error = GetLastError(); if (error == ERROR_FILE_NOT_FOUND) return 1; if (error == ERROR_ACCESS_DENIED) return 1; return -1; } LARGE_INTEGER fileSize; if (!GetFileSizeEx(*handle, &fileSize)) { CloseHandle(*handle); return -1; } if (fileSize.QuadPart > INT_MAX) { CloseHandle(*handle); return -1; } *size = (int) fileSize.QuadPart; return 0; #else *handle = open(path, O_RDONLY); if (*handle < 0) { if (errno == ENOENT) return 1; return -1; } struct stat info; if (fstat(*handle, &info) < 0) { close(*handle); return -1; } if (S_ISDIR(info.st_mode)) { close(*handle); return 1; } if (info.st_size > INT_MAX) { close(*handle); return -1; } *size = (int) info.st_size; return 0; #endif } static void file_close(File file) { #ifdef _WIN32 CloseHandle(file); #else close(file); #endif } static int file_read(File file, char *dst, int max) { #ifdef _WIN32 DWORD num; BOOL ok = ReadFile(file, dst, max, &num, NULL); if (!ok) return -1; return (int) num; #else return read(file, dst, max); #endif } static int serve_file_or_index(HTTP_ResponseHandle res, HTTP_String base_endpoint, HTTP_String base_path, HTTP_String endpoint) { char mem[1<<12]; int ret = swap_parents(base_endpoint, base_path, endpoint, mem, sizeof(mem)); if (ret <= 0) return ret; HTTP_String path = {mem, ret}; // Note that this is zero terminated int size; File file; ret = file_open(path.ptr, &file, &size); if (ret == -1) { http_response_status(res, 500); http_response_done(res); return 1; } if (ret == 1) { // File missing char index[] = "index.html"; if (path.len + sizeof(index) + 1 > sizeof(mem)) { http_response_status(res, 500); http_response_done(res); return 1; } path.ptr[path.len++] = '/'; memcpy(path.ptr + path.len, index, sizeof(index)); path.len += sizeof(index)-1; ret = file_open(path.ptr, &file, &size); if (ret == -1) { http_response_status(res, 500); http_response_done(res); return 1; } if (ret == 1) return 0; // File missing } HTTP_ASSERT(ret == 0); int cap; char *dst; http_response_status(res, 200); http_response_bodycap(res, size); dst = http_response_bodybuf(res, &cap); if (dst) { int copied = 0; while (copied < size) { int ret = file_read(file, dst + copied, size - copied); if (ret < 0) goto err; if (ret == 0) break; copied += ret; } if (copied < size) goto err; http_response_bodyack(res, size); } http_response_done(res); file_close(file); return 1; err: http_response_bodyack(res, 0); http_response_undo(res); http_response_status(res, 500); http_response_done(res); file_close(file); return 1; } static int serve_dynamic_route(Route *route, HTTP_Request *req, HTTP_ResponseHandle res) { char path_mem[1<<12]; int path_len = sanitize_path(req->url.path, path_mem, (int) sizeof(path_mem)); if (path_len < 0) { http_response_status(res, 400); http_response_body(res, HTTP_STR("Invalid path")); http_response_done(res); return 1; } HTTP_String path = {path_mem, path_len}; if (!http_streq(path, route->endpoint)) return 0; route->func(req, res, route->ptr); return 1; } void http_router_resolve(HTTP_Router *router, HTTP_Request *req, HTTP_ResponseHandle res) { for (int i = 0; i < router->num_routes; i++) { Route *route = &router->routes[i]; switch (route->type) { case ROUTE_STATIC_DIR: if (serve_file_or_index(res, route->endpoint, route->path, req->url.path)) return; break; case ROUTE_DYNAMIC: if (serve_dynamic_route(route, req, res)) return; break; default: http_response_status(res, 500); http_response_done(res); return; } } http_response_status(res, 404); http_response_done(res); } int http_serve(char *addr, int port, HTTP_Router *router) { int ret; HTTP_Server *server = http_server_init_ex((HTTP_String) { addr, strlen(addr) }, port, 0, (HTTP_String) {}, (HTTP_String) {}); if (server == NULL) { http_router_free(router); return -1; } for (;;) { HTTP_Request *req; HTTP_ResponseHandle res; ret = http_server_wait(server, &req, &res); if (ret < 0) { http_server_free(server); http_router_free(router); return -1; } if (ret == 0) continue; http_router_resolve(router, req, res); } http_server_free(server); http_router_free(router); return 0; }