summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAnna (navi) Figueiredo Gomes <navi@vlhl.dev>2023-07-08 15:06:33 -0300
committerAnna (navi) Figueiredo Gomes <navi@vlhl.dev>2023-07-08 15:06:33 -0300
commit7a388dad85152a203033c14fee3c64607301865a (patch)
tree054b7bb03883576a8b68da8470908ff259e029d4 /src
libactivity: (tmp name) http request parse
the parse works via callbacks, registered to a method and path. further work will be done to simplify extraction of path, and to make the request struct more private. missing the parsing and handling of reponses. Signed-off-by: Anna (navi) Figueiredo Gomes <navi@vlhl.dev>
Diffstat (limited to 'src')
-rw-r--r--src/buffer.c81
-rw-r--r--src/http.c203
-rw-r--r--src/main.c116
-rw-r--r--src/meson.build3
-rw-r--r--src/util.c57
5 files changed, 460 insertions, 0 deletions
diff --git a/src/buffer.c b/src/buffer.c
new file mode 100644
index 0000000..0759f5d
--- /dev/null
+++ b/src/buffer.c
@@ -0,0 +1,81 @@
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "buffer.h"
+#include "util.h"
+
+static void buf_realloc(struct buffer *buf, size_t new_size) {
+ if (new_size > buf->size) {
+ buf->size = new_size + 1;
+ buf->data = realloc(buf->data, buf->size);
+ }
+}
+
+struct buffer *buf_new(const char *init) {
+ struct buffer *buf = malloc(sizeof(struct buffer));
+ if (init) {
+ buf->data = strdup(init);
+ buf->len = strlen(init);
+ buf->size = buf->len + 1;
+ } else {
+ buf->size = 1024;
+ buf->data = malloc(buf->size);
+ *buf->data = '\0';
+ buf->len = 0;
+ }
+
+ return buf;
+}
+
+void buf_del(struct buffer **buf) {
+ assert(*buf);
+ struct buffer *tmp = *buf;
+ free(tmp->data);
+ free(tmp);
+ *buf = NULL;
+}
+
+struct buffer *buf_dup(struct buffer *src) {
+ struct buffer *dest = malloc(sizeof(struct buffer));
+ dest->len = src->len;
+ dest->size = src->size;
+ dest->data = malloc(dest->size);
+ memcpy(dest->data, src->data, dest->size);
+ return dest;
+}
+
+char *buf_strdup(struct buffer *buf) {
+ return strdup(buf->data);
+}
+
+void buf_append(struct buffer *buf, const char *str) {
+ size_t len = strlen(str);
+ buf_realloc(buf, buf->len + len + 1);
+ strncat(buf->data, str, buf->size);
+}
+
+void buf_printf(struct buffer *buf, char *fmt, ...) {
+ va_list ap;
+ size_t len;
+
+ va_start(ap, fmt);
+ len = vsnprintf(buf->data, buf->size, fmt, ap);
+ va_end(ap);
+
+ if (len >= buf->size) {
+ buf_realloc(buf, len + 1);
+ va_start(ap, fmt);
+ len = vsnprintf(buf->data, buf->size, fmt, ap);
+ va_end(ap);
+ }
+
+ if (len < 0 || len >= buf->size) {
+ fprintf(stderr, "buf_printf: failed to format str");
+ exit(EXIT_FAILURE);
+ }
+
+ buf->len = len;
+}
diff --git a/src/http.c b/src/http.c
new file mode 100644
index 0000000..793f257
--- /dev/null
+++ b/src/http.c
@@ -0,0 +1,203 @@
+#include <stdlib.h>
+#include <assert.h>
+#include <string.h>
+#include <cjson/cJSON.h>
+#include <stdio.h>
+
+#include "http.h"
+#include "util.h"
+#include "buffer.h"
+
+struct http {
+ struct request_handler *requests;
+ size_t req_siz, n_requests;
+};
+
+struct request_handler {
+ char *method;
+ char *path;
+ void *data;
+ request_callback *callback;
+};
+
+struct header_item {
+ char *key;
+ char *value;
+ struct header_item *next;
+};
+
+bool parse_query(char *path, char **query) {
+ char *sep = strchr(path, '?');
+ if (!sep) {
+ *query = NULL;
+ return false;
+ }
+
+ *sep = '\0';
+ *query = strdup(sep + 1);
+
+ return true;
+}
+
+void add_header(struct header_list *list, char *header, char *value) {
+ size_t index = hash(header, MAX_HEADERS);
+ struct header_item to_insert = {0};
+ struct header_item *item = NULL;
+ to_insert.key = strdup(header);
+ to_insert.value = strdup(value);
+
+ if (!list->items[index]) {
+ list->items[index] = malloc(sizeof(struct header_item));
+ *(list->items[index]) = to_insert;
+ } else {
+ item = list->items[index];
+ while (item->next != NULL) {
+ item = item->next;
+ }
+ item->next = malloc(sizeof(struct header_item));
+ *(item->next) = to_insert;
+ }
+}
+
+const char *http_get_header(struct request *req, const char *key) {
+ size_t index = hash(key, MAX_HEADERS);
+
+ struct header_item *item = req->header.items[index];
+
+ while (item != NULL) {
+ if (strcmp(item->key, key) == 0) {
+ return item->value;
+ }
+ item = item->next;
+ }
+
+ return NULL;
+}
+
+enum parse_result {
+ PARSE_OK,
+ PARSE_ERR_UNSUPPORTED_HTTP_VERSION,
+ PARSE_MALFORMED_INPUT
+};
+
+static enum parse_result parse_request(struct request *req, const char *request) {
+ assert(req);
+ assert(request);
+
+ char *copy = strdup(request);
+ char *saveptr, *line;
+ char *path, *query;
+ char *header;
+
+ char *data = strstr(copy, "\r\n\r\n");
+ if (data) {
+ *data = '\0';
+ data += 4;
+ }
+
+ line = strtok_r(copy, "\r\n", &saveptr);
+
+ path = malloc(strlen(line) + 1);
+ req->method = malloc(strlen(line) + 1);
+
+ if (sscanf(line, "%s %s HTTP/%s", req->method, path, req->http_version) != 3) {
+ free(copy);
+ free(path);
+ free(req->method);
+ return PARSE_MALFORMED_INPUT;
+ }
+
+ if (parse_query(path, &query)) {
+ req->path = path;
+ req->query = query;
+ } else {
+ req->path = path;
+ req->query = strdup("");
+ }
+
+ for (line = strtok_r(NULL, "\r\n", &saveptr);
+ line != NULL && strlen(line) > 0;
+ line = strtok_r(NULL, "\r\n", &saveptr)) {
+
+ header = strchr(line, ':');
+ if (!header)
+ continue;
+ *(header++) = '\0';
+ while (*header == ' ')
+ header++;
+ add_header(&req->header, line, header);
+ }
+
+ if (data)
+ req->data = strdup(data);
+
+ return PARSE_OK;
+}
+
+struct buffer *http_build_reply(struct reply *reply) {
+ assert(reply);
+
+ struct buffer *buf = buf_new(NULL);
+
+ buf_printf(buf,
+ "HTTP/1.1 %d %s\r\n"
+ "Content-Length: %ld\r\n"
+ "Content-Type: %s\r\n"
+ "\r\n"
+ "%s",
+ reply->status, "OK", strlen(reply->body), "application/json", reply->body);
+
+ return buf;
+}
+
+struct reply *http_handle_request(struct http *http, struct buffer *req) {
+ struct request data = {0};
+ struct reply *ret = NULL;
+
+ if (parse_request(&data, req->data) != PARSE_OK) {
+ // todo: log
+ return NULL;
+ }
+
+ for (size_t i = 0; i < http->n_requests; i++) {
+ if (strcmp(data.method, http->requests[i].method) == 0 &&
+ strcmp(data.path, http->requests[i].path) == 0) {
+ ret = http->requests[i].callback(&data, http->requests[i].data);
+ }
+ }
+
+ if (!ret) {
+ ret = malloc(sizeof(struct reply));
+ ret->status = 404;
+ ret->body = strdup("");
+ }
+
+ return ret;
+}
+
+struct http *http_init(void) {
+ struct http *http = malloc(sizeof(struct http));
+ http->requests = malloc(sizeof(struct request_handler) * 5);
+ http->n_requests = 0;
+ http->req_siz = 5;
+ return http;
+}
+
+bool http_register_handler(struct http *http, char *method, char *path,
+ void *data, request_callback *callback) {
+ if (http->n_requests + 1 >= http->req_siz) {
+ http->req_siz += 10;
+ http->requests = realloc(http->requests,
+ sizeof(struct request_handler) * http->req_siz);
+ memset(http->requests, 0,
+ sizeof(struct request_handler) * (http->req_siz - http->n_requests));
+ }
+ struct request_handler *handle = &http->requests[http->n_requests++];
+
+ handle->method = strdup(method);
+ handle->path = strdup(path);
+ handle->data = data;
+ handle->callback = callback;
+
+ return true;
+}
diff --git a/src/main.c b/src/main.c
new file mode 100644
index 0000000..09cf045
--- /dev/null
+++ b/src/main.c
@@ -0,0 +1,116 @@
+#include <stdio.h>
+#include <cjson/cJSON.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+
+#include "buffer.h"
+#include "http.h"
+
+static struct reply *handle(struct request *req, void *data) {
+ (void)data;
+ struct reply *ret = malloc(sizeof(struct reply));
+ cJSON *obj = cJSON_CreateObject();
+ cJSON_AddStringToObject(obj, "path", req->path);
+ cJSON_AddStringToObject(obj, "query", req->query);
+ cJSON_AddStringToObject(obj, "method", req->method);
+ cJSON_AddStringToObject(obj, "version", req->http_version);
+ cJSON_AddStringToObject(obj, "hostname", http_get_header(req, "Host"));
+ if (req->data) {
+ cJSON_AddItemReferenceToObject(obj, "data", cJSON_Parse(req->data));
+ }
+
+ ret->status = 200;
+ ret->body = cJSON_Print(obj);
+ cJSON_Delete(obj);
+
+ return ret;
+}
+
+int main(int argc, char **argv) {
+ (void)argc;
+ (void)argv;
+ int server_fd = 0;
+ int client_fd = 0;
+ struct sockaddr_in addr = {
+ .sin_family = AF_INET,
+ .sin_addr.s_addr = INADDR_ANY,
+ .sin_port = htons(45748),
+ };
+
+ int addrlen = sizeof(addr);
+
+ if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
+ perror("socket failed");
+ return EXIT_FAILURE;
+ }
+
+ int opt = 1;
+
+ if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
+ perror("setsockopt failed");
+ return EXIT_FAILURE;
+ }
+
+ if (bind(server_fd, (struct sockaddr*)&addr, addrlen) < 0) {
+ perror("bind failed");
+ return EXIT_FAILURE;
+ }
+
+ if (listen(server_fd, 16) < 0) {
+ perror("listen failed");
+ return EXIT_FAILURE;
+ }
+
+ bool running = true;
+ ssize_t rlen;
+ char buf[1025];
+ struct buffer *request, *reply;
+ struct reply *rpy;
+
+ struct http *http = http_init();
+
+ http_register_handler(http, "GET", "/", NULL, &handle);
+ http_register_handler(http, "GET", "/nyaa", NULL, &handle);
+ http_register_handler(http, "POST", "/nyaa", NULL, &handle);
+
+ while (running) {
+ if ((client_fd = accept(server_fd,
+ (struct sockaddr*)&addr, (socklen_t*)&addrlen)) < 0) {
+ perror("accept failed");
+ return EXIT_FAILURE;
+ }
+
+ if ((rlen = read(client_fd, buf, 1024)) < 0) {
+ perror("read failed");
+ return EXIT_FAILURE;
+ }
+
+ buf[rlen] = '\0';
+ request = buf_new(buf);
+
+ while (rlen >= 1024) {
+ if ((rlen = read(client_fd, buf, 1024)) < 0) {
+ perror("read failed");
+ return EXIT_FAILURE;
+ }
+ buf[rlen] = '\0';
+ buf_append(request, buf);
+ }
+
+ printf("%s\n", request->data);
+
+ rpy = http_handle_request(http, request);
+ reply = http_build_reply(rpy);
+ free(rpy);
+
+ write(client_fd, reply->data, reply->size);
+
+ buf_del(&request);
+
+ close(client_fd);
+ }
+}
diff --git a/src/meson.build b/src/meson.build
new file mode 100644
index 0000000..1590aef
--- /dev/null
+++ b/src/meson.build
@@ -0,0 +1,3 @@
+executable('main', ['main.c', 'http.c', 'util.c', 'buffer.c'],
+ dependencies : jsondep,
+ include_directories: incdir)
diff --git a/src/util.c b/src/util.c
new file mode 100644
index 0000000..e6b7977
--- /dev/null
+++ b/src/util.c
@@ -0,0 +1,57 @@
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "util.h"
+
+int vasprintf(char **dest, const char *fmt, va_list ap) {
+ assert(*dest == NULL);
+ char *buf = NULL; size_t len, size;
+ va_list ap2;
+
+ size = 4096;
+ buf = malloc(size);
+
+ va_copy(ap2, ap);
+ len = vsnprintf(buf, size, fmt, ap);
+ va_end(ap);
+
+ if (len >= size) {
+ size = len + 1;
+ buf = realloc(buf, size);
+ va_copy(ap2, ap);
+ len = vsnprintf(buf, size, fmt, ap);
+ va_end(ap2);
+ }
+ if (len < 0 || len >= size) {
+ fprintf(stderr, "asprintf: failed to format buffer");
+ free(buf);
+ exit(EXIT_FAILURE);
+ }
+
+ *dest = buf;
+ return len;
+}
+
+int asprintf(char **dest, const char *fmt, ...) {
+ va_list ap;
+ int ret;
+ va_start(ap, fmt);
+ ret = vasprintf(dest, fmt, ap);
+ va_end(ap);
+ return ret;
+}
+
+size_t hash(const char *str, int max_val) {
+ size_t ret = 0;
+
+ for (const char *p = str; *p != '\0'; p++) {
+ ret += (size_t)*p + ((size_t)*p << 6) + ((size_t)*p << 16);
+ }
+
+ ret = ret % max_val;
+
+ return ret;
+}