diff options
author | Anna (navi) Figueiredo Gomes <navi@vlhl.dev> | 2023-07-08 15:06:33 -0300 |
---|---|---|
committer | Anna (navi) Figueiredo Gomes <navi@vlhl.dev> | 2023-07-08 15:06:33 -0300 |
commit | 7a388dad85152a203033c14fee3c64607301865a (patch) | |
tree | 054b7bb03883576a8b68da8470908ff259e029d4 /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.c | 81 | ||||
-rw-r--r-- | src/http.c | 203 | ||||
-rw-r--r-- | src/main.c | 116 | ||||
-rw-r--r-- | src/meson.build | 3 | ||||
-rw-r--r-- | src/util.c | 57 |
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; +} |