// TODO // - Error handling // - HTTPS support #ifndef KIT_HTTP1_H #define KIT_HTTP1_H #include "types.h" #include "string_builder.h" #include "sockets.h" #include #include #ifdef __cplusplus extern "C" { #endif #define KIT_HTTP1_NEWLINE "\r\n" #define KIT_HTTP1_SPACE " " #define KIT_HTTP1_HEADER_SEPARATOR ": " #define KIT_HTTP1_BUFFER_SIZE 8192 enum { KIT_HTTP1_OPTIONS, KIT_HTTP1_GET, KIT_HTTP1_HEAD, KIT_HTTP1_POST, KIT_HTTP1_PUT, KIT_HTTP1_DELETE, KIT_HTTP1_TRACE, KIT_HTTP1_CONNECT, }; typedef struct { kit_str_t key; kit_str_t value; } kit_http1_str_pair_t; typedef KIT_DA(kit_http1_str_pair_t) kit_http1_str_map_t; typedef struct { kit_str_t str; i64 position; } kit_http1_tok_t; typedef struct { kit_str_t protocol; kit_str_t host; kit_str_t port; kit_str_t address; kit_str_t query_string; kit_str_t hash; kit_http1_str_map_t parameters; } kit_http1_uri_t; typedef struct { i8 success; kit_str_builder_t protocol; kit_str_builder_t response; kit_str_builder_t response_str; kit_http1_str_map_t header; kit_str_builder_t header_str; kit_str_builder_t body; } kit_http1_response_t; #ifdef __GNUC__ # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wunused-function" # pragma GCC diagnostic ignored "-Wunknown-pragmas" #endif static kit_str_t kit_http1_tok_tail(kit_http1_tok_t *tok) { assert(tok != NULL); if (tok == NULL) return (kit_str_t) { .size = 0, .values = NULL }; i64 previous_position = tok->position; tok->position = tok->str.size; return (kit_str_t) { .size = tok->position - previous_position, .values = tok->str.values + previous_position }; } static kit_str_t kit_http1_tok_next(kit_http1_tok_t *tok, kit_str_t search, i8 return_tail) { assert(tok != NULL); if (tok == NULL) return (kit_str_t) { .size = 0, .values = NULL }; i64 hit = tok->position; while (hit + search.size <= tok->str.size) { kit_str_t s = { .size = search.size, .values = tok->str.values + hit }; if (KIT_AR_EQUAL(s, search)) break; } if (hit + search.size > tok->str.size) { if (return_tail) return kit_http1_tok_tail(tok); else return (kit_str_t) { .size = 0, .values = NULL }; } i64 previous_position = tok->position; tok->position = hit + search.size; return (kit_str_t) { .size = hit - previous_position, .values = tok->str.values + previous_position }; } // TODO // - Return error status static void kit_http1_uri_init(kit_http1_uri_t *uri, kit_str_t input, kit_allocator_t *alloc) { static const kit_str_t port_80 = { .size = 2, .values = "80" }; assert(uri != NULL); if (uri == NULL) return; memset(uri, 0, sizeof *uri); kit_http1_tok_t input_tok = { .str = input, .position = 0 }; uri->protocol = kit_http1_tok_next(&input_tok, SZ("://"), 0); kit_str_t host_port_str = kit_http1_tok_next(&input_tok, SZ("/"), 0); // FIXME // Handle invalid host port error assert(host_port_str.size > 0); if (host_port_str.size == 0) return; kit_http1_tok_t host_port_tok = { .str = host_port_str, .position = 0 }; uri->host = kit_http1_tok_next( &host_port_tok, host_port_str.values[0] == '[' ? SZ("]:") : SZ(":"), 1); if (uri->host.values[0] == '[') uri->host = (kit_str_t) { .size = uri->host.size - 1, .values = uri->host.values + 1 }; // TODO // - HTTPS default port 443 uri->port = kit_http1_tok_tail(&host_port_tok); if (uri->port.size == 0) uri->port = port_80; uri->address = kit_http1_tok_next(&input_tok, SZ("?"), 1); uri->query_string = kit_http1_tok_next(&input_tok, SZ("#"), 1); uri->hash = kit_http1_tok_tail(&input_tok); KIT_DA_INIT(uri->parameters, 0, alloc); kit_http1_tok_t query_tok = { .str = { .size = uri->query_string.size, .values = uri->query_string.values }, .position = 0 }; for (;;) { kit_str_t key = kit_http1_tok_next(&query_tok, SZ("="), 0); if (key.size == 0) break; kit_str_t value = kit_http1_tok_next(&query_tok, SZ("&"), 1); i64 index = uri->parameters.size; KIT_DA_RESIZE(uri->parameters, index + 1); assert(uri->parameters.size == index + 1); if (uri->parameters.size != index + 1) // FIXME // Handle bad alloc error break; uri->parameters.values[index] = (kit_http1_str_pair_t) { .key = key, .value = value }; } } static void kit_http1_uri_destroy(kit_http1_uri_t *uri) { assert(uri != NULL); if (uri == NULL) return; KIT_DA_DESTROY(uri->parameters); memset(uri, 0, sizeof *uri); } static kit_str_t kit_http1_method_to_str(i32 method) { static kit_str_t methods[] = { { .size = 7, .values = "OPTIONS" }, { .size = 3, .values = "GET" }, { .size = 4, .values = "HEAD" }, { .size = 4, .values = "POST" }, { .size = 3, .values = "PUT" }, { .size = 6, .values = "DELETE" }, { .size = 5, .values = "TRACE" }, { .size = 7, .values = "CONNECT" } }; assert(method >= 0 && method < sizeof methods / sizeof *methods); if (method < 0 || method >= sizeof methods / sizeof *methods) return (kit_str_t) { .size = 0, .values = NULL }; return methods[method]; } // TODO // - Return error status static socket_t kit_http1_connect_to_uri(kit_http1_uri_t *uri) { assert(uri != NULL); if (uri == NULL) return INVALID_SOCKET; struct addrinfo hints, *result, *rp; memset(&hints, 0, sizeof hints); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; char host_str[128]; char port_str[128]; assert(uri->host.size < sizeof host_str); memcpy(host_str, uri->host.values, uri->host.size); host_str[uri->host.size] = '\0'; assert(uri->port.size < sizeof port_str); memcpy(port_str, uri->port.values, uri->port.size); port_str[uri->port.size] = '\0'; i32 getaddrinfo_result = getaddrinfo(host_str, port_str, &hints, &result); if (getaddrinfo_result != 0) return INVALID_SOCKET; socket_t fd = INVALID_SOCKET; for (rp = result; rp != NULL; rp = rp->ai_next) { fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if (fd == INVALID_SOCKET) continue; i32 connect_result = connect(fd, rp->ai_addr, (socklen_t) rp->ai_addrlen); if (connect_result == -1) { closesocket(fd); fd = INVALID_SOCKET; continue; } break; } freeaddrinfo(result); return fd; } // TODO // - Return error status static kit_str_builder_t kit_http1_buffered_read( socket_t fd, kit_allocator_t *alloc) { kit_str_builder_t buf; KIT_DA_INIT(buf, KIT_HTTP1_BUFFER_SIZE, alloc); // FIXME // Handle bad alloc error assert(buf.size == KIT_HTTP1_BUFFER_SIZE); if (buf.size != KIT_HTTP1_BUFFER_SIZE) return buf; i64 offset = 0; for (;;) { i64 chunk = buf.size - offset; i64 n = recv(fd, buf.values + offset, (socklen_t) chunk, 0); if (n < chunk) break; offset += n; KIT_DA_RESIZE(buf, offset + KIT_HTTP1_BUFFER_SIZE); // FIXME // Handle bad alloc error assert(buf.size == offset + KIT_HTTP1_BUFFER_SIZE); if (buf.size != offset + KIT_HTTP1_BUFFER_SIZE) break; } return buf; } // TODO // - Return error status static kit_http1_response_t kit_http1_request( i32 method, kit_http1_uri_t *uri, kit_str_t body, kit_allocator_t *alloc) { socket_t fd = kit_http1_connect_to_uri(uri); assert(fd != INVALID_SOCKET); if (fd == INVALID_SOCKET) { kit_http1_response_t res; memset(&res, 0, sizeof res); res.success = 0; return res; } kit_str_builder_t req; // reserve KIT_DA_INIT(req, 512, alloc); assert(req.size == 512); if (req.size != 512) { // bad alloc // closesocket(fd); kit_http1_response_t res; memset(&res, 0, sizeof res); res.success = 0; return res; } req.size = 0; // FIXME // Handle errors // kit_str_append(&req, kit_http1_method_to_str(method)); kit_str_append(&req, SZ(" /")); kit_str_append(&req, uri->address); kit_str_append(&req, uri->query_string.size == 0 ? SZ("") : SZ("?")); kit_str_append(&req, uri->query_string); kit_str_append(&req, SZ(" HTTP/1.1")); kit_str_append(&req, SZ(KIT_HTTP1_NEWLINE)); kit_str_append(&req, SZ("Host: ")); kit_str_append(&req, uri->host); kit_str_append(&req, SZ(":")); kit_str_append(&req, uri->port); kit_str_append(&req, SZ(KIT_HTTP1_NEWLINE)); kit_str_append(&req, SZ("Accept: */*")); kit_str_append(&req, SZ(KIT_HTTP1_NEWLINE)); kit_str_append( &req, SZ("Content-Type: text/plain; version=0.0.4; charset=utf-8")); kit_str_append(&req, SZ(KIT_HTTP1_NEWLINE)); // FIXME // Use our own print instead char content_length_str[11]; sprintf(content_length_str, "%lld", body.size); kit_str_append(&req, SZ("Content-Length: ")); kit_str_append(&req, (kit_str_t) { .size = strlen(content_length_str), .values = content_length_str }); kit_str_append(&req, SZ(KIT_HTTP1_NEWLINE)); kit_str_append(&req, SZ(KIT_HTTP1_NEWLINE)); kit_str_append(&req, body); // Append '\0' at the end // { i64 n = req.size; KIT_DA_RESIZE(req, n + 1); assert(req.size == n + 1); if (req.size != n + 1) { KIT_DA_DESTROY(req); closesocket(fd); kit_http1_response_t res; memset(&res, 0, sizeof res); res.success = 0; return res; } req.size = n; req.values[n] = '\0'; } i64 req_size = req.size; i64 n = send(fd, req.values, (socklen_t) req.size, 0); KIT_DA_DESTROY(req); if (n != req_size) { // send failed // closesocket(fd); kit_http1_response_t res; memset(&res, 0, sizeof res); res.success = 0; return res; } kit_str_builder_t buffer = kit_http1_buffered_read(fd, alloc); closesocket(fd); kit_http1_response_t res; memset(&res, 0, sizeof res); res.success = 1; kit_http1_tok_t buf_tok = { .str = { .size = buffer.size, .values = buffer.values }, .position = 0 }; res.protocol = kit_str_build( kit_http1_tok_next(&buf_tok, SZ(KIT_HTTP1_SPACE), 0), alloc); res.response = kit_str_build( kit_http1_tok_next(&buf_tok, SZ(KIT_HTTP1_SPACE), 0), alloc); res.response_str = kit_str_build( kit_http1_tok_next(&buf_tok, SZ(KIT_HTTP1_NEWLINE), 0), alloc); res.header_str = kit_str_build( kit_http1_tok_next(&buf_tok, SZ(KIT_HTTP1_NEWLINE KIT_HTTP1_NEWLINE), 0), alloc); res.body = kit_str_build(kit_http1_tok_tail(&buf_tok), alloc); kit_http1_tok_t header_tok = { .str = (kit_str_t) { .size = res.header_str.size, .values = res.header_str.values }, .position = 0 }; KIT_DA_INIT(res.header, 0, alloc); for (;;) { kit_str_t key = kit_http1_tok_next( &header_tok, SZ(KIT_HTTP1_HEADER_SEPARATOR), 0); if (key.size == 0) break; kit_str_t value = kit_http1_tok_next(&header_tok, SZ(KIT_HTTP1_NEWLINE), 1); i64 index = res.header.size; KIT_DA_RESIZE(res.header, index + 1); assert(res.header.size == index + 1); if (res.header.size != index + 1) { // FIXME // Handle bad alloc error res.success = 0; break; } res.header.values[index] = (kit_http1_str_pair_t) { .key = key, .value = value }; } return res; } static void kit_http1_response_destroy( kit_http1_response_t *response) { assert(response != NULL); if (response == NULL) return; KIT_DA_DESTROY(response->protocol); KIT_DA_DESTROY(response->response); KIT_DA_DESTROY(response->response_str); KIT_DA_DESTROY(response->header); KIT_DA_DESTROY(response->header_str); KIT_DA_DESTROY(response->body); memset(response, 0, sizeof *response); } #ifdef __GNUC__ # pragma GCC diagnostic pop #endif #ifdef __cplusplus } #endif #endif