diff options
Diffstat (limited to 'kit/http1.h')
-rw-r--r-- | kit/http1.h | 511 |
1 files changed, 511 insertions, 0 deletions
diff --git a/kit/http1.h b/kit/http1.h new file mode 100644 index 0000000..0a58f96 --- /dev/null +++ b/kit/http1.h @@ -0,0 +1,511 @@ +// TODO +// - Error handling +// - HTTPS support + +#ifndef KIT_HTTP1_H +#define KIT_HTTP1_H + +#include "types.h" +#include "string_builder.h" +#include "sockets.h" + +#include <stdio.h> +#include <assert.h> + +#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 < (i32) (sizeof methods / sizeof *methods)); + if (method < 0 || method >= (i32) (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 < (i64) sizeof host_str); + memcpy(host_str, uri->host.values, uri->host.size); + host_str[uri->host.size] = '\0'; + + assert(uri->port.size < (i64) 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 |