summaryrefslogtreecommitdiff
path: root/kit/http1.h
diff options
context:
space:
mode:
Diffstat (limited to 'kit/http1.h')
-rw-r--r--kit/http1.h511
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