// cHTTP, an HTTP client and server library! // // This file was generated automatically. Do not modify directly. // // Refer to the end of this file for the license #ifndef HTTP_DONT_INCLUDE #include "chttp.h" #endif //////////////////////////////////////////////////////////////////////////////////////// // src/basic.c //////////////////////////////////////////////////////////////////////////////////////// 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 <= '~'; } 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 //////////////////////////////////////////////////////////////////////////////////////// // From RFC 9112 // request-target = origin-form // / absolute-form // / authority-form // / asterisk-form // origin-form = absolute-path [ "?" query ] // absolute-form = absolute-URI // authority-form = uri-host ":" port // asterisk-form = "*" // // From RFC 9110 // URI-reference = // absolute-URI = // relative-part = // authority = // uri-host = // port = // path-abempty = // segment = // query = // // absolute-path = 1*( "/" segment ) // partial-URI = relative-part [ "?" query ] // // From RFC 3986: // segment = *pchar // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" // pct-encoded = "%" HEXDIG HEXDIG // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" // / "*" / "+" / "," / ";" / "=" // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" // query = *( pchar / "/" / "?" ) // absolute-URI = scheme ":" hier-part [ "?" query ] // hier-part = "//" authority path-abempty // / path-absolute // / path-rootless // / path-empty // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) typedef struct { char *src; int len; int cur; } Scanner; static int is_digit(char c) { return c >= '0' && c <= '9'; } static int is_alpha(char c) { return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); } static int is_hex_digit(char c) { return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'); } // From RFC 3986: // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" // / "*" / "+" / "," / ";" / "=" static int is_sub_delim(char c) { return c == '!' || c == '$' || c == '&' || c == '\'' || c == '(' || c == ')' || c == '*' || c == '+' || c == ',' || c == ';' || c == '='; } // From RFC 3986: // unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" static int is_unreserved(char c) { return is_alpha(c) || is_digit(c) || c == '-' || c == '.' || c == '_' || c == '~'; } // From RFC 3986: // pchar = unreserved / pct-encoded / sub-delims / ":" / "@" static int is_pchar(char c) { return is_unreserved(c) || is_sub_delim(c) || c == ':' || c == '@'; } static int is_tchar(char c) { return is_digit(c) || is_alpha(c) || c == '!' || c == '#' || c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '~'; } static int is_vchar(char c) { return c >= ' ' && c <= '~'; } #define CONSUME_OPTIONAL_SEQUENCE(scanner, func) \ while ((scanner)->cur < (scanner)->len && (func)((scanner)->src[(scanner)->cur])) \ (scanner)->cur++; static int consume_absolute_path(Scanner *s) { if (s->cur == s->len || s->src[s->cur] != '/') return -1; // ERROR s->cur++; for (;;) { CONSUME_OPTIONAL_SEQUENCE(s, is_pchar); if (s->cur == s->len || s->src[s->cur] != '/') break; s->cur++; } return 0; } // If abempty=1: // path-abempty = *( "/" segment ) // else: // path-absolute = "/" [ segment-nz *( "/" segment ) ] // path-rootless = segment-nz *( "/" segment ) // path-empty = 0 static int parse_path(Scanner *s, HTTP_String *path, int abempty) { int start = s->cur; if (abempty) { // path-abempty while (s->cur < s->len && s->src[s->cur] == '/') { do s->cur++; while (s->cur < s->len && is_pchar(s->src[s->cur])); } } else if (s->cur < s->len && (s->src[s->cur] == '/')) { // path-absolute s->cur++; if (s->cur < s->len && is_pchar(s->src[s->cur])) { s->cur++; for (;;) { CONSUME_OPTIONAL_SEQUENCE(s, is_pchar); if (s->cur == s->len || s->src[s->cur] != '/') break; s->cur++; } } } else if (s->cur < s->len && is_pchar(s->src[s->cur])) { // path-rootless s->cur++; for (;;) { CONSUME_OPTIONAL_SEQUENCE(s, is_pchar) if (s->cur == s->len || s->src[s->cur] != '/') break; s->cur++; } } else { // path->empty // (do nothing) } *path = (HTTP_String) { s->src + start, s->cur - start, }; if (path->len == 0) path->ptr = NULL; return 0; } // RFC 3986: // query = *( pchar / "/" / "?" ) static int is_query(char c) { return is_pchar(c) || c == '/' || c == '?'; } // RFC 3986: // fragment = *( pchar / "/" / "?" ) static int is_fragment(char c) { return is_pchar(c) || c == '/' || c == '?'; } static int little_endian(void) { uint16_t x = 1; return *((uint8_t*) &x); } static void invert_bytes(void *p, int len) { char *c = p; for (int i = 0; i < len/2; i++) { char tmp = c[i]; c[i] = c[len-i-1]; c[len-i-1] = tmp; } } static int parse_ipv4(Scanner *s, HTTP_IPv4 *ipv4) { unsigned int out = 0; int i = 0; for (;;) { if (s->cur == s->len || !is_digit(s->src[s->cur])) return -1; int b = 0; do { int x = s->src[s->cur++] - '0'; if (b > (UINT8_MAX - x) / 10) return -1; b = b * 10 + x; } while (s->cur < s->len && is_digit(s->src[s->cur])); out <<= 8; out |= (unsigned char) b; i++; if (i == 4) break; if (s->cur == s->len || s->src[s->cur] != '.') return -1; s->cur++; } if (little_endian()) invert_bytes(&out, 4); ipv4->data = out; return 0; } static int hex_digit_to_int(char c) { if (c >= 'a' && c <= 'f') return c - 'a' + 10; if (c >= 'A' && c <= 'F') return c - 'A' + 10; if (c >= '0' && c <= '9') return c - '0'; return -1; } static int parse_ipv6_comp(Scanner *s) { unsigned short buf; if (s->cur == s->len || !is_hex_digit(s->src[s->cur])) return -1; buf = hex_digit_to_int(s->src[s->cur]); s->cur++; if (s->cur == s->len || !is_hex_digit(s->src[s->cur])) return buf; buf <<= 4; buf |= hex_digit_to_int(s->src[s->cur]); s->cur++; if (s->cur == s->len || !is_hex_digit(s->src[s->cur])) return buf; buf <<= 4; buf |= hex_digit_to_int(s->src[s->cur]); s->cur++; if (s->cur == s->len || !is_hex_digit(s->src[s->cur])) return buf; buf <<= 4; buf |= hex_digit_to_int(s->src[s->cur]); s->cur++; return (int) buf; } static int parse_ipv6(Scanner *s, HTTP_IPv6 *ipv6) { unsigned short head[8]; unsigned short tail[8]; int head_len = 0; int tail_len = 0; if (s->len - s->cur > 1 && s->src[s->cur+0] == ':' && s->src[s->cur+1] == ':') s->cur += 2; else { for (;;) { int ret = parse_ipv6_comp(s); if (ret < 0) return ret; head[head_len++] = (unsigned short) ret; if (head_len == 8) break; if (s->cur == s->len || s->src[s->cur] != ':') return -1; s->cur++; if (s->cur < s->len && s->src[s->cur] == ':') { s->cur++; break; } } } if (head_len < 8) { while (s->cur < s->len && is_hex_digit(s->src[s->cur])) { int ret = parse_ipv6_comp(s); if (ret < 0) return ret; tail[tail_len++] = (unsigned short) ret; if (head_len + tail_len == 8) break; if (s->cur == s->len || s->src[s->cur] != ':') break; s->cur++; } } for (int i = 0; i < head_len; i++) ipv6->data[i] = head[i]; for (int i = 0; i < 8 - head_len - tail_len; i++) ipv6->data[head_len + i] = 0; for (int i = 0; i < tail_len; i++) ipv6->data[8 - tail_len + i] = tail[i]; if (little_endian()) for (int i = 0; i < 8; i++) invert_bytes(&ipv6->data[i], 2); return 0; } // From RFC 3986: // reg-name = *( unreserved / pct-encoded / sub-delims ) static int is_regname(char c) { return is_unreserved(c) || is_sub_delim(c); } static int parse_regname(Scanner *s, HTTP_String *regname) { if (s->cur == s->len || !is_regname(s->src[s->cur])) return -1; int start = s->cur; do s->cur++; while (s->cur < s->len && is_regname(s->src[s->cur])); regname->ptr = s->src + start; regname->len = s->cur - start; return 0; } static int parse_host(Scanner *s, HTTP_Host *host) { int ret; if (s->cur < s->len && s->src[s->cur] == '[') { s->cur++; int start = s->cur; HTTP_IPv6 ipv6; ret = parse_ipv6(s, &ipv6); if (ret < 0) return ret; host->mode = HTTP_HOST_MODE_IPV6; host->ipv6 = ipv6; host->text = (HTTP_String) { s->src + start, s->cur - start }; if (s->cur == s->len || s->src[s->cur] != ']') return -1; s->cur++; } else { int start = s->cur; HTTP_IPv4 ipv4; ret = parse_ipv4(s, &ipv4); if (ret >= 0) { host->mode = HTTP_HOST_MODE_IPV4; host->ipv4 = ipv4; } else { s->cur = start; HTTP_String regname; ret = parse_regname(s, ®name); if (ret < 0) return ret; host->mode = HTTP_HOST_MODE_NAME; host->name = regname; } host->text = (HTTP_String) { s->src + start, s->cur - start }; } return 0; } // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) static int is_scheme_head(char c) { return is_alpha(c); } static int is_scheme_body(char c) { return is_alpha(c) || is_digit(c) || c == '+' || c == '-' || c == '.'; } // userinfo = *( unreserved / pct-encoded / sub-delims / ":" ) // Note: percent-encoded characters (%XX) are not currently validated static int is_userinfo(char c) { return is_unreserved(c) || is_sub_delim(c) || c == ':'; } // authority = [ userinfo "@" ] host [ ":" port ] static int parse_authority(Scanner *s, HTTP_Authority *authority) { HTTP_String userinfo; { int start = s->cur; CONSUME_OPTIONAL_SEQUENCE(s, is_userinfo); if (s->cur < s->len && s->src[s->cur] == '@') { userinfo = (HTTP_String) { s->src + start, s->cur - start }; s->cur++; } else { // Rollback s->cur = start; userinfo = (HTTP_String) {NULL, 0}; } } HTTP_Host host; { int ret = parse_host(s, &host); if (ret < 0) return ret; } int port = 0; if (s->cur < s->len && s->src[s->cur] == ':') { s->cur++; if (s->cur < s->len && is_digit(s->src[s->cur])) { port = s->src[s->cur++] - '0'; while (s->cur < s->len && is_digit(s->src[s->cur])) { int x = s->src[s->cur++] - '0'; if (port > (UINT16_MAX - x) / 10) return -1; // ERROR: Port too big port = port * 10 + x; } } } authority->userinfo = userinfo; authority->host = host; authority->port = port; return 0; } static int parse_uri(Scanner *s, HTTP_URL *url, int allow_fragment) { HTTP_String scheme = {0}; { int start = s->cur; if (s->cur == s->len || !is_scheme_head(s->src[s->cur])) return -1; // ERROR: Missing scheme do s->cur++; while (s->cur < s->len && is_scheme_body(s->src[s->cur])); scheme = (HTTP_String) { s->src + start, s->cur - start, }; if (s->cur == s->len || s->src[s->cur] != ':') return -1; // ERROR: Missing ':' after scheme s->cur++; } int abempty = 0; HTTP_Authority authority = {0}; if (s->len - s->cur > 1 && s->src[s->cur+0] == '/' && s->src[s->cur+1] == '/') { s->cur += 2; int ret = parse_authority(s, &authority); if (ret < 0) return ret; abempty = 1; } HTTP_String path; int ret = parse_path(s, &path, abempty); if (ret < 0) return ret; HTTP_String query = {0}; if (s->cur < s->len && s->src[s->cur] == '?') { int start = s->cur; do s->cur++; while (s->cur < s->len && is_query(s->src[s->cur])); query = (HTTP_String) { s->src + start, s->cur - start, }; } HTTP_String fragment = {0}; if (allow_fragment && s->cur < s->len && s->src[s->cur] == '#') { int start = s->cur; do s->cur++; while (s->cur < s->len && is_fragment(s->src[s->cur])); fragment = (HTTP_String) { s->src + start, s->cur - start, }; } url->scheme = scheme; url->authority = authority; url->path = path; url->query = query; url->fragment = fragment; return 1; } // authority-form = host ":" port // host = IP-literal / IPv4address / reg-name // IP-literal = "[" ( IPv6address / IPvFuture ) "]" // reg-name = *( unreserved / pct-encoded / sub-delims ) static int parse_authority_form(Scanner *s, HTTP_Host *host, int *port) { int ret; ret = parse_host(s, host); if (ret < 0) return ret; // Default port value *port = 0; if (s->cur == s->len || s->src[s->cur] != ':') return 0; // No port s->cur++; if (s->cur == s->len || !is_digit(s->src[s->cur])) return 0; // No port int buf = 0; do { int x = s->src[s->cur++] - '0'; if (buf > (UINT16_MAX - x) / 10) return -1; // ERROR buf = buf * 10 + x; } while (s->cur < s->len && is_digit(s->src[s->cur])); *port = buf; return 0; } static int parse_origin_form(Scanner *s, HTTP_String *path, HTTP_String *query) { int ret, start; start = s->cur; ret = consume_absolute_path(s); if (ret < 0) return ret; *path = (HTTP_String) { s->src + start, s->cur - start }; if (s->cur < s->len && s->src[s->cur] == '?') { start = s->cur; do s->cur++; while (s->cur < s->len && is_query(s->src[s->cur])); *query = (HTTP_String) { s->src + start, s->cur - start }; } else *query = (HTTP_String) { NULL, 0 }; return 0; } static int parse_asterisk_form(Scanner *s) { if (s->len - s->cur < 2 || s->src[s->cur+0] != '*' || s->src[s->cur+1] != ' ') return -1; s->cur++; return 0; } static int parse_request_target(Scanner *s, HTTP_URL *url) { int ret; memset(url, 0, sizeof(HTTP_URL)); // asterisk-form ret = parse_asterisk_form(s); if (ret >= 0) return ret; ret = parse_uri(s, url, 0); if (ret >= 0) return ret; ret = parse_authority_form(s, &url->authority.host, &url->authority.port); if (ret >= 0) return ret; ret = parse_origin_form(s, &url->path, &url->query); if (ret >= 0) return ret; return -1; } bool consume_str(Scanner *scan, HTTP_String token) { assert(token.len > 0); if (token.len > scan->len - scan->cur) return false; for (int i = 0; i < token.len; i++) if (scan->src[scan->cur + i] != token.ptr[i]) return false; scan->cur += token.len; return true; } static int is_header_body(char c) { return is_vchar(c) || c == ' ' || c == '\t'; } static int parse_headers(Scanner *s, HTTP_Header *headers, int max_headers) { int num_headers = 0; while (!consume_str(s, HTTP_STR("\r\n"))) { // RFC 9112: // field-line = field-name ":" OWS field-value OWS // // RFC 9110: // field-value = *field-content // field-content = field-vchar // [ 1*( SP / HTAB / field-vchar ) field-vchar ] // field-vchar = VCHAR / obs-text // obs-text = %x80-FF int start; if (s->cur == s->len || !is_tchar(s->src[s->cur])) return -1; // ERROR start = s->cur; do s->cur++; while (s->cur < s->len && is_tchar(s->src[s->cur])); HTTP_String name = { s->src + start, s->cur - start }; if (s->cur == s->len || s->src[s->cur] != ':') return -1; // ERROR s->cur++; start = s->cur; CONSUME_OPTIONAL_SEQUENCE(s, is_header_body); HTTP_String body = { s->src + start, s->cur - start }; body = http_trim(body); if (num_headers < max_headers) headers[num_headers++] = (HTTP_Header) { name, body }; if (!consume_str(s, HTTP_STR("\r\n"))) { return -1; } } return num_headers; } typedef enum { TRANSFER_ENCODING_OPTION_CHUNKED, TRANSFER_ENCODING_OPTION_COMPRESS, TRANSFER_ENCODING_OPTION_DEFLATE, TRANSFER_ENCODING_OPTION_GZIP, } TransferEncodingOption; static bool is_space(char c) { return c == ' ' || c == '\t'; } static int parse_transfer_encoding(HTTP_String src, TransferEncodingOption *dst, int max) { Scanner s = { src.ptr, src.len, 0 }; int num = 0; for (;;) { CONSUME_OPTIONAL_SEQUENCE(&s, is_space); TransferEncodingOption opt; if (0) {} else if (consume_str(&s, HTTP_STR("chunked"))) opt = TRANSFER_ENCODING_OPTION_CHUNKED; else if (consume_str(&s, HTTP_STR("compress"))) opt = TRANSFER_ENCODING_OPTION_COMPRESS; else if (consume_str(&s, HTTP_STR("deflate"))) opt = TRANSFER_ENCODING_OPTION_DEFLATE; else if (consume_str(&s, HTTP_STR("gzip"))) opt = TRANSFER_ENCODING_OPTION_GZIP; else return -1; // Invalid option if (num == max) return -1; // Too many options dst[num++] = opt; CONSUME_OPTIONAL_SEQUENCE(&s, is_space); if (s.cur == s.len) break; if (s.src[s.cur] != ',') return -1; // Missing comma separator } return num; } static int parse_content_length(const char *src, int len, uint64_t *out) { int cur = 0; while (cur < len && (src[cur] == ' ' || src[cur] == '\t')) cur++; if (cur == len || !is_digit(src[cur])) return -1; uint64_t buf = 0; do { int d = src[cur++] - '0'; if (buf > (UINT64_MAX - d) / 10) return -1; buf = buf * 10 + d; } while (cur < len && is_digit(src[cur])); *out = buf; return 0; } static int parse_body(Scanner *s, HTTP_Header *headers, int num_headers, HTTP_String *body, bool body_expected) { // RFC 9112 section 6: // The presence of a message body in a request is signaled by a Content-Length or // Transfer-Encoding header field. Request message framing is independent of method // semantics. int header_index = http_find_header(headers, num_headers, HTTP_STR("Transfer-Encoding")); if (header_index != -1) { // RFC 9112 section 6.1: // A server MAY reject a request that contains both Content-Length and Transfer-Encoding // or process such a request in accordance with the Transfer-Encoding alone. Regardless, // the server MUST close the connection after responding to such a request to avoid the // potential attacks. if (http_find_header(headers, num_headers, HTTP_STR("Content-Length")) != -1) return -1; HTTP_String value = headers[header_index].value; // RFC 9112 section 6.1: // If any transfer coding other than chunked is applied to a request's content, the // sender MUST apply chunked as the final transfer coding to ensure that the message // is properly framed. If any transfer coding other than chunked is applied to a // response's content, the sender MUST either apply chunked as the final transfer // coding or terminate the message by closing the connection. TransferEncodingOption opts[8]; int num = parse_transfer_encoding(value, opts, HTTP_COUNT(opts)); if (num != 1 || opts[0] != TRANSFER_ENCODING_OPTION_CHUNKED) return -1; HTTP_String chunks_maybe[128]; HTTP_String *chunks = chunks_maybe; int num_chunks = 0; int max_chunks = HTTP_COUNT(chunks_maybe); #define FREE_CHUNK_LIST \ if (chunks != chunks_maybe) \ free(chunks); char *content_start = s->src + s->cur; for (;;) { // RFC 9112 section 7.1: // The chunked transfer coding wraps content in order to transfer it as a series of chunks, // each with its own size indicator, followed by an OPTIONAL trailer section containing // trailer fields. if (s->cur == s->len) { FREE_CHUNK_LIST return 0; // Incomplete request } if (!is_hex_digit(s->src[s->cur])) { FREE_CHUNK_LIST return -1; } int chunk_len = 0; do { char c = s->src[s->cur++]; int n = hex_digit_to_int(c); if (chunk_len > (INT_MAX - n) / 16) { FREE_CHUNK_LIST return -1; // overflow } chunk_len = chunk_len * 16 + n; } while (s->cur < s->len && is_hex_digit(s->src[s->cur])); if (s->cur == s->len) { FREE_CHUNK_LIST return 0; // Incomplete request } if (s->src[s->cur] != '\r') { FREE_CHUNK_LIST return -1; } s->cur++; if (s->cur == s->len) { FREE_CHUNK_LIST return 0; } if (s->src[s->cur] != '\n') { FREE_CHUNK_LIST return -1; } s->cur++; char *chunk_ptr = s->src + s->cur; if (chunk_len > s->len - s->cur) { FREE_CHUNK_LIST return 0; // Incomplete request } s->cur += chunk_len; if (s->cur == s->len) return 0; // Incomplete request if (s->src[s->cur] != '\r') { FREE_CHUNK_LIST return -1; } s->cur++; if (s->cur == s->len) { FREE_CHUNK_LIST return 0; // Incomplete request } if (s->src[s->cur] != '\n') { FREE_CHUNK_LIST return -1; } s->cur++; if (chunk_len == 0) break; if (num_chunks == max_chunks) { max_chunks *= 2; HTTP_String *new_chunks = malloc(max_chunks * sizeof(HTTP_String)); if (new_chunks == NULL) { if (chunks != chunks_maybe) free(chunks); return -1; } for (int i = 0; i < num_chunks; i++) new_chunks[i] = chunks[i]; if (chunks != chunks_maybe) free(chunks); chunks = new_chunks; } chunks[num_chunks++] = (HTTP_String) { chunk_ptr, chunk_len }; } char *content_ptr = content_start; for (int i = 0; i < num_chunks; i++) { memmove(content_ptr, chunks[i].ptr, chunks[i].len); content_ptr += chunks[i].len; } *body = (HTTP_String) { content_start, content_ptr - content_start }; if (chunks != chunks_maybe) free(chunks); return 1; } // RFC 9112 section 6.3: // If a valid Content-Length header field is present without Transfer-Encoding, // its decimal value defines the expected message body length in octets. header_index = http_find_header(headers, num_headers, HTTP_STR("Content-Length")); if (header_index != -1) { // Have Content-Length HTTP_String value = headers[header_index].value; uint64_t tmp; if (parse_content_length(value.ptr, value.len, &tmp) < 0) return -1; if (tmp > INT_MAX) return -1; int len = (int) tmp; if (len > s->len - s->cur) return 0; // Incomplete request *body = (HTTP_String) { s->src + s->cur, len }; s->cur += len; return 1; } // No Content-Length or Transfer-Encoding if (body_expected) return -1; *body = (HTTP_String) { NULL, 0 }; return 1; } static int contains_head(char *src, int len) { int cur = 0; while (len - cur > 3) { if (src[cur+0] == '\r' && src[cur+1] == '\n' && src[cur+2] == '\r' && src[cur+3] == '\n') return 1; cur++; } return 0; } static int parse_request(Scanner *s, HTTP_Request *req) { if (!contains_head(s->src + s->cur, s->len - s->cur)) return 0; req->secure = false; if (0) {} else if (consume_str(s, HTTP_STR("GET "))) req->method = HTTP_METHOD_GET; else if (consume_str(s, HTTP_STR("POST "))) req->method = HTTP_METHOD_POST; else if (consume_str(s, HTTP_STR("PUT "))) req->method = HTTP_METHOD_PUT; else if (consume_str(s, HTTP_STR("HEAD "))) req->method = HTTP_METHOD_HEAD; else if (consume_str(s, HTTP_STR("DELETE "))) req->method = HTTP_METHOD_DELETE; else if (consume_str(s, HTTP_STR("CONNECT "))) req->method = HTTP_METHOD_CONNECT; else if (consume_str(s, HTTP_STR("OPTIONS "))) req->method = HTTP_METHOD_OPTIONS; else if (consume_str(s, HTTP_STR("TRACE "))) req->method = HTTP_METHOD_TRACE; else if (consume_str(s, HTTP_STR("PATCH "))) req->method = HTTP_METHOD_PATCH; else return -1; { Scanner s2 = *s; int peek = s->cur; while (peek < s->len && s->src[peek] != ' ') peek++; if (peek == s->len) return -1; s2.len = peek; int ret = parse_request_target(&s2, &req->url); if (ret < 0) return ret; s->cur = s2.cur; } if (consume_str(s, HTTP_STR(" HTTP/1.1\r\n"))) { req->minor = 1; } else if (consume_str(s, HTTP_STR(" HTTP/1.0\r\n")) || consume_str(s, HTTP_STR(" HTTP/1\r\n"))) { req->minor = 0; } else { return -1; } int num_headers = parse_headers(s, req->headers, HTTP_MAX_HEADERS); if (num_headers < 0) return num_headers; req->num_headers = num_headers; // Request methods that typically don't have a body bool body_expected = true; if (req->method == HTTP_METHOD_GET || req->method == HTTP_METHOD_HEAD || req->method == HTTP_METHOD_DELETE || req->method == HTTP_METHOD_OPTIONS || req->method == HTTP_METHOD_TRACE) body_expected = false; return parse_body(s, req->headers, req->num_headers, &req->body, body_expected); } int http_find_header(HTTP_Header *headers, int num_headers, HTTP_String name) { for (int i = 0; i < num_headers; i++) if (http_streqcase(name, headers[i].name)) return i; return -1; } static int parse_response(Scanner *s, HTTP_Response *res) { if (!contains_head(s->src + s->cur, s->len - s->cur)) return 0; if (consume_str(s, HTTP_STR("HTTP/1.1 "))) { res->minor = 1; } else if (consume_str(s, HTTP_STR("HTTP/1.0 ")) || consume_str(s, HTTP_STR("HTTP/1 "))) { res->minor = 0; } else { return -1; } if (s->len - s->cur < 5 || s->src[s->cur+0] != ' ' || !is_digit(s->src[s->cur+1]) || !is_digit(s->src[s->cur+2]) || !is_digit(s->src[s->cur+3]) || s->src[s->cur+4] != ' ') return -1; s->cur += 5; res->status = (s->src[s->cur-2] - '0') * 1 + (s->src[s->cur-3] - '0') * 10 + (s->src[s->cur-4] - '0') * 100; // Parse reason phrase: HTAB / SP / VCHAR / obs-text // Note: obs-text (obsolete text, octets 0x80-0xFF) is not validated while (s->cur < s->len && ( s->src[s->cur] == '\t' || s->src[s->cur] == ' ' || is_vchar(s->src[s->cur]))) 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; // Responses with certain status codes don't have a body: // - 1xx (Informational) // - 204 (No Content) // - 304 (Not Modified) // Note: HEAD responses also don't have a body, but we can't determine // that here without access to the request method bool body_expected = true; if ((res->status >= 100 && res->status < 200) || res->status == 204 || res->status == 304) body_expected = false; return parse_body(s, res->headers, res->num_headers, &res->body, body_expected); } int http_parse_ipv4(char *src, int len, HTTP_IPv4 *ipv4) { Scanner s = {src, len, 0}; int ret = parse_ipv4(&s, ipv4); if (ret < 0) return ret; return s.cur; } int http_parse_ipv6(char *src, int len, HTTP_IPv6 *ipv6) { Scanner s = {src, len, 0}; int ret = parse_ipv6(&s, ipv6); if (ret < 0) return ret; return s.cur; } int http_parse_url(char *src, int len, HTTP_URL *url) { Scanner s = {src, len, 0}; int ret = parse_uri(&s, url, 1); if (ret == 1) return s.cur; return ret; } int http_parse_request(char *src, int len, HTTP_Request *req) { Scanner s = {src, len, 0}; int ret = parse_request(&s, req); if (ret == 1) return s.cur; return ret; } int http_parse_response(char *src, int len, HTTP_Response *res) { Scanner s = {src, len, 0}; int ret = parse_response(&s, res); if (ret == 1) return s.cur; return ret; } HTTP_String http_get_cookie(HTTP_Request *req, HTTP_String name) { // Simple cookie parsing - does not handle quoted values or special characters // See RFC 6265 for full cookie specification for (int i = 0; i < req->num_headers; i++) { if (!http_streqcase(req->headers[i].name, HTTP_STR("Cookie"))) continue; char *src = req->headers[i].value.ptr; int len = req->headers[i].value.len; int cur = 0; // Cookie: name1=value1; name2=value2; name3=value3 for (;;) { while (cur < len && src[cur] == ' ') cur++; int off = cur; while (cur < len && src[cur] != '=') cur++; HTTP_String cookie_name = { src + off, cur - off }; if (cur == len) break; cur++; off = cur; while (cur < len && src[cur] != ';') cur++; HTTP_String cookie_value = { src + off, cur - off }; if (http_streq(name, cookie_name)) return cookie_value; if (cur == len) break; cur++; } } return HTTP_STR(""); } HTTP_String http_get_param(HTTP_String body, HTTP_String str, char *mem, int cap) { // This is just a best-effort implementation char *src = body.ptr; int len = body.len; int cur = 0; if (cur < len && src[cur] == '?') cur++; while (cur < len) { HTTP_String name; { int off = cur; while (cur < len && src[cur] != '=' && src[cur] != '&') cur++; name = (HTTP_String) { src + off, cur - off }; } HTTP_String body = HTTP_STR(""); if (cur < len) { cur++; if (src[cur-1] == '=') { int off = cur; while (cur < len && src[cur] != '&') cur++; body = (HTTP_String) { src + off, cur - off }; if (cur < len) cur++; } } if (http_streq(str, name)) { bool percent_encoded = false; for (int i = 0; i < body.len; i++) if (body.ptr[i] == '+' || body.ptr[i] == '%') { percent_encoded = true; break; } if (!percent_encoded) return body; if (body.len > cap) return (HTTP_String) { NULL, 0 }; HTTP_String decoded = { mem, 0 }; for (int i = 0; i < body.len; i++) { char c = body.ptr[i]; if (c == '+') c = ' '; else { if (body.ptr[i] == '%') { if (body.len - i < 3 || !is_hex_digit(body.ptr[i+1]) || !is_hex_digit(body.ptr[i+2])) return (HTTP_String) { NULL, 0 }; int h = hex_digit_to_int(body.ptr[i+1]); int l = hex_digit_to_int(body.ptr[i+2]); c = (h << 4) | l; i += 2; } } decoded.ptr[decoded.len++] = c; } return decoded; } } return HTTP_STR(""); } int http_get_param_i(HTTP_String body, HTTP_String str) { char buf[128]; HTTP_String out = http_get_param(body, str, buf, (int) sizeof(buf)); if (out.len == 0 || !is_digit(out.ptr[0])) return -1; int cur = 0; int res = 0; do { int d = out.ptr[cur++] - '0'; if (res > (INT_MAX - d) / 10) return -1; res = res * 10 + d; } while (cur < out.len && is_digit(out.ptr[cur])); return res; } bool http_match_host(HTTP_Request *req, HTTP_String domain, int port) { int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Host")); assert(idx != -1); // Requests without the host header are always rejected char tmp[1<<8]; if (port > -1 && port != 80) { int ret = snprintf(tmp, sizeof(tmp), "%.*s:%d", domain.len, domain.ptr, port); assert(ret > 0); domain = (HTTP_String) { tmp, ret }; } HTTP_String host = req->headers[idx].value; return http_streq(host, domain); } //////////////////////////////////////////////////////////////////////////////////////// // src/thread.c //////////////////////////////////////////////////////////////////////////////////////// int mutex_init(Mutex *mutex) { #ifdef _WIN32 InitializeCriticalSection(mutex); return 0; #else if (pthread_mutex_init(mutex, NULL)) return -1; return 0; #endif } int mutex_free(Mutex *mutex) { #ifdef _WIN32 DeleteCriticalSection(mutex); return 0; #else if (pthread_mutex_destroy(mutex)) return -1; return 0; #endif } int mutex_lock(Mutex *mutex) { #ifdef _WIN32 EnterCriticalSection(mutex); return 0; #else if (pthread_mutex_lock(mutex)) return -1; return 0; #endif } int mutex_unlock(Mutex *mutex) { #ifdef _WIN32 LeaveCriticalSection(mutex); return 0; #else if (pthread_mutex_unlock(mutex)) return -1; return 0; #endif } //////////////////////////////////////////////////////////////////////////////////////// // src/secure_context.c //////////////////////////////////////////////////////////////////////////////////////// int global_secure_context_init(void) { #ifdef HTTPS_ENABLED SSL_library_init(); SSL_load_error_strings(); OpenSSL_add_all_algorithms(); #endif return 0; } int global_secure_context_free(void) { #ifdef HTTPS_ENABLED EVP_cleanup(); #endif return 0; } int client_secure_context_init(ClientSecureContext *ctx) { #ifdef HTTPS_ENABLED SSL_CTX *p = SSL_CTX_new(TLS_client_method()); if (!p) return -1; SSL_CTX_set_min_proto_version(p, TLS1_2_VERSION); SSL_CTX_set_verify(p, SSL_VERIFY_PEER, NULL); if (SSL_CTX_set_default_verify_paths(p) != 1) { SSL_CTX_free(p); return -1; } ctx->p = p; return 0; #else (void) ctx; return -1; #endif } void client_secure_context_free(ClientSecureContext *ctx) { #ifdef HTTPS_ENABLED SSL_CTX_free(ctx->p); #endif } #ifdef HTTPS_ENABLED static int servername_callback(SSL *ssl, int *ad, void *arg) { ServerSecureContext *ctx = arg; // The 'ad' parameter is used to set the alert description when returning // SSL_TLSEXT_ERR_ALERT_FATAL. Since we only return OK or NOACK, it's unused. (void) ad; const char *servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); if (servername == NULL) return SSL_TLSEXT_ERR_NOACK; for (int i = 0; i < ctx->num_certs; i++) { ServerCertificate *cert = &ctx->certs[i]; if (!strcmp(cert->domain, servername)) { SSL_set_SSL_CTX(ssl, cert->ctx); return SSL_TLSEXT_ERR_OK; } } return SSL_TLSEXT_ERR_NOACK; } #endif int server_secure_context_init(ServerSecureContext *ctx, HTTP_String cert_file, HTTP_String key_file) { #ifdef HTTPS_ENABLED SSL_CTX *p = SSL_CTX_new(TLS_server_method()); if (!p) return -1; SSL_CTX_set_min_proto_version(p, TLS1_2_VERSION); char cert_buffer[1024]; if (cert_file.len >= (int) sizeof(cert_buffer)) { SSL_CTX_free(p); return -1; } memcpy(cert_buffer, cert_file.ptr, cert_file.len); cert_buffer[cert_file.len] = '\0'; // Copy private key file path to static buffer char key_buffer[1024]; if (key_file.len >= (int) sizeof(key_buffer)) { SSL_CTX_free(p); 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(p, cert_buffer, SSL_FILETYPE_PEM) != 1) { SSL_CTX_free(p); return -1; } if (SSL_CTX_use_PrivateKey_file(p, key_buffer, SSL_FILETYPE_PEM) != 1) { SSL_CTX_free(p); return -1; } // Verify that the private key matches the certificate if (SSL_CTX_check_private_key(p) != 1) { SSL_CTX_free(p); return -1; } SSL_CTX_set_tlsext_servername_callback(p, servername_callback); SSL_CTX_set_tlsext_servername_arg(p, ctx); ctx->p = p; ctx->num_certs = 0; return 0; #else (void) ctx; (void) cert_file; (void) key_file; return -1; #endif } void server_secure_context_free(ServerSecureContext *ctx) { #ifdef HTTPS_ENABLED SSL_CTX_free(ctx->p); for (int i = 0; i < ctx->num_certs; i++) SSL_CTX_free(ctx->certs[i].ctx); #else (void) ctx; #endif } int server_secure_context_add_certificate(ServerSecureContext *ctx, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) { #ifdef HTTPS_ENABLED if (ctx->num_certs == SERVER_CERTIFICATE_LIMIT) return -1; SSL_CTX *p = SSL_CTX_new(TLS_server_method()); if (!p) return -1; SSL_CTX_set_min_proto_version(p, TLS1_2_VERSION); char cert_buffer[1024]; if (cert_file.len >= (int) sizeof(cert_buffer)) { SSL_CTX_free(p); return -1; } memcpy(cert_buffer, cert_file.ptr, cert_file.len); cert_buffer[cert_file.len] = '\0'; char key_buffer[1024]; if (key_file.len >= (int) sizeof(key_buffer)) { SSL_CTX_free(p); return -1; } memcpy(key_buffer, key_file.ptr, key_file.len); key_buffer[key_file.len] = '\0'; if (SSL_CTX_use_certificate_file(p, cert_buffer, SSL_FILETYPE_PEM) != 1) { SSL_CTX_free(p); return -1; } if (SSL_CTX_use_PrivateKey_file(p, key_buffer, SSL_FILETYPE_PEM) != 1) { SSL_CTX_free(p); return -1; } if (SSL_CTX_check_private_key(p) != 1) { SSL_CTX_free(p); return -1; } ServerCertificate *cert = &ctx->certs[ctx->num_certs]; if (domain.len >= (int) sizeof(cert->domain)) { SSL_CTX_free(p); return -1; } memcpy(cert->domain, domain.ptr, domain.len); cert->domain[domain.len] = '\0'; cert->ctx = p; ctx->num_certs++; return 0; #else (void) ctx; (void) domain; (void) cert_file; (void) key_file; return -1; #endif } //////////////////////////////////////////////////////////////////////////////////////// // src/socket.c //////////////////////////////////////////////////////////////////////////////////////// static int create_socket_pair(NATIVE_SOCKET *a, NATIVE_SOCKET *b) { #ifdef _WIN32 SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock == INVALID_SOCKET) return -1; // Bind to loopback address with port 0 (dynamic port assignment) struct sockaddr_in addr; int addr_len = sizeof(addr); memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // 127.0.0.1 addr.sin_port = 0; // Let system choose port if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { closesocket(sock); return -1; } if (getsockname(sock, (struct sockaddr*)&addr, &addr_len) == SOCKET_ERROR) { closesocket(sock); return -1; } if (connect(sock, (struct sockaddr*)&addr, sizeof(addr)) == SOCKET_ERROR) { closesocket(sock); return -1; } // Optional: Set socket to non-blocking mode // This prevents send() from blocking if the receive buffer is full u_long mode = 1; if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR) { closesocket(sock); return -1; } *a = sock; *b = sock; return 0; #else int fds[2]; if (pipe(fds) < 0) return -1; *a = fds[0]; *b = fds[1]; return 0; #endif } static int set_socket_blocking(NATIVE_SOCKET sock, bool value) { #ifdef _WIN32 u_long mode = !value; if (ioctlsocket(sock, FIONBIO, &mode) == SOCKET_ERROR) return -1; #endif #ifdef __linux__ int flags = fcntl(sock, F_GETFL, 0); if (flags < 0) return -1; if (value) flags &= ~O_NONBLOCK; else flags |= O_NONBLOCK; if (fcntl(sock, F_SETFL, flags) < 0) return -1; #endif return 0; } static NATIVE_SOCKET create_listen_socket(HTTP_String addr, Port port, bool reuse_addr, int backlog) { NATIVE_SOCKET sock = socket(AF_INET, SOCK_STREAM, 0); if (sock == NATIVE_SOCKET_INVALID) return NATIVE_SOCKET_INVALID; if (set_socket_blocking(sock, false) < 0) { CLOSE_NATIVE_SOCKET(sock); return NATIVE_SOCKET_INVALID; } 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_NATIVE_SOCKET(sock); return NATIVE_SOCKET_INVALID; } memcpy(copy, addr.ptr, addr.len); copy[addr.len] = '\0'; if (inet_pton(AF_INET, copy, &addr_buf) < 0) { CLOSE_NATIVE_SOCKET(sock); return NATIVE_SOCKET_INVALID; } } 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) { CLOSE_NATIVE_SOCKET(sock); return NATIVE_SOCKET_INVALID; } if (listen(sock, backlog) < 0) { CLOSE_NATIVE_SOCKET(sock); return NATIVE_SOCKET_INVALID; } return sock; } static void close_socket_pair(NATIVE_SOCKET a, NATIVE_SOCKET b) { #ifdef _WIN32 closesocket(a); (void) b; #else close(a); close(b); #endif } int socket_manager_init(SocketManager *sm, Socket *socks, int num_socks) { if (mutex_init(&sm->mutex) < 0) return -1; sm->plain_sock = NATIVE_SOCKET_INVALID; sm->secure_sock = NATIVE_SOCKET_INVALID; if (create_socket_pair(&sm->wait_sock, &sm->signal_sock) < 0) return -1; sm->at_least_one_secure_connect = false; sm->num_used = 0; sm->max_used = num_socks; sm->sockets = socks; for (int i = 0; i < num_socks; i++) { socks[i].state = SOCKET_STATE_FREE; socks[i].gen = 1; } return 0; } void socket_manager_free(SocketManager *sm) { close_socket_pair(sm->wait_sock, sm->signal_sock); if (sm->secure_sock != NATIVE_SOCKET_INVALID) server_secure_context_free(&sm->server_secure_context); if (sm->at_least_one_secure_connect) client_secure_context_free(&sm->client_secure_context); if (sm->plain_sock != NATIVE_SOCKET_INVALID) CLOSE_NATIVE_SOCKET(sm->plain_sock); if (sm->secure_sock != NATIVE_SOCKET_INVALID) CLOSE_NATIVE_SOCKET(sm->secure_sock); mutex_free(&sm->mutex); } int socket_manager_listen_tcp(SocketManager *sm, HTTP_String addr, Port port, int backlog, bool reuse_addr) { if (sm->plain_sock != NATIVE_SOCKET_INVALID) return -1; sm->plain_sock = create_listen_socket(addr, port, reuse_addr, backlog); if (sm->plain_sock == NATIVE_SOCKET_INVALID) return -1; return 0; } int socket_manager_listen_tls(SocketManager *sm, HTTP_String addr, Port port, int backlog, bool reuse_addr, HTTP_String cert_file, HTTP_String key_file) { if (sm->secure_sock != NATIVE_SOCKET_INVALID) return -1; sm->secure_sock = create_listen_socket(addr, port, reuse_addr, backlog); if (sm->secure_sock == NATIVE_SOCKET_INVALID) return -1; if (server_secure_context_init(&sm->server_secure_context, cert_file, key_file) < 0) { CLOSE_NATIVE_SOCKET(sm->secure_sock); sm->secure_sock = NATIVE_SOCKET_INVALID; return -1; } return 0; } int socket_manager_add_certificate(SocketManager *sm, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) { if (sm->secure_sock == NATIVE_SOCKET_INVALID) return -1; int ret = server_secure_context_add_certificate( &sm->server_secure_context, domain, cert_file, key_file); if (ret < 0) return -1; return 0; } static bool is_secure(Socket *s) { #ifdef HTTPS_ENABLED return s->server_secure_context != NULL || s->client_secure_context != NULL; #else return false; #endif } static bool connect_pending(void) { #ifdef _WIN32 return WSAGetLastError() == WSAEWOULDBLOCK; #else return errno == EINPROGRESS; #endif } static bool connect_failed_because_of_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_of_peer(void) { #ifdef _WIN32 int err = WSAGetLastError(); #else int err = errno; #endif return connect_failed_because_of_peer_2(err); } static void free_addr_list(AddressAndPort *addrs, int num_addr) { #ifdef HTTPS_ENABLED for (int i = 0; i < num_addr; i++) { RegisteredName *name = addrs[i].name; if (name) { assert(name->refs > 0); name->refs--; if (name->refs == 0) free(name); } } #else (void) addrs; (void) num_addr; #endif } // This function moves the socket state machine // to the next state until an I/O event would // be required to continue. static void socket_update(Socket *s) { // Each case of this switch encodes a state transition. // If the evaluated case requires a given I/O event to // continue, the loop will exit so that the caller can // wait for that event. If the case can continue to a // different case, the again flag is set, which causes // a different case to be evaluated. bool again; do { again = false; switch (s->state) { case SOCKET_STATE_PENDING: { // This point may be reached because // 1. The socket was just created by a connect // operation. // 2. Connecting to a host failed and now we // need to try the next one. // If (2) is true, we have some resources // to clean up. if (s->sock != NATIVE_SOCKET_INVALID) { // This is not the first attempt #ifdef HTTPS_ENABLED if (s->ssl) { SSL_free(s->ssl); s->ssl = NULL; } #endif CLOSE_NATIVE_SOCKET(s->sock); s->next_addr++; if (s->next_addr == s->num_addr) { // All addresses have been tried and failed s->state = SOCKET_STATE_DIED; s->events = 0; continue; } } AddressAndPort addr = s->addrs[s->next_addr]; int family = (addr.is_ipv4 ? AF_INET : AF_INET6); NATIVE_SOCKET sock = socket(family, SOCK_STREAM, 0); if (sock == NATIVE_SOCKET_INVALID) { s->state = SOCKET_STATE_DIED; s->events = 0; continue; } if (set_socket_blocking(sock, false) < 0) { CLOSE_NATIVE_SOCKET(sock); s->state = SOCKET_STATE_DIED; s->events = 0; continue; } int ret; if (addr.is_ipv4) { struct sockaddr_in buf; buf.sin_family = AF_INET; buf.sin_port = htons(addr.port); memcpy(&buf.sin_addr, &addr.ipv4, sizeof(HTTP_IPv4)); ret = connect(sock, (struct sockaddr*) &buf, sizeof(buf)); } else { struct sockaddr_in6 buf; buf.sin6_family = AF_INET6; buf.sin6_port = htons(addr.port); memcpy(&buf.sin6_addr, &addr.ipv6, sizeof(HTTP_IPv6)); ret = connect(sock, (struct sockaddr*) &buf, sizeof(buf)); } if (ret == 0) { // Connect resolved immediately s->sock = sock; s->state = SOCKET_STATE_CONNECTED; s->events = 0; again = true; } else if (connect_pending()) { // Connect is pending, which is expected s->sock = sock; s->state = SOCKET_STATE_CONNECTING; s->events = POLLOUT; } else if (connect_failed_because_of_peer()) { // Conenct failed due to the peer host // We should try a different address. s->sock = sock; s->state = SOCKET_STATE_PENDING; s->events = 0; again = true; } else { // An error occurred that we can't recover from s->sock = sock; s->state = SOCKET_STATE_DIED; s->events = 0; again = true; } } break; case SOCKET_STATE_CONNECTING: { // This point is reached when a connect() // operation completes. int err = 0; socklen_t len = sizeof(err); if (getsockopt(s->sock, SOL_SOCKET, SO_ERROR, (void*) &err, &len) < 0) { // Failed to get socket error status s->state = SOCKET_STATE_DIED; s->events = 0; continue; } if (err == 0) { // Connection succeded s->state = SOCKET_STATE_CONNECTED; s->events = 0; again = true; } else if (connect_failed_because_of_peer_2(err)) { // Try the next address s->state = SOCKET_STATE_PENDING; s->events = 0; again = true; } else { s->state = SOCKET_STATE_DIED; s->events = 0; } } break; case SOCKET_STATE_CONNECTED: { if (!is_secure(s)) { // We managed to connect to the peer. // We can free the target array if it // was allocated dynamically. if (s->num_addr > 1) free(s->addrs); s->events = 0; s->state = SOCKET_STATE_ESTABLISHED_READY; } else { #ifdef HTTPS_ENABLED if (s->ssl == NULL) { s->ssl = SSL_new(s->client_secure_context->p); if (s->ssl == NULL) { s->state = SOCKET_STATE_DIED; s->events = 0; break; } if (SSL_set_fd(s->ssl, s->sock) != 1) { s->state = SOCKET_STATE_DIED; s->events = 0; break; } AddressAndPort addr; if (s->num_addr > 1) addr = s->addrs[s->next_addr]; else addr = s->addr; if (addr.name) SSL_set_tlsext_host_name(s->ssl, addr.name->data); } int ret = SSL_connect(s->ssl); if (ret == 1) { // Handshake done // We managed to connect to the peer. // We can free the target array if it // was allocated dynamically. if (s->num_addr == 1) free_addr_list(&s->addr, 1); else { assert(s->num_addr > 1); free_addr_list(s->addrs, s->num_addr); free(s->addrs); } s->state = SOCKET_STATE_ESTABLISHED_READY; s->events = 0; break; } int err = SSL_get_error(s->ssl, ret); if (err == SSL_ERROR_WANT_READ) { s->events = POLLIN; break; } if (err == SSL_ERROR_WANT_WRITE) { s->events = POLLOUT; break; } s->state = SOCKET_STATE_PENDING; s->events = 0; again = true; #endif } } break; case SOCKET_STATE_ACCEPTED: { if (!is_secure(s)) { s->state = SOCKET_STATE_ESTABLISHED_READY; s->events = 0; } else { #ifdef HTTPS_ENABLED // Start server-side SSL handshake if (!s->ssl) { s->ssl = SSL_new(s->server_secure_context->p); if (s->ssl == NULL) { s->state = SOCKET_STATE_DIED; s->events = 0; break; } if (SSL_set_fd(s->ssl, s->sock) != 1) { s->state = SOCKET_STATE_DIED; s->events = 0; break; } } int ret = SSL_accept(s->ssl); if (ret == 1) { // Handshake done s->state = SOCKET_STATE_ESTABLISHED_READY; s->events = 0; break; } int err = SSL_get_error(s->ssl, ret); if (err == SSL_ERROR_WANT_READ) { s->events = POLLIN; break; } if (err == SSL_ERROR_WANT_WRITE) { s->events = POLLOUT; break; } // Server socket error - close the connection s->state = SOCKET_STATE_DIED; s->events = 0; #endif } } break; case SOCKET_STATE_ESTABLISHED_WAIT: s->state = SOCKET_STATE_ESTABLISHED_READY; s->events = 0; break; case SOCKET_STATE_SHUTDOWN: { if (!is_secure(s)) { s->state = SOCKET_STATE_DIED; s->events = 0; } else { #ifdef HTTPS_ENABLED int ret = SSL_shutdown(s->ssl); if (ret == 1) { s->state = SOCKET_STATE_DIED; s->events = 0; break; } int err = SSL_get_error(s->ssl, ret); if (err == SSL_ERROR_WANT_READ) { s->events = POLLIN; break; } if (err == SSL_ERROR_WANT_WRITE) { s->events = POLLOUT; break; } s->state = SOCKET_STATE_DIED; s->events = 0; #endif } } break; default: // Do nothing break; } } while (again); } int socket_manager_wakeup(SocketManager *sm) { if (mutex_lock(&sm->mutex) < 0) return -1; // Send a byte through the signal socket to wake up any thread // blocked on poll() with the wait socket char byte = 1; int ret = 0; #ifdef _WIN32 if (send(sm->signal_sock, &byte, 1, 0) < 0) ret = -1; #else if (write(sm->signal_sock, &byte, 1) < 0) ret = -1; #endif if (mutex_unlock(&sm->mutex) < 0) return -1; return ret; } static int socket_manager_register_events_nolock( SocketManager *sm, EventRegister *reg) { // The poll array must be able to hold descriptors // for a socket manager at full capacity. Note that // other than having a number of connection sockets, // the manager also needs 2 for the listeners and // one for the wakeup self-pipe. if (reg->max_polled < sm->max_used+3) return -1; reg->num_polled = 0; reg->polled[reg->num_polled].fd = sm->wait_sock; reg->polled[reg->num_polled].events = POLLIN; reg->polled[reg->num_polled].revents = 0; reg->ptrs[reg->num_polled] = NULL; reg->num_polled++; // If the manager isn't at full capacity, monitor // the listener sockets for incoming connections. if (sm->num_used < sm->max_used) { if (sm->plain_sock != NATIVE_SOCKET_INVALID) { reg->polled[reg->num_polled].fd = sm->plain_sock; reg->polled[reg->num_polled].events = POLLIN; reg->polled[reg->num_polled].revents = 0; reg->ptrs[reg->num_polled] = NULL; reg->num_polled++; } if (sm->secure_sock != NATIVE_SOCKET_INVALID) { reg->polled[reg->num_polled].fd = sm->secure_sock; reg->polled[reg->num_polled].events = POLLIN; reg->polled[reg->num_polled].revents = 0; reg->ptrs[reg->num_polled] = NULL; reg->num_polled++; } } // Iterate over each socket and register those that // are waiting for I/O. If at least one socket that // is ready to be processed exists, return an empty // event registration list so that those entries can // be processed immediately. for (int i = 0, j = 0; j < sm->num_used; i++) { Socket *s = &sm->sockets[i]; if (s->state == SOCKET_STATE_FREE) continue; j++; // TODO: comment if (s->state == SOCKET_STATE_DIED || s->state == SOCKET_STATE_ESTABLISHED_READY) { reg->num_polled = 0; return 0; } if (s->events) { reg->polled[reg->num_polled].fd = s->sock; reg->polled[reg->num_polled].events = s->events; reg->polled[reg->num_polled].revents = 0; reg->ptrs[reg->num_polled] = s; reg->num_polled++; } } return 0; } int socket_manager_register_events(SocketManager *sm, EventRegister *reg) { if (mutex_lock(&sm->mutex) < 0) return -1; int ret = socket_manager_register_events_nolock(sm, reg); if (mutex_unlock(&sm->mutex) < 0) return -1; return ret; } static SocketHandle socket_to_handle(SocketManager *sm, Socket *s) { return ((uint32_t) (s - sm->sockets) << 16) | s->gen; } static Socket *handle_to_socket(SocketManager *sm, SocketHandle handle) { uint16_t gen = handle & 0xFFFF; uint16_t idx = handle >> 16; if (idx >= sm->max_used) return NULL; if (sm->sockets[idx].gen != gen) return NULL; return &sm->sockets[idx]; } static int socket_manager_translate_events_nolock( SocketManager *sm, SocketEvent *events, EventRegister *reg) { int num_events = 0; for (int i = 0; i < reg->num_polled; i++) { if (!reg->polled[i].revents) continue; if (reg->polled[i].fd == sm->plain_sock || reg->polled[i].fd == sm->secure_sock) { // We only listen for input events from the listener // if the socket pool isn't fool. This ensures that // at least one socket struct is available. Note that // it's still possible that we were at capacity MAX-1 // and then got events from both the TCP and TCP/TLS // listeners, causing one to be left witout a struct. // This means we still need to check for full capacity. // Fortunately, poll() is level-triggered, which means // we'll handle this at the next iteration. if (sm->num_used == sm->max_used) continue; // Determine whether the event came from // the encrypted listener or not. bool secure = (reg->polled[i].fd == sm->secure_sock); Socket *s = sm->sockets; while (s->state != SOCKET_STATE_FREE) { s++; assert(s - sm->sockets < + sm->max_used); } NATIVE_SOCKET sock = accept(reg->polled[i].fd, NULL, NULL); if (sock == NATIVE_SOCKET_INVALID) continue; if (set_socket_blocking(sock, false) < 0) { CLOSE_NATIVE_SOCKET(sock); continue; } s->state = SOCKET_STATE_ACCEPTED; s->sock = sock; s->events = 0; s->user = NULL; #ifdef HTTPS_ENABLED s->ssl = NULL; s->server_secure_context = NULL; s->client_secure_context = NULL; if (secure) &s->server_secure_context = sm->server_secure_context; #endif socket_update(s); if (s->state == SOCKET_STATE_DIED) { CLOSE_NATIVE_SOCKET(sock); s->state = SOCKET_STATE_FREE; s->gen++; if (s->gen == 0) s->gen = 1; continue; } sm->num_used++; } else if (reg->polled[i].fd == sm->wait_sock) { // Consume one byte from the wakeup signal char byte; #ifdef _WIN32 recv(sm->wait_sock, &byte, 1, 0); #else read(sm->wait_sock, &byte, 1); #endif } else { Socket *s = reg->ptrs[i]; if (reg->polled[i].revents) socket_update(s); } } for (int i = 0, j = 0; j < sm->num_used; i++) { Socket *s = &sm->sockets[i]; if (s->state == SOCKET_STATE_FREE) continue; j++; if (s->state == SOCKET_STATE_DIED) { events[num_events++] = (SocketEvent) { SOCKET_EVENT_DISCONNECT, SOCKET_HANDLE_INVALID, s->user }; // Free resources associated to socket s->state = SOCKET_STATE_FREE; if (s->sock != NATIVE_SOCKET_INVALID) CLOSE_NATIVE_SOCKET(s->sock); if (s->sock == SOCKET_STATE_PENDING || s->sock == SOCKET_STATE_CONNECTING) { if (s->num_addr > 1) free(s->addrs); } sm->num_used--; } else if (s->state == SOCKET_STATE_ESTABLISHED_READY) { events[num_events++] = (SocketEvent) { SOCKET_EVENT_READY, socket_to_handle(sm, s), s->user }; } } return num_events; } int socket_manager_translate_events(SocketManager *sm, SocketEvent *events, EventRegister *reg) { if (mutex_lock(&sm->mutex) < 0) return -1; int ret = socket_manager_translate_events_nolock(sm, events, reg); if (mutex_unlock(&sm->mutex) < 0) return -1; return ret; } static int resolve_connect_targets(ConnectTarget *targets, int num_targets, AddressAndPort *resolved, int max_resolved) { int num_resolved = 0; for (int i = 0; i < num_targets; i++) { switch (targets[i].type) { case CONNECT_TARGET_NAME: { char portstr[16]; int len = snprintf(portstr, sizeof(portstr), "%u", targets[i].port); assert(len > 1 && len < (int) sizeof(portstr)); struct addrinfo hints = {0}; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; #ifdef HTTPS_ENABLED RegisteredName *name = malloc(sizeof(RegisteredName) + targets[i].name.len + 1); if (name == NULL) { free_addr_list(resolved, num_resolved); return -1; } name->refs = 0; memcpy(name->data, targets[i].name.ptr, targets[i].name.len); name->data[targets[i].name.len] = '\0'; char *hostname = name->data; #else // 512 bytes is more than enough for a DNS hostname (max 253 chars) char hostname[1<<9]; if (targets[i].name.len >= (int) sizeof(hostname)) return -1; memcpy(hostname, targets[i].name.ptr, targets[i].name.len); hostname[targets[i].name.len] = '\0'; #endif struct addrinfo *res = NULL; int ret = getaddrinfo(hostname, portstr, &hints, &res); if (ret != 0) { #ifdef HTTPS_ENABLED // Free the name allocated for this target free(name); #endif free_addr_list(resolved, num_resolved); return -1; } for (struct addrinfo *rp = res; rp; rp = rp->ai_next) { if (rp->ai_family == AF_INET) { HTTP_IPv4 ipv4 = *(HTTP_IPv4*) &((struct sockaddr_in*)rp->ai_addr)->sin_addr; if (num_resolved < max_resolved) { resolved[num_resolved].is_ipv4 = true; resolved[num_resolved].ipv4 = ipv4; resolved[num_resolved].port = targets[i].port; #ifdef HTTPS_ENABLED resolved[num_resolved].name = name; name->refs++; #endif num_resolved++; } } else if (rp->ai_family == AF_INET6) { HTTP_IPv6 ipv6 = *(HTTP_IPv6*) &((struct sockaddr_in6*)rp->ai_addr)->sin6_addr; if (num_resolved < max_resolved) { resolved[num_resolved].is_ipv4 = false; resolved[num_resolved].ipv6 = ipv6; resolved[num_resolved].port = targets[i].port; #ifdef HTTPS_ENABLED resolved[num_resolved].name = name; name->refs++; #endif num_resolved++; } } } #ifdef HTTPS_ENABLED if (name->refs == 0) free(name); #endif freeaddrinfo(res); } break; case CONNECT_TARGET_IPV4: if (num_resolved < max_resolved) { resolved[num_resolved].is_ipv4 = true; resolved[num_resolved].ipv4 = targets[i].ipv4; resolved[num_resolved].port = targets[i].port; #ifdef HTTPS_ENABLED resolved[num_resolved].name = NULL; #endif num_resolved++; } break; case CONNECT_TARGET_IPV6: if (num_resolved < max_resolved) { resolved[num_resolved].is_ipv4 = false; resolved[num_resolved].ipv6 = targets[i].ipv6; resolved[num_resolved].port = targets[i].port; #ifdef HTTPS_ENABLED resolved[num_resolved].name = NULL; #endif num_resolved++; } break; } } return num_resolved; } #define MAX_CONNECT_TARGETS 16 int socket_connect(SocketManager *sm, int num_targets, ConnectTarget *targets, bool secure, void *user) { if (sm->num_used == sm->max_used) return -1; #ifdef HTTPS_ENABLED if (!sm->at_least_one_secure_connect) { if (client_secure_context_init(&sm->client_secure_context) < 0) return -1; sm->at_least_one_secure_connect = true; } #else if (secure) return -1; #endif AddressAndPort resolved[MAX_CONNECT_TARGETS]; int num_resolved = resolve_connect_targets( targets, num_targets, resolved, MAX_CONNECT_TARGETS); if (num_resolved <= 0) return -1; Socket *s = sm->sockets; while (s->state != SOCKET_STATE_FREE) { s++; assert(s - sm->sockets < + sm->max_used); } if (num_resolved == 1) { s->num_addr = 1; s->next_addr = 0; s->addr = resolved[0]; } else { s->num_addr = num_resolved; s->next_addr = 0; s->addrs = malloc(num_resolved * sizeof(AddressAndPort)); if (s->addrs == NULL) return -1; for (int i = 0; i < num_resolved; i++) s->addrs[i] = resolved[i]; } s->state = SOCKET_STATE_PENDING; s->sock = NATIVE_SOCKET_INVALID; s->user = user; #ifdef HTTPS_ENABLED s->server_secure_context = NULL; s->client_secure_context = NULL; s->ssl = NULL; if (secure) s->client_secure_context = &sm->client_secure_context; #endif sm->num_used++; return 0; } 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 } static int socket_recv_nolock(SocketManager *sm, SocketHandle handle, char *dst, int max) { Socket *s = handle_to_socket(sm, handle); if (s == NULL) return 0; if (s->state != SOCKET_STATE_ESTABLISHED_READY) { s->state = SOCKET_STATE_DIED; s->events = 0; return 0; } if (!is_secure(s)) { int ret = recv(s->sock, dst, max, 0); if (ret == 0) { s->state = SOCKET_STATE_DIED; s->events = 0; } else if (ret < 0) { if (would_block()) { s->state = SOCKET_STATE_ESTABLISHED_WAIT; s->events = POLLIN; } else if (!interrupted()) { s->state = SOCKET_STATE_DIED; s->events = 0; } ret = 0; } return ret; } else { #ifdef HTTPS_ENABLED int ret = SSL_read(s->ssl, dst, max); if (ret <= 0) { int err = SSL_get_error(s->ssl, ret); if (err == SSL_ERROR_WANT_READ) { s->state = SOCKET_STATE_ESTABLISHED_WAIT; s->events = POLLIN; } else if (err == SSL_ERROR_WANT_WRITE) { s->state = SOCKET_STATE_ESTABLISHED_WAIT; s->events = POLLOUT; } else { s->state = SOCKET_STATE_DIED; s->events = 0; } ret = 0; } return ret; #endif } } int socket_recv(SocketManager *sm, SocketHandle handle, char *dst, int max) { if (mutex_lock(&sm->mutex) < 0) return -1; int ret = socket_recv_nolock(sm, handle, dst, max); if (mutex_unlock(&sm->mutex) < 0) return -1; return ret; } static int socket_send_nolock(SocketManager *sm, SocketHandle handle, char *src, int len) { Socket *s = handle_to_socket(sm, handle); if (s == NULL) return 0; if (s->state != SOCKET_STATE_ESTABLISHED_READY) { s->state = SOCKET_STATE_DIED; s->events = 0; return 0; } if (!is_secure(s)) { int ret = send(s->sock, src, len, 0); if (ret < 0) { if (would_block()) { s->state = SOCKET_STATE_ESTABLISHED_WAIT; s->events = POLLOUT; } else if (!interrupted()) { s->state = SOCKET_STATE_DIED; s->events = 0; } ret = 0; } return ret; } else { #ifdef HTTPS_ENABLED int ret = SSL_write(s->ssl, src, len); if (ret <= 0) { int err = SSL_get_error(s->ssl, ret); if (err == SSL_ERROR_WANT_READ) { s->state = SOCKET_STATE_ESTABLISHED_WAIT; s->events = POLLIN; } else if (err == SSL_ERROR_WANT_WRITE) { s->state = SOCKET_STATE_ESTABLISHED_WAIT; s->events = POLLOUT; } else { s->state = SOCKET_STATE_DIED; s->events = 0; } ret = 0; } return ret; #endif } } int socket_send(SocketManager *sm, SocketHandle handle, char *src, int len) { if (mutex_lock(&sm->mutex) < 0) return -1; int ret = socket_send_nolock(sm, handle, src, len); if (mutex_unlock(&sm->mutex) < 0) return -1; return ret; } int socket_close(SocketManager *sm, SocketHandle handle) { if (mutex_lock(&sm->mutex) < 0) return -1; int ret; Socket *s = handle_to_socket(sm, handle); if (s == NULL) ret = -1; else { // Only transition to SHUTDOWN if socket is not already DIED if (s->state != SOCKET_STATE_DIED) { s->state = SOCKET_STATE_SHUTDOWN; s->events = 0; socket_update(s); } } if (mutex_unlock(&sm->mutex) < 0) return -1; return ret; } int socket_is_secure(SocketManager *sm, SocketHandle handle) { if (mutex_lock(&sm->mutex) < 0) return -1; Socket *s = handle_to_socket(sm, handle); int ret; if (s == NULL) ret = -1; else ret = is_secure(s); if (mutex_unlock(&sm->mutex) < 0) return -1; return ret; } int socket_set_user(SocketManager *sm, SocketHandle handle, void *user) { if (mutex_lock(&sm->mutex) < 0) return -1; int ret; Socket *s = handle_to_socket(sm, handle); if (s == NULL) ret = -1; else { s->user = user; ret = 0; } if (mutex_unlock(&sm->mutex) < 0) return -1; return ret; } //////////////////////////////////////////////////////////////////////////////////////// // src/byte_queue.c //////////////////////////////////////////////////////////////////////////////////////// void byte_queue_init(ByteQueue *queue, uint32_t limit) { 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; } // Deinitialize the queue void byte_queue_free(ByteQueue *queue) { if (queue->read_target) { if (queue->read_target != queue->data) free(queue->read_target); queue->read_target = NULL; queue->read_target_size = 0; } free(queue->data); queue->data = NULL; } int byte_queue_error(ByteQueue *queue) { return queue->flags & BYTE_QUEUE_ERROR; } int byte_queue_empty(ByteQueue *queue) { return queue->used == 0; } int byte_queue_full(ByteQueue *queue) { return queue->used == queue->limit; } ByteView byte_queue_read_buf(ByteQueue *queue) { if (queue->flags & BYTE_QUEUE_ERROR) return (ByteView) {NULL, 0}; assert((queue->flags & BYTE_QUEUE_READ) == 0); queue->flags |= BYTE_QUEUE_READ; queue->read_target = queue->data; queue->read_target_size = queue->size; if (queue->data == NULL) return (ByteView) {NULL, 0}; return (ByteView) { queue->data + queue->head, queue->used }; } void byte_queue_read_ack(ByteQueue *queue, uint32_t num) { if (queue->flags & BYTE_QUEUE_ERROR) return; if ((queue->flags & BYTE_QUEUE_READ) == 0) return; queue->flags &= ~BYTE_QUEUE_READ; assert((uint32_t) num <= queue->used); queue->head += (uint32_t) num; queue->used -= (uint32_t) num; queue->curs += (uint32_t) num; if (queue->read_target) { if (queue->read_target != queue->data) free(queue->read_target); queue->read_target = NULL; queue->read_target_size = 0; } } ByteView byte_queue_write_buf(ByteQueue *queue) { if ((queue->flags & BYTE_QUEUE_ERROR) || queue->data == NULL) return (ByteView) {NULL, 0}; assert((queue->flags & BYTE_QUEUE_WRITE) == 0); queue->flags |= BYTE_QUEUE_WRITE; return (ByteView) { queue->data + (queue->head + queue->used), queue->size - (queue->head + queue->used), }; } void byte_queue_write_ack(ByteQueue *queue, uint32_t num) { if (queue->flags & BYTE_QUEUE_ERROR) return; if ((queue->flags & BYTE_QUEUE_WRITE) == 0) return; queue->flags &= ~BYTE_QUEUE_WRITE; queue->used += num; } int byte_queue_write_setmincap(ByteQueue *queue, uint32_t 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. assert((queue->flags & BYTE_QUEUE_WRITE) == 0); uint32_t total_free_space = queue->size - queue->used; uint32_t free_space_after_data = queue->size - queue->used - queue->head; int moved = 0; if (free_space_after_data < mincap) { if (total_free_space < mincap || (queue->read_target == queue->data)) { // Resize required if (queue->used + mincap > queue->limit) { queue->flags |= BYTE_QUEUE_ERROR; return 0; } uint32_t size; if (queue->size > UINT32_MAX / 2) size = UINT32_MAX; else size = 2 * queue->size; if (size < queue->used + mincap) size = queue->used + mincap; if (size > queue->limit) size = queue->limit; uint8_t *data = malloc(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) free(queue->data); 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; } void byte_queue_write(ByteQueue *queue, void *ptr, uint32_t len) { byte_queue_write_setmincap(queue, len); ByteView dst = byte_queue_write_buf(queue); if (dst.ptr) { memcpy(dst.ptr, ptr, len); byte_queue_write_ack(queue, len); } } void byte_queue_write_fmt2(ByteQueue *queue, const char *fmt, va_list args) { if (queue->flags & BYTE_QUEUE_ERROR) return; va_list args2; va_copy(args2, args); byte_queue_write_setmincap(queue, 128); ByteView dst = byte_queue_write_buf(queue); int len = vsnprintf(dst.ptr, dst.len, fmt, args); if (len < 0) { queue->flags |= BYTE_QUEUE_ERROR; va_end(args2); return; } if (len > dst.len) { byte_queue_write_ack(queue, 0); byte_queue_write_setmincap(queue, len+1); dst = byte_queue_write_buf(queue); vsnprintf(dst.ptr, dst.len, fmt, args2); } byte_queue_write_ack(queue, len); va_end(args2); } void byte_queue_write_fmt(ByteQueue *queue, const char *fmt, ...) { va_list args; va_start(args, fmt); byte_queue_write_fmt2(queue, fmt, args); va_end(args); } ByteQueueOffset byte_queue_offset(ByteQueue *queue) { if (queue->flags & BYTE_QUEUE_ERROR) return (ByteQueueOffset) { 0 }; return (ByteQueueOffset) { queue->curs + queue->used }; } void byte_queue_patch(ByteQueue *queue, ByteQueueOffset off, void *src, uint32_t len) { if (queue->flags & BYTE_QUEUE_ERROR) return; // Check that the offset is in range assert(off >= queue->curs && off - queue->curs < queue->used); // Check that the length is in range assert(len <= queue->used - (off - queue->curs)); // Perform the patch uint8_t *dst = queue->data + queue->head + (off - queue->curs); memcpy(dst, src, len); } uint32_t byte_queue_size_from_offset(ByteQueue *queue, ByteQueueOffset off) { return queue->curs + queue->used - off; } void byte_queue_remove_from_offset(ByteQueue *queue, ByteQueueOffset offset) { if (queue->flags & BYTE_QUEUE_ERROR) return; uint64_t num = (queue->curs + queue->used) - offset; assert(num <= queue->used); queue->used -= num; } //////////////////////////////////////////////////////////////////////////////////////// // src/cert.c //////////////////////////////////////////////////////////////////////////////////////// #ifdef HTTPS_ENABLED static EVP_PKEY *generate_rsa_key_pair(int key_bits) { EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); if (!ctx) return NULL; if (EVP_PKEY_keygen_init(ctx) <= 0) { EVP_PKEY_CTX_free(ctx); return NULL; } if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, key_bits) <= 0) { EVP_PKEY_CTX_free(ctx); return NULL; } EVP_PKEY *pkey = NULL; if (EVP_PKEY_keygen(ctx, &pkey) <= 0) { EVP_PKEY_CTX_free(ctx); return NULL; } EVP_PKEY_CTX_free(ctx); return pkey; } static X509 *create_certificate(EVP_PKEY *pkey, HTTP_String C, HTTP_String O, HTTP_String CN, int days) { X509 *x509 = X509_new(); if (!x509) return NULL; // Set version (version 3) X509_set_version(x509, 2); // Set serial number ASN1_INTEGER_set(X509_get_serialNumber(x509), 1); // Set validity period X509_gmtime_adj(X509_get_notBefore(x509), 0); X509_gmtime_adj(X509_get_notAfter(x509), 31536000L * days); // days * seconds_per_year // Set public key X509_set_pubkey(x509, pkey); // Set subject name X509_NAME *name = X509_get_subject_name(x509); X509_NAME_add_entry_by_txt(name, "C", MBSTRING_ASC, (unsigned char*) C.ptr, C.len, -1, 0); X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, (unsigned char*) O.ptr, O.len, -1, 0); X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, (unsigned char*) CN.ptr, CN.len, -1, 0); // Set issuer name (same as subject for self-signed) X509_set_issuer_name(x509, name); if (!X509_sign(x509, pkey, EVP_sha256())) { X509_free(x509); return NULL; } return x509; } static int save_private_key(EVP_PKEY *pkey, HTTP_String file) { char copy[1<<10]; if (file.len >= (int) sizeof(copy)) return -1; memcpy(copy, file.ptr, file.len); copy[file.len] = '\0'; FILE *fp = fopen(copy, "wb"); if (!fp) return -1; // Write private key in PEM format if (!PEM_write_PrivateKey(fp, pkey, NULL, NULL, 0, NULL, NULL)) { fclose(fp); return -1; } fclose(fp); return 0; } static int save_certificate(X509 *x509, HTTP_String file) { char copy[1<<10]; if (file.len >= (int) sizeof(copy)) return -1; memcpy(copy, file.ptr, file.len); copy[file.len] = '\0'; FILE *fp = fopen(copy, "wb"); if (!fp) return -1; // Write certificate in PEM format if (!PEM_write_X509(fp, x509)) { fclose(fp); return -1; } fclose(fp); return 0; } int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, HTTP_String cert_file, HTTP_String key_file) { EVP_PKEY *pkey = generate_rsa_key_pair(2048); if (pkey == NULL) return -1; X509 *x509 = create_certificate(pkey, C, O, CN, 1); if (x509 == NULL) { EVP_PKEY_free(pkey); return -1; } if (save_private_key(pkey, key_file) < 0) { X509_free(x509); EVP_PKEY_free(pkey); return -1; } if (save_certificate(x509, cert_file) < 0) { X509_free(x509); EVP_PKEY_free(pkey); return -1; } X509_free(x509); EVP_PKEY_free(pkey); return 0; } #else int http_create_test_certificate(HTTP_String C, HTTP_String O, HTTP_String CN, HTTP_String cert_file, HTTP_String key_file) { (void) C; (void) O; (void) CN; (void) cert_file; (void) key_file; return -1; } #endif //////////////////////////////////////////////////////////////////////////////////////// // src/client.c //////////////////////////////////////////////////////////////////////////////////////// static void http_client_conn_init(HTTP_ClientConn *conn, SocketHandle handle, uint32_t input_buffer_limit, uint32_t output_buffer_limit) { conn->state = HTTP_CLIENT_CONN_WAIT_LINE; conn->handle = handle; conn->gen = 0; byte_queue_init(&conn->input, input_buffer_limit); byte_queue_init(&conn->output, output_buffer_limit); } static void http_client_conn_free(HTTP_ClientConn *conn) { byte_queue_free(&conn->output); byte_queue_free(&conn->input); } int http_client_init(HTTP_Client *client) { client->input_buffer_limit = 1<<20; client->output_buffer_limit = 1<<20; client->num_conns = 0; for (int i = 0; i < HTTP_CLIENT_CAPACITY; i++) { client->conns[i].state = HTTP_CLIENT_CONN_FREE; client->conns[i].gen = 0; } client->num_ready = 0; client->ready_head = 0; if (socket_manager_init(&client->sockets, client->socket_pool, HTTP_CLIENT_CAPACITY) < 0) return -1; return 0; } void http_client_free(HTTP_Client *client) { socket_manager_free(&client->sockets); for (int i = 0, j = 0; j < client->num_conns; i++) { HTTP_ClientConn *conn = &client->conns[i]; if (conn->state == HTTP_CLIENT_CONN_FREE) continue; j++; http_client_conn_free(conn); } } void http_client_set_input_limit(HTTP_Client *client, uint32_t limit) { client->input_buffer_limit = limit; } void http_client_set_output_limit(HTTP_Client *client, uint32_t limit) { client->output_buffer_limit = limit; } int http_client_wakeup(HTTP_Client *client) { if (socket_manager_wakeup(&client->sockets) < 0) return -1; return 0; } int http_client_register_events(HTTP_Client *client, EventRegister *reg) { if (socket_manager_register_events(&client->sockets, reg) < 0) return -1; return 0; } // Get a connection pointer from a request builder. // If the builder is invalid, returns NULL. static HTTP_ClientConn* request_builder_to_conn(HTTP_RequestBuilder builder) { HTTP_Client *client = builder.client; if (client == NULL) return NULL; if (builder.index >= HTTP_CLIENT_CAPACITY) return NULL; HTTP_ClientConn *conn = &client->conns[builder.index]; if (builder.gen != conn->gen) return NULL; return conn; } int http_client_get_builder(HTTP_Client *client, HTTP_Response *response, HTTP_RequestBuilder *builder) { HTTP_ClientConn *conn = NULL; if (response != NULL && response->context != NULL) { // Reuse the connection from the previous response conn = (HTTP_ClientConn*) response->context; // Mark the response as freed response->context = NULL; // Reset the connection for a new request byte_queue_read_ack(&conn->input, byte_queue_read_buf(&conn->input).len); byte_queue_read_ack(&conn->output, byte_queue_read_buf(&conn->output).len); conn->state = HTTP_CLIENT_CONN_WAIT_LINE; } else { // Find a free connection slot if (client->num_conns == HTTP_CLIENT_CAPACITY) return -1; int i = 0; while (client->conns[i].state != HTTP_CLIENT_CONN_FREE) i++; conn = &client->conns[i]; conn->state = HTTP_CLIENT_CONN_WAIT_LINE; conn->handle = SOCKET_HANDLE_INVALID; conn->client = client; byte_queue_init(&conn->input, client->input_buffer_limit); byte_queue_init(&conn->output, client->output_buffer_limit); client->num_conns++; } *builder = (HTTP_RequestBuilder) { client, conn - client->conns, conn->gen }; return 0; } void http_request_builder_url(HTTP_RequestBuilder builder, HTTP_Method method, HTTP_String url) { HTTP_ClientConn *conn = request_builder_to_conn(builder); if (conn == NULL) return; if (conn->state != HTTP_CLIENT_CONN_WAIT_LINE) return; // Parse the URL to extract components HTTP_URL parsed_url; if (http_parse_url(url.ptr, url.len, &parsed_url) != 1) return; // Store method and parsed URL for connection establishment conn->method = method; conn->url = parsed_url; // Convert method enum to string const char *method_str; switch (method) { case HTTP_METHOD_GET: method_str = "GET"; break; case HTTP_METHOD_HEAD: method_str = "HEAD"; break; case HTTP_METHOD_POST: method_str = "POST"; break; case HTTP_METHOD_PUT: method_str = "PUT"; break; case HTTP_METHOD_DELETE: method_str = "DELETE"; break; case HTTP_METHOD_CONNECT: method_str = "CONNECT"; break; case HTTP_METHOD_OPTIONS: method_str = "OPTIONS"; break; case HTTP_METHOD_TRACE: method_str = "TRACE"; break; case HTTP_METHOD_PATCH: method_str = "PATCH"; break; default: return; } // Build request line: METHOD path HTTP/1.1\r\n byte_queue_write_fmt(&conn->output, "%s %.*s HTTP/1.1\r\n", method_str, parsed_url.path.len, parsed_url.path.ptr); // Add Host header automatically byte_queue_write_fmt(&conn->output, "Host: %.*s", parsed_url.authority.host.text.len, parsed_url.authority.host.text.ptr); if (parsed_url.authority.port > 0) { byte_queue_write_fmt(&conn->output, ":%d", parsed_url.authority.port); } byte_queue_write(&conn->output, "\r\n", 2); conn->state = HTTP_CLIENT_CONN_WAIT_HEADER; } void http_request_builder_header(HTTP_RequestBuilder builder, HTTP_String str) { HTTP_ClientConn *conn = request_builder_to_conn(builder); if (conn == NULL) return; if (conn->state != HTTP_CLIENT_CONN_WAIT_HEADER) return; // Validate header: must contain a colon and no control characters bool has_colon = false; for (int i = 0; i < str.len; i++) { char c = str.ptr[i]; if (c == ':') has_colon = true; // Reject control characters (especially \r and \n) if (c < 0x20 && c != '\t') return; } if (!has_colon) return; byte_queue_write(&conn->output, str.ptr, str.len); byte_queue_write(&conn->output, "\r\n", 2); } void http_request_builder_body(HTTP_RequestBuilder builder, HTTP_String str) { HTTP_ClientConn *conn = request_builder_to_conn(builder); if (conn == NULL) return; // Transition from WAIT_HEADER to WAIT_BODY if needed if (conn->state == HTTP_CLIENT_CONN_WAIT_HEADER) { // End headers section byte_queue_write(&conn->output, "\r\n", 2); conn->state = HTTP_CLIENT_CONN_WAIT_BODY; } if (conn->state != HTTP_CLIENT_CONN_WAIT_BODY) return; byte_queue_write(&conn->output, str.ptr, str.len); } int http_request_builder_send(HTTP_RequestBuilder builder) { HTTP_ClientConn *conn = request_builder_to_conn(builder); if (conn == NULL) return -1; // Finalize the request if (conn->state == HTTP_CLIENT_CONN_WAIT_HEADER) { // No body, just end headers byte_queue_write(&conn->output, "\r\n", 2); } // Establish connection if not already connected if (conn->handle == SOCKET_HANDLE_INVALID) { // Determine if connection should be secure bool secure = false; if (conn->url.scheme.len == 5 && strncmp(conn->url.scheme.ptr, "https", 5) == 0) { secure = true; } // Prepare connection target ConnectTarget target; target.port = conn->url.authority.port; if (target.port <= 0) target.port = secure ? 443 : 80; // Set up target based on host type if (conn->url.authority.host.mode == HTTP_HOST_MODE_NAME) { target.type = CONNECT_TARGET_NAME; target.name = conn->url.authority.host.name; } else if (conn->url.authority.host.mode == HTTP_HOST_MODE_IPV4) { target.type = CONNECT_TARGET_IPV4; target.ipv4 = conn->url.authority.host.ipv4; } else if (conn->url.authority.host.mode == HTTP_HOST_MODE_IPV6) { target.type = CONNECT_TARGET_IPV6; target.ipv6 = conn->url.authority.host.ipv6; } else { // Invalid host mode - clean up connection http_client_conn_free(conn); conn->state = HTTP_CLIENT_CONN_FREE; conn->client->num_conns--; return -1; } if (socket_connect(&conn->client->sockets, 1, &target, secure, conn) < 0) { // Connection failed - clean up http_client_conn_free(conn); conn->state = HTTP_CLIENT_CONN_FREE; conn->client->num_conns--; return -1; } } conn->state = HTTP_CLIENT_CONN_FLUSHING; conn->gen++; return 0; } // Look at the input buffer to see if a complete response // was buffered. If it was, change the connection's status // to COMPLETE and push it to the ready queue. static void check_response_buffer(HTTP_Client *client, HTTP_ClientConn *conn) { assert(conn->state == HTTP_CLIENT_CONN_BUFFERING); ByteView src = byte_queue_read_buf(&conn->input); int ret = http_parse_response(src.ptr, src.len, &conn->response); if (ret < 0) { // Invalid response byte_queue_read_ack(&conn->input, 0); socket_close(&client->sockets, conn->handle); } else if (ret == 0) { // Still waiting byte_queue_read_ack(&conn->input, 0); // If the queue reached its limit and we still didn't receive // a complete response, abort the exchange. if (byte_queue_full(&conn->input)) socket_close(&client->sockets, conn->handle); } else { // Ready assert(ret == 1); conn->state = HTTP_CLIENT_CONN_COMPLETE; conn->response.context = conn; // Push to the ready queue assert(client->num_ready < HTTP_CLIENT_CAPACITY); int tail = (client->ready_head + client->num_ready) % HTTP_CLIENT_CAPACITY; client->ready[tail] = conn - client->conns; client->num_ready++; } } int http_client_process_events(HTTP_Client *client, EventRegister *reg) { SocketEvent events[HTTP_CLIENT_CAPACITY]; int num_events = socket_manager_translate_events( &client->sockets, events, reg); if (num_events < 0) return -1; for (int i = 0; i < num_events; i++) { HTTP_ClientConn *conn = events[i].user; if (events[i].type == SOCKET_EVENT_DISCONNECT) { if (conn != NULL) { http_client_conn_free(conn); conn->state = HTTP_CLIENT_CONN_FREE; client->num_conns--; } } else if (events[i].type == SOCKET_EVENT_READY) { if (conn == NULL) continue; // Store the handle if this is a new connection if (conn->handle == SOCKET_HANDLE_INVALID) conn->handle = events[i].handle; if (conn->state == HTTP_CLIENT_CONN_FLUSHING) { // Send request data int num = 0; ByteView src = byte_queue_read_buf(&conn->output); if (src.len) num = socket_send(&client->sockets, conn->handle, src.ptr, src.len); byte_queue_read_ack(&conn->output, num); if (byte_queue_error(&conn->output)) { socket_close(&client->sockets, conn->handle); } else if (byte_queue_empty(&conn->output)) { // Request fully sent, now wait for response conn->state = HTTP_CLIENT_CONN_BUFFERING; } } else if (conn->state == HTTP_CLIENT_CONN_BUFFERING) { // Receive response data int min_recv = 1<<10; byte_queue_write_setmincap(&conn->input, min_recv); int num = 0; ByteView dst = byte_queue_write_buf(&conn->input); if (dst.len) num = socket_recv(&client->sockets, conn->handle, dst.ptr, dst.len); byte_queue_write_ack(&conn->input, num); if (byte_queue_error(&conn->input)) socket_close(&client->sockets, conn->handle); else check_response_buffer(client, conn); } } } return 0; } bool http_client_next_response(HTTP_Client *client, HTTP_Response **response) { if (client->num_ready == 0) return false; HTTP_ClientConn *conn = &client->conns[client->ready[client->ready_head]]; client->ready_head = (client->ready_head + 1) % HTTP_CLIENT_CAPACITY; client->num_ready--; assert(conn->state == HTTP_CLIENT_CONN_COMPLETE); *response = &conn->response; return true; } void http_free_response(HTTP_Response *res) { if (res == NULL || res->context == NULL) return; HTTP_ClientConn *conn = (HTTP_ClientConn*) res->context; // Free the connection resources http_client_conn_free(conn); conn->state = HTTP_CLIENT_CONN_FREE; conn->client->num_conns--; // Mark response as freed res->context = NULL; } //////////////////////////////////////////////////////////////////////////////////////// // src/server.c //////////////////////////////////////////////////////////////////////////////////////// static void http_server_conn_init(HTTP_ServerConn *conn, SocketHandle handle, uint32_t input_buffer_limit, uint32_t output_buffer_limit) { conn->state = HTTP_SERVER_CONN_BUFFERING; conn->handle = handle; conn->closing = false; byte_queue_init(&conn->input, input_buffer_limit); byte_queue_init(&conn->output, output_buffer_limit); } static void http_server_conn_free(HTTP_ServerConn *conn) { byte_queue_free(&conn->output); byte_queue_free(&conn->input); } int http_server_init(HTTP_Server *server) { server->input_buffer_limit = 1<<20; server->output_buffer_limit = 1<<20; server->trace_bytes = false; server->reuse_addr = false; server->backlog = 32; server->num_conns = 0; for (int i = 0; i < HTTP_SERVER_CAPACITY; i++) { server->conns[i].state = HTTP_SERVER_CONN_FREE; server->conns[i].gen = 0; } server->num_ready = 0; server->ready_head = 0; if (socket_manager_init(&server->sockets, server->socket_pool, HTTP_SERVER_CAPACITY) < 0) return -1; return 0; } void http_server_free(HTTP_Server *server) { socket_manager_free(&server->sockets); for (int i = 0, j = 0; j < server->num_conns; i++) { HTTP_ServerConn *conn = &server->conns[i]; if (conn->state == HTTP_SERVER_CONN_FREE) continue; j++; http_server_conn_free(conn); } } void http_server_set_input_limit(HTTP_Server *server, uint32_t limit) { server->input_buffer_limit = limit; } void http_server_set_output_limit(HTTP_Server *server, uint32_t limit) { server->output_buffer_limit = limit; } void http_server_set_trace_bytes(HTTP_Server *server, bool value) { server->trace_bytes = value; } void http_server_set_reuse_addr(HTTP_Server *server, bool reuse) { server->reuse_addr = reuse; } void http_server_set_backlog(HTTP_Server *server, int backlog) { server->backlog = backlog; } int http_server_listen_tcp(HTTP_Server *server, HTTP_String addr, Port port) { if (socket_manager_listen_tcp(&server->sockets, addr, port, server->backlog, server->reuse_addr) < 0) return -1; return 0; } int http_server_listen_tls(HTTP_Server *server, HTTP_String addr, Port port, HTTP_String cert_file_name, HTTP_String key_file_name) { if (socket_manager_listen_tls(&server->sockets, addr, port, server->backlog, server->reuse_addr, cert_file_name, key_file_name) < 0) return -1; return 0; } int http_server_add_certificate(HTTP_Server *server, HTTP_String domain, HTTP_String cert_file, HTTP_String key_file) { if (socket_manager_add_certificate(&server->sockets, domain, cert_file, key_file) < 0) return -1; return 0; } int http_server_wakeup(HTTP_Server *server) { if (socket_manager_wakeup(&server->sockets) < 0) return -1; return 0; } int http_server_register_events(HTTP_Server *server, EventRegister *reg) { if (socket_manager_register_events(&server->sockets, reg) < 0) return -1; return 0; } // Look at the head of the input buffer to see if // a request was buffered. If it was, change the // connection's status to WAIT_STATUS and push it // to the ready queue. If the request is invalid, // close the socket. static void check_request_buffer(HTTP_Server *server, HTTP_ServerConn *conn) { assert(conn->state == HTTP_SERVER_CONN_BUFFERING); ByteView src = byte_queue_read_buf(&conn->input); int ret = http_parse_request(src.ptr, src.len, &conn->request); if (ret < 0) { // Invalid request byte_queue_read_ack(&conn->input, 0); socket_close(&server->sockets, conn->handle); } else if (ret == 0) { // Still waiting byte_queue_read_ack(&conn->input, 0); // If the queue reached its limit and we still didn't receive // a complete request, abort the exchange. if (byte_queue_full(&conn->input)) socket_close(&server->sockets, conn->handle); } else { // Ready assert(ret > 0); conn->state = HTTP_SERVER_CONN_WAIT_STATUS; conn->request_len = ret; conn->response_offset = byte_queue_offset(&conn->output); // Push to the ready queue assert(server->num_ready < HTTP_SERVER_CAPACITY); int tail = (server->ready_head + server->num_ready) % HTTP_SERVER_CAPACITY; server->ready[tail] = conn - server->conns; server->num_ready++; } } static void http_server_conn_process_events(HTTP_Server *server, HTTP_ServerConn *conn) { if (conn->state == HTTP_SERVER_CONN_FLUSHING) { ByteView src = byte_queue_read_buf(&conn->output); int num = 0; if (src.len) num = socket_send(&server->sockets, conn->handle, src.ptr, src.len); if (server->trace_bytes) print_bytes(HTTP_STR("<< "), (HTTP_String) { src.ptr, num }); byte_queue_read_ack(&conn->output, num); if (byte_queue_error(&conn->output)) { socket_close(&server->sockets, conn->handle); return; } if (byte_queue_empty(&conn->output)) { // We finished sending the response. Now we can // either close the connection or process a new // buffered request. if (conn->closing) { socket_close(&server->sockets, conn->handle); return; } conn->state = HTTP_SERVER_CONN_BUFFERING; } } if (conn->state == HTTP_SERVER_CONN_BUFFERING) { int min_recv = 1<<10; byte_queue_write_setmincap(&conn->input, min_recv); // Note that it's extra important that we don't // buffer while the user is building the response. // If we did that, a resize would invalidate all // pointers on the parsed request structure. ByteView dst = byte_queue_write_buf(&conn->input); int num = 0; if (dst.len) num = socket_recv(&server->sockets, conn->handle, dst.ptr, dst.len); if (server->trace_bytes) print_bytes(HTTP_STR(">> "), (HTTP_String) { dst.ptr, num }); byte_queue_write_ack(&conn->input, num); if (byte_queue_error(&conn->output)) { socket_close(&server->sockets, conn->handle); } else { check_request_buffer(server, conn); } } } int http_server_process_events(HTTP_Server *server, EventRegister *reg) { SocketEvent events[HTTP_SERVER_CAPACITY]; int num_events = socket_manager_translate_events(&server->sockets, events, reg); if (num_events < 0) return -1; for (int i = 0; i < num_events; i++) { HTTP_ServerConn *conn = events[i].user; if (events[i].type == SOCKET_EVENT_DISCONNECT) { http_server_conn_free(conn); server->num_conns--; } else if (events[i].type == SOCKET_EVENT_READY) { if (events[i].user == NULL) { if (server->num_conns == HTTP_SERVER_CAPACITY) { socket_close(&server->sockets, events[i].handle); continue; } int j = 0; while (server->conns[j].state != HTTP_SERVER_CONN_FREE) { j++; assert(i < HTTP_SERVER_CAPACITY); } conn = &server->conns[j]; http_server_conn_init(conn, events[i].handle, server->input_buffer_limit, server->output_buffer_limit); server->num_conns++; socket_set_user(&server->sockets, events[i].handle, conn); } http_server_conn_process_events(server, conn); } } return 0; } bool http_server_next_request(HTTP_Server *server, HTTP_Request **request, HTTP_ResponseBuilder *builder) { if (server->num_ready == 0) return false; HTTP_ServerConn *conn = &server->conns[server->ready[server->ready_head]]; server->ready_head = (server->ready_head + 1) % HTTP_SERVER_CAPACITY; server->num_ready--; assert(conn->state == HTTP_SERVER_CONN_WAIT_STATUS); *request = &conn->request; *builder = (HTTP_ResponseBuilder) { server, conn - server->conns, conn->gen }; return true; } // Get a connection pointer from a response builder. // If the builder is invalid, returns NULL. // Note that only connections in the responding states // can be returned, as any builder is invalidated by // incrementing the connection's generation counter // when a response is completed. static HTTP_ServerConn* builder_to_conn(HTTP_ResponseBuilder builder) { HTTP_Server *server = builder.server; if (server == NULL) return NULL; if (builder.index > HTTP_SERVER_CAPACITY) return NULL; HTTP_ServerConn *conn = &server->conns[builder.index]; if (builder.gen != conn->gen) return NULL; return conn; } 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 "???"; } static void write_status(HTTP_ServerConn *conn, int status) { byte_queue_write_fmt(&conn->output, "HTTP/1.1 %d %s\r\n", status, get_status_text(status)); } void http_response_builder_status(HTTP_ResponseBuilder builder, int status) { HTTP_ServerConn *conn = builder_to_conn(builder); if (conn == NULL) return; if (conn->state != HTTP_SERVER_CONN_WAIT_STATUS) { // Reset all response content and start from scrach. byte_queue_remove_from_offset(&conn->output, conn->response_offset); conn->state = HTTP_SERVER_CONN_WAIT_STATUS; } write_status(conn, status); conn->state = HTTP_SERVER_CONN_WAIT_HEADER; } void http_response_builder_header(HTTP_ResponseBuilder builder, HTTP_String str) { HTTP_ServerConn *conn = builder_to_conn(builder); if (conn == NULL) return; if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER) return; // Validate header: must contain a colon and no control characters // (to prevent HTTP response splitting attacks) bool has_colon = false; for (int i = 0; i < str.len; i++) { char c = str.ptr[i]; if (c == ':') has_colon = true; // Reject control characters (especially \r and \n) if (c < 0x20 && c != '\t') return; } if (!has_colon) return; byte_queue_write(&conn->output, str.ptr, str.len); byte_queue_write(&conn->output, "\r\n", 2); } static void append_special_headers(HTTP_ServerConn *conn) { HTTP_String s; if (conn->closing) { s = HTTP_STR("Connection: Close\r\n"); byte_queue_write(&conn->output, s.ptr, s.len); } else { s = HTTP_STR("Connection: Keep-Alive\r\n"); byte_queue_write(&conn->output, s.ptr, s.len); } s = HTTP_STR("Content-Length: "); byte_queue_write(&conn->output, s.ptr, s.len); conn->content_length_value_offset = byte_queue_offset(&conn->output); #define TEN_SPACES " " _Static_assert(sizeof(TEN_SPACES) == 10+1); s = HTTP_STR(TEN_SPACES "\r\n"); byte_queue_write(&conn->output, s.ptr, s.len); byte_queue_write(&conn->output, "\r\n", 2); conn->content_length_offset = byte_queue_offset(&conn->output); } static void patch_special_headers(HTTP_ServerConn *conn) { int content_length = byte_queue_size_from_offset(&conn->output, conn->content_length_offset); char tmp[11]; int len = snprintf(tmp, sizeof(tmp), "%d", content_length); assert(len > 0 && len < 11); byte_queue_patch(&conn->output, conn->content_length_value_offset, tmp, len); } void http_response_builder_body(HTTP_ResponseBuilder builder, HTTP_String str) { HTTP_ServerConn *conn = builder_to_conn(builder); if (conn == NULL) return; if (conn->state != HTTP_SERVER_CONN_WAIT_HEADER) { append_special_headers(conn); conn->state = HTTP_SERVER_CONN_WAIT_BODY; } if (conn->state != HTTP_SERVER_CONN_WAIT_BODY) return; byte_queue_write(&conn->output, str.ptr, str.len); } void http_response_builder_send(HTTP_ResponseBuilder builder) { HTTP_ServerConn *conn = builder_to_conn(builder); if (conn == NULL) return; if (conn->state == HTTP_SERVER_CONN_WAIT_STATUS) { write_status(conn, 500); conn->state = HTTP_SERVER_CONN_WAIT_HEADER; } if (conn->state == HTTP_SERVER_CONN_WAIT_HEADER) { append_special_headers(conn); conn->state = HTTP_SERVER_CONN_WAIT_BODY; } assert(conn->state == HTTP_SERVER_CONN_WAIT_BODY); patch_special_headers(conn); // Remove the buffered request byte_queue_read_ack(&conn->input, conn->request_len); conn->state = HTTP_SERVER_CONN_FLUSHING; conn->gen++; http_server_conn_process_events(builder.server, conn); } //////////////////////////////////////////////////////////////////////////////////////// // Copyright 2025 Francesco Cozzuto // // Permission is hereby granted, free of charge, to any person // obtaining a copy of this software and associated documentation // files (the "Software"), to deal in the Software without // restriction, including without limitation the rights to use, // copy, modify, merge, publish, distribute, sublicense, and/or // sell copies of the Software, and to permit persons to whom // the Software is furnished to do so, subject to the following // conditions: // // The above copyright notice and this permission notice shall // be included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES // OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT // HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, // WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. ////////////////////////////////////////////////////////////////////////////////////////