diff --git a/Makefile b/Makefile index d6d9364..6e13422 100644 --- a/Makefile +++ b/Makefile @@ -111,12 +111,14 @@ $(BUILD_DIR)/$(TEST_DIR)/%.c.o: $(TEST_DIR)/%.c lint: clang-format -n --Werror $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) make -C lsm lint + make -C lnm lint make -C landerctl lint .PHONY: fmt fmt: clang-format -i $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) make -C lsm fmt + make -C lnm fmt make -C landerctl fmt .PHONY: check @@ -133,12 +135,14 @@ check: -j$(shell nproc) \ $(SRCS) make -C lsm check + make -C lnm check make -C landerctl check .PHONY: clean clean: rm -rf $(BUILD_DIR) $(MAKE) -C lsm clean + $(MAKE) -C lnm clean $(MAKE) -C landerctl clean .PHONY: bear diff --git a/config.mk b/config.mk index 244a7b2..39269d8 100644 --- a/config.mk +++ b/config.mk @@ -8,7 +8,7 @@ TEST_DIR = test THIRDPARTY_DIR = thirdparty INC_DIRS = include $(THIRDPARTY_DIR)/include lsm/include lnm/include -LIBS = m lsm lnm +LIBS = lsm lnm LIB_DIRS = ./lsm/build ./lnm/build # -MMD: generate a .d file for every source file. This file can be imported by diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index 791393e..8c50ec1 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -54,7 +54,7 @@ void lnm_http_loop_process_route(lnm_http_conn *conn) { for (size_t i = 0; i < gctx->routes.len && match_level < 3; i++) { route = gctx->routes.arr[i]; - bool matched_path; + bool matched_path = false; switch (route->type) { case lnm_http_route_type_literal: @@ -238,7 +238,7 @@ void lnm_http_loop_process_write_body(lnm_http_conn *conn) { size_t to_write = LNM_MIN(res->body.len - res->written, LNM_LOOP_BUF_SIZE - conn->w.size); - size_t written; + size_t written = 0; switch (res->body.type) { case lnm_http_res_body_type_buf: diff --git a/src/event_loop/event_loop.c b/src/event_loop/event_loop.c deleted file mode 100644 index a01ca37..0000000 --- a/src/event_loop/event_loop.c +++ /dev/null @@ -1,192 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "event_loop.h" -#include "log.h" - -static void event_loop_fd_set_nb(int fd) { - int flags = fcntl(fd, F_GETFL); - - flags |= O_NONBLOCK; - - fcntl(fd, F_SETFL, flags); -} - -event_loop *event_loop_init() { - event_loop *el = calloc(sizeof(event_loop), 1); - - // No idea if this is a good starter value - el->connections = calloc(sizeof(event_loop_conn *), 16); - el->connection_count = 16; - - return el; -} - -int event_loop_put(event_loop *el, event_loop_conn *conn) { - if ((size_t)conn->fd >= el->connection_count) { - event_loop_conn **resized = - realloc(el->connections, sizeof(event_loop_conn *) * (conn->fd + 1)); - - if (resized == NULL) { - return -1; - } - - el->connections = resized; - el->connection_count = conn->fd + 1; - } - - el->connections[conn->fd] = conn; - - return 0; -} - -int event_loop_accept(event_loop *el, int fd) { - struct sockaddr_in client_addr; - socklen_t socklen = sizeof(client_addr); - int connfd = accept(fd, (struct sockaddr *)&client_addr, &socklen); - - if (connfd < 0) { - return -1; - } - - // set the new connection fd to nonblocking mode - event_loop_fd_set_nb(connfd); - - // creating the struct Conn - event_loop_conn *conn = event_loop_conn_init(el); - - // Close the connectoin if we fail to allocate a connection struct - if (conn == NULL) { - close(connfd); - - return -3; - } - - conn->fd = connfd; - conn->state = event_loop_conn_state_req; - - int res = event_loop_put(el, conn); - - if (res != 0) { - close(connfd); - - return -4; - } - - debug("Connection established on fd %i", connfd); - - return 0; -} - -void event_loop_run(event_loop *el, int port) { - int fd = socket(AF_INET, SOCK_STREAM, 0); - - if (fd < 0) { - critical(1, "Failed to open listening socket, errno: %i", errno); - } - - int val = 1; - setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)); - - // bind - struct sockaddr_in addr = {.sin_family = AF_INET, - .sin_port = ntohs(port), - .sin_addr.s_addr = ntohl(0)}; - - int res = bind(fd, (const struct sockaddr *)&addr, sizeof(addr)); - - if (res) { - critical(1, "Failed to bind listening socket, errno: %i", errno); - } - - debug("Listening socket bound to fd %i", fd); - - res = listen(fd, SOMAXCONN); - - if (res) { - critical(1, "Failed to start listening on listening socket, errno: %i", - errno); - } - - // The listening socket is always poll'ed in non-blocking mode as well - event_loop_fd_set_nb(fd); - - // TODO don't hardcode the number 32 - struct pollfd *poll_args = calloc(sizeof(struct pollfd), 32); - - // for convenience, the listening fd is put in the first position - struct pollfd pfd = {fd, POLLIN, 0}; - poll_args[0] = pfd; - - event_loop_conn *conn; - int events; - - info("Starting event loop on port %i", port); - - while (1) { - size_t poll_args_count = 1; - - // connection fds - for (size_t i = 0; i < el->connection_count; i++) { - conn = el->connections[i]; - - if (conn == NULL) { - continue; - } - - events = (conn->state == event_loop_conn_state_req) ? POLLIN : POLLOUT; - events |= POLLERR; - - pfd.fd = conn->fd; - pfd.events = events; - - poll_args[poll_args_count] = pfd; - poll_args_count++; - - // We do at most 32 connections at a time for now - if (poll_args_count == 32) - break; - } - - // poll for active fds - // the timeout argument doesn't matter here - int rv = poll(poll_args, (nfds_t)poll_args_count, -1); - - if (rv < 0) { - critical(1, "Poll failed, errno: %i", errno); - } - - // process active connections - for (size_t i = 1; i < poll_args_count; i++) { - if (poll_args[i].revents) { - conn = el->connections[poll_args[i].fd]; - - event_loop_conn_io(el, conn); - - if (conn->state == event_loop_conn_state_end) { - // client closed normally, or something bad happened. - // destroy this connection - el->connections[conn->fd] = NULL; - - close(conn->fd); - debug("Connection closed on fd %i", conn->fd); - event_loop_conn_free(el, conn); - } - } - } - - // try to accept a new connection if the listening fd is active - if (poll_args[0].revents) { - event_loop_accept(el, fd); - } - } -} diff --git a/src/event_loop/event_loop_conn.c b/src/event_loop/event_loop_conn.c deleted file mode 100644 index 4791d8f..0000000 --- a/src/event_loop/event_loop_conn.c +++ /dev/null @@ -1,13 +0,0 @@ -#include "event_loop.h" - -event_loop_conn *event_loop_conn_init(event_loop *el) { - event_loop_conn *conn = calloc(sizeof(event_loop_conn), 1); - conn->ctx = el->ctx_init(el->gctx); - - return conn; -} - -void event_loop_conn_free(event_loop *el, event_loop_conn *conn) { - el->ctx_free(conn->ctx); - free(conn); -} diff --git a/src/event_loop/event_loop_io.c b/src/event_loop/event_loop_io.c deleted file mode 100644 index 674c6ec..0000000 --- a/src/event_loop/event_loop_io.c +++ /dev/null @@ -1,98 +0,0 @@ -#include -#include -#include - -#include "event_loop.h" - -void event_loop_conn_io_res(event_loop *el, event_loop_conn *conn) { - do { - ssize_t res = 0; - size_t remain = conn->wbuf_size - conn->wbuf_sent; - - do { - res = write(conn->fd, &conn->wbuf[conn->wbuf_sent], remain); - } while (res < 0 && errno == EINTR); - - // EAGAIN doesn't mean there was an error, but rather that there's no more - // data right now, but there might be more later, aka "try again later" - if (res < 0 && errno == EAGAIN) { - return; - } - - // If it's not EGAIN, there was an error writing so we simply end the - // request - if (res < 0) { - conn->state = event_loop_conn_state_end; - return; - } - - conn->wbuf_sent += (size_t)res; - - // After writing the entire buffer, we run the write_data command to receive - // new data, or exit the loop - if (conn->wbuf_sent == conn->wbuf_size) { - conn->wbuf_sent = 0; - conn->wbuf_size = 0; - - el->write_data(conn); - } - } while (conn->state == event_loop_conn_state_res); -} - -/** - * Read new data into the read buffer. This command performs at most one - * successful read syscall. - * - * Returns whether the function should be retried immediately or not. - */ -void event_loop_conn_io_req(event_loop *el, event_loop_conn *conn) { - do { - // Move remaining data to start of buffer - memmove(conn->rbuf, &conn->rbuf[conn->rbuf_read], - conn->rbuf_size - conn->rbuf_read); - conn->rbuf_size -= conn->rbuf_read; - conn->rbuf_read = 0; - - ssize_t res; - size_t cap = EVENT_LOOP_BUFFER_SIZE - conn->rbuf_size; - - // Try to read at most cap bytes from the file descriptor - do { - res = read(conn->fd, &conn->rbuf[conn->rbuf_size], cap); - } while (res < 0 && errno == EINTR); - - // EGAIN means we try again later - if (res < 0 && errno == EAGAIN) { - return; - } - - // Any other negative error message means the read errored out. If res is 0, - // we've reached the end of the input which (usually) means the remote peer - // has closed the connection. Either way, we close the connection. - if (res <= 0) { - conn->state = event_loop_conn_state_end; - - return; - } - - conn->rbuf_size += (size_t)res; - - // This loop allows processing multiple requests from a single read buffer - while (el->handle_data(conn)) - ; - } - // We can keep reading as long as we're in request mode - while (conn->state == event_loop_conn_state_req); -} - -void event_loop_conn_io(event_loop *el, event_loop_conn *conn) { - switch (conn->state) { - case event_loop_conn_state_req: - event_loop_conn_io_req(el, conn); - break; - case event_loop_conn_state_res: - event_loop_conn_io_res(el, conn); - break; - case event_loop_conn_state_end:; - } -} diff --git a/src/http/http_consts.c b/src/http/http_consts.c deleted file mode 100644 index a072309..0000000 --- a/src/http/http_consts.c +++ /dev/null @@ -1,133 +0,0 @@ -#include - -#include "http/types.h" - -// Very important that this is in the same order as http_request_method -const char *http_method_names[] = {"GET", "POST", "PUT", "PATCH", "DELETE"}; -const size_t http_method_names_len = - sizeof(http_method_names) / sizeof(http_method_names[0]); - -// clang-format off - -const char *http_status_names[][32] = { - // 1xx - { - "Continue", // 100 - "Switching Protocols", // 101, - "Processing", // 102 - "Early Hints", // 103 - }, - // 2xx - { - "OK", // 200 - "Created", // 201 - "Accepted", // 202 - "Non-Authoritative Information", // 203 - "No Content", // 204 - "Reset Content", // 205 - "Partial Content", // 206 - "Multi-Status", // 207 - "Already Reported", // 208 - }, - // 3xx - { - "Multiple Choices", // 300 - "Moved Permanently", // 301 - "Found", // 302 - "See Other", // 303 - "Not Modified", // 304 - NULL, // 305 - NULL, // 306 - "Temporary Redirect", // 307 - "Permanent Redirect", // 308 - }, - // 4xx - { - "Bad Request", // 400 - "Unauthorized", // 401 - "Payment Required", // 402 - "Forbidden", // 403 - "Not Found", // 404 - "Method Not Allowed", // 405 - "Not Acceptable", // 406 - "Proxy Authentication Required", // 407 - "Request Timeout", // 408 - "Conflict", // 409 - "Gone", // 410 - "Length Required", // 411 - "Precondition Failed", // 412 - "Content Too Large", // 413 - "URI Too Long", // 414 - "Unsupported Media Type", // 415 - "Range Not Satisfiable", // 416 - "Expectation Failed", // 417 - "I'm a teapot", // 418 - NULL, // 419 - NULL, // 420 - "Misdirected Request", // 421 - "Unprocessable Content", // 422 - "Locked", // 423 - "Failed Dependency", // 424 - "Too Early", // 425 - "Upgrade Required", // 426 - NULL, // 427 - "Precondition Required", // 428 - "Too Many Requests", // 429 - NULL, // 430 - "Request Header Fields Too Large", // 431 - }, - // 5xx - { - "Internal Server Error", // 500 - "Not Implemented", // 501 - "Bad Gateway", // 502 - "Service Unavailable", // 503 - "Gateway Timeout", // 504 - "HTTP Version Not Supported", // 505 - "Variant Also Negotiates", // 506 - "Insufficient Storage", // 507 - "Loop Detected", // 508 - NULL, // 509 - "Not Extended", // 510 - "Network Authentication Required" // 511 - }, -}; - -const char *http_header_names[] = { - "Connection", - "Location", - "Content-Type", - "Content-Disposition", - "Server", - "Content-Length" -}; - -const char *http_mime_type_names[][2] = { - { "aac", "audio/aac" }, - { "bz", "application/x-bzip" }, - { "bz2", "application/x-bzip2" }, - { "css", "text/css" }, - { "csv", "text/csv" }, - { "gz", "application/gzip" }, - { "gif", "image/gif" }, - { "htm", "text/html" }, - { "html", "text/html" }, - { "jar", "application/java-archive" }, - { "jpeg", "image/jpeg" }, - { "jpg", "image/jpeg" }, - { "js", "text/javascript" }, - { "json", "application/json" }, - { "mp3", "audio/mpeg" }, - { "mp4", "video/mp4" }, - { "png", "image/png" }, - { "pdf", "application/pdf" }, - { "rar", "application/vnd.rar" }, - { "sh", "application/x-sh" }, - { "svg", "image/svg+xml" }, - { "tar", "application/x-tar" }, - { "txt", "text/plain" }, - { "wav", "audio/wav" }, - { "7z", "application/x-7z-compressed" }, -}; - -// clang-format on diff --git a/src/http/res.c b/src/http/res.c deleted file mode 100644 index d965802..0000000 --- a/src/http/res.c +++ /dev/null @@ -1,52 +0,0 @@ -#include -#include - -#include "http/res.h" - -void http_res_set_body_buf(http_response *res, const char *body, - size_t body_len, bool owned) { - res->body.type = http_body_buf; - res->body.buf = (char *)body; - res->body.expected_len = body_len; - res->body.buf_owned = owned; -} - -void http_res_set_body_file(http_response *res, const char *filename) { - struct stat st; - stat(filename, &st); - - // TODO error handling - FILE *f = fopen(filename, "r"); - - res->body.type = http_body_file; - res->body.file = f; - res->body.expected_len = st.st_size; -} - -void http_res_add_header(http_response *res, http_header type, - const char *value, bool owned) { - // Extend the header array - if (res->headers.len == res->headers.cap) { - http_response_header *new_arr = realloc( - res->headers.arr, 2 * res->headers.cap * sizeof(http_response_header)); - - if (new_arr == NULL) { - // TODO error handling - return; - } - - res->headers.arr = new_arr; - res->headers.cap *= 2; - } - - res->headers.arr[res->headers.len].type = type; - res->headers.arr[res->headers.len].value = value; - res->headers.arr[res->headers.len].owned = owned; - - res->headers.len++; -} - -void http_res_set_mime_type(http_response *res, http_mime_type mime_type) { - http_res_add_header(res, http_header_content_type, - http_mime_type_names[mime_type][1], false); -} diff --git a/src/http/types.c b/src/http/types.c deleted file mode 100644 index d7f2647..0000000 --- a/src/http/types.c +++ /dev/null @@ -1,33 +0,0 @@ -#include - -#include "http/types.h" - -http_body *http_body_init() { return calloc(sizeof(http_body), 1); } - -void http_body_reset(http_body *body) { - if (body->type == http_body_file) { - fclose(body->file); - } - - if (body->buf_owned) { - free(body->buf); - } - - if (body->fname_owned) { - free(body->fname); - } - - body->type = 0; - body->buf = NULL; - body->buf_owned = false; - body->file = NULL; - body->fname = NULL; - body->fname_owned = false; - body->expected_len = 0; - body->len = 0; -} - -void http_body_free(http_body *body) { - http_body_reset(body); - free(body); -} diff --git a/src/http_loop/http_loop.c b/src/http_loop/http_loop.c deleted file mode 100644 index cb4289e..0000000 --- a/src/http_loop/http_loop.c +++ /dev/null @@ -1,101 +0,0 @@ -#include - -#include "http/types.h" -#include "http_loop.h" -#include "log.h" - -const http_step http_default_res_steps[HTTP_LOOP_MAX_STEPS] = { - http_loop_step_write_header, http_loop_step_write_body, NULL}; - -bool http_loop_handle_request(event_loop_conn *conn) { - // Prevents the request handler function from looping indefinitely without - // ever consuming new data - if (conn->rbuf_size - conn->rbuf_read == 0) { - return false; - } - - http_loop_ctx *ctx = conn->ctx; - - // If route is defined, we're currently processing a request - if (ctx->route == NULL) { - http_parse_error res = http_loop_parse_request(conn); - - if (res == http_parse_error_invalid || - (res == http_parse_error_incomplete && - conn->rbuf_size == EVENT_LOOP_BUFFER_SIZE)) { - conn->state = event_loop_conn_state_end; - - return false; - } - - conn->rbuf_read += ctx->req.len; - - // It's fun to respond with extremely specific error messages - // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/501 - if (res == http_parse_error_unknown_method) { - ctx->res.status = http_method_not_implemented; - conn->state = event_loop_conn_state_res; - } else { - http_loop_route_request(conn); - } - } - - if (conn->state == event_loop_conn_state_req) { - http_loop_process_request(conn); - } - - // TODO in highly concurrent situations, it might actually be better to always - // return false here, as this allows cycling better through all connections - return conn->state == event_loop_conn_state_req; -} - -event_loop *http_loop_init(http_route *routes, size_t route_count, - void *custom_gctx, void *(*custom_ctx_init)(), - void(custom_ctx_reset)(), void(custom_ctx_free)()) { - event_loop *el = event_loop_init(); - - el->ctx_init = (void *(*)(void *))http_loop_ctx_init; - el->ctx_free = (void (*)(void *))http_loop_ctx_free; - el->handle_data = http_loop_handle_request; - el->write_data = http_loop_handle_response; - - http_loop_gctx *gctx = http_loop_gctx_init(); - gctx->c = custom_gctx; - gctx->routes = routes; - gctx->route_count = route_count; - gctx->custom_ctx_init = custom_ctx_init; - gctx->custom_ctx_reset = custom_ctx_reset; - gctx->custom_ctx_free = custom_ctx_free; - el->gctx = gctx; - - return el; -} - -void http_loop_set_api_key(http_loop *hl, const char *api_key) { - http_loop_gctx *gctx = hl->gctx; - gctx->api_key = api_key; -} - -void http_loop_run(event_loop *el, int port) { - debug("Compiling RegEx routes"); - - http_loop_gctx *gctx = el->gctx; - - for (size_t i = 0; i < gctx->route_count; i++) { - http_route *route = &gctx->routes[i]; - - if (route->type == http_route_regex) { - regex_t *r = calloc(sizeof(regex_t), 1); - - if (regcomp(r, route->path, REG_EXTENDED) != 0) { - critical(1, "RegEx expression '%s' failed to compile", route->path); - } - - route->regex = r; - } - } - - debug("RegEx routes compiled successfully"); - - event_loop_run(el, port); -} diff --git a/src/http_loop/http_loop_ctx.c b/src/http_loop/http_loop_ctx.c deleted file mode 100644 index 53a02bf..0000000 --- a/src/http_loop/http_loop_ctx.c +++ /dev/null @@ -1,62 +0,0 @@ -#include - -#include "http/types.h" -#include "http_loop.h" - -http_loop_gctx *http_loop_gctx_init() { - http_loop_gctx *gctx = calloc(sizeof(http_loop_gctx), 1); - - return gctx; -} - -http_loop_ctx *http_loop_ctx_init(http_loop_gctx *g) { - http_loop_ctx *ctx = calloc(sizeof(http_loop_ctx), 1); - ctx->g = g; - ctx->c = g->custom_ctx_init(); - - // TODO error checking - ctx->res.headers.arr = malloc(4 * sizeof(http_response_header)); - ctx->res.headers.cap = 4; - - return ctx; -} - -void http_loop_ctx_free(http_loop_ctx *ctx) { - http_loop_ctx_reset(ctx); - ctx->g->custom_ctx_free(ctx->c); - - if (ctx->res.headers.cap > 0) { - free(ctx->res.headers.arr); - } - - free(ctx); -} - -void http_loop_ctx_reset(http_loop_ctx *ctx) { - ctx->route = NULL; - ctx->current_step = 0; - - if (ctx->res.head != NULL) { - free((void *)ctx->res.head); - ctx->res.head = NULL; - } - - http_body_reset(&ctx->req.body); - http_body_reset(&ctx->res.body); - - for (size_t i = 0; i < ctx->res.headers.len; i++) { - if (ctx->res.headers.arr[i].owned) { - free((void *)ctx->res.headers.arr[i].value); - } - } - - // We don't set the cap as we can just keep the original array for the next - // requests - ctx->res.headers.len = 0; - - ctx->res.status = 0; - ctx->res.head_len = 0; - ctx->res.head_written = 0; - - ctx->g->custom_ctx_reset(ctx->c); -} diff --git a/src/http_loop/http_loop_req.c b/src/http_loop/http_loop_req.c deleted file mode 100644 index a8cd841..0000000 --- a/src/http_loop/http_loop_req.c +++ /dev/null @@ -1,149 +0,0 @@ -#include - -#include "http_loop.h" -#include "log.h" - -http_parse_error http_loop_parse_request(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; - - // First we try to parse the incoming HTTP request - size_t num_headers = HTTP_MAX_ALLOWED_HEADERS; - http_request *req = &ctx->req; - - const char *method; - size_t method_len; - char *path; - size_t path_len; - - int res = - phr_parse_request((const char *)&conn->rbuf[conn->rbuf_read], - conn->rbuf_size - conn->rbuf_read, &method, &method_len, - (const char **)&path, &path_len, &req->minor_version, - req->headers, &num_headers, 0); - - if (res == -1) { - return http_parse_error_invalid; - } else if (res == -2) { - return http_parse_error_incomplete; - } - - req->num_headers = num_headers; - req->len = res; - - // Try to parse the method type - bool match = false; - size_t i = 0; - - for (i = 0; i < http_method_names_len; i++) { - if (strncmp(method, http_method_names[i], method_len) == 0) { - req->method = i; - match = true; - } - } - - if (!match) { - return http_parse_error_unknown_method; - } - - // Split path into path & query - i = 0; - bool no_query = true; - - while (no_query && i < path_len) { - if (path[i] == '?') { - // Ensure we don't store an invalid pointer if the request simply ends - // with '?' - if (i + 1 < req->path_len) { - req->query = &path[i + 1]; - req->query_len = path_len - (i + 1); - } - - path_len = i; - - no_query = false; - } - - i++; - } - - // The path needs to be NULL-terminated in order for regex routes to be - // matched properly. We know we can overwrite this char because it's either - // '?' if there's a query, or '\n' because we know the buf contains a valid - // HTTP rqeuest - path[path_len] = '\0'; - - req->path = path; - req->path_len = path_len; - - // Ensure we clear the old request's query - if (no_query) { - req->query = NULL; - req->query_len = 0; - } - - return http_parse_error_ok; -} - -void http_loop_route_request(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; - http_loop_gctx *gctx = ctx->g; - - info("%s %.*s", http_method_names[ctx->req.method], ctx->req.path_len, - ctx->req.path); - - http_route *route; - bool path_matched = false; - - for (size_t i = 0; i < gctx->route_count; i++) { - route = &gctx->routes[i]; - - switch (route->type) { - case http_route_literal: - if (strncmp(route->path, ctx->req.path, ctx->req.path_len) == 0) { - path_matched = true; - - if (ctx->req.method == route->method) { - ctx->route = route; - return; - } - } - break; - case http_route_regex: - if (regexec(route->regex, ctx->req.path, HTTP_MAX_REGEX_GROUPS, - ctx->req.regex_groups, 0) == 0) { - path_matched = true; - - if (ctx->req.method == route->method) { - ctx->route = route; - return; - } - } - break; - } - } - - // Fallthrough case - ctx->res.status = path_matched ? http_method_not_allowed : http_not_found; - conn->state = event_loop_conn_state_res; -} - -void http_loop_process_request(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; - - // We keep processing step functions as long as they don't need to wait for - // I/O - while ((conn->state == event_loop_conn_state_req) && - (ctx->route->steps[ctx->current_step] != NULL) && - ctx->route->steps[ctx->current_step](conn)) { - ctx->current_step++; - } - - // Request processing can stop early by switching the connection state - // Either way, we reset the step counter as it will be used by the response - // steps - if ((conn->state != event_loop_conn_state_req) || - (ctx->route->steps[ctx->current_step] == NULL)) { - ctx->current_step = 0; - conn->state = event_loop_conn_state_res; - } -} diff --git a/src/http_loop/http_loop_res.c b/src/http_loop/http_loop_res.c deleted file mode 100644 index 8e96b56..0000000 --- a/src/http_loop/http_loop_res.c +++ /dev/null @@ -1,143 +0,0 @@ -#include "http_loop.h" -#include "log.h" - -static const char *server = "lander/" LANDER_VERSION; - -static int num_digits(size_t n) { - int digits = 1; - - while (n > 9) { - digits++; - - n /= 10; - } - - return digits; -} - -/* - * This function precalculates the size of the total buffer required using - * snprintf. When this function is called with a buf size of 0, it never tries - * to write any data, but it does return the amount of bytes that would be - * written. - */ -void http_loop_init_header(http_response *res) { - if (res->status == 0) { - res->status = http_ok; - } - - http_res_add_header(res, http_header_server, server, false); - - char *content_length_header = malloc(num_digits(res->body.expected_len) + 1); - sprintf(content_length_header, "%zu", res->body.expected_len); - - http_res_add_header(res, http_header_content_length, content_length_header, - true); - - const char *response_type_name = - http_status_names[res->status / 100 - 1][res->status % 100]; - - // First we calculate the size of the start of the header - int buf_size = - snprintf(NULL, 0, "HTTP/1.1 %i %s\n", res->status, response_type_name); - - // We add each header's required size - for (size_t i = 0; i < res->headers.len; i++) { - buf_size += snprintf(NULL, 0, "%s: %s\n", - http_header_names[res->headers.arr[i].type], - res->headers.arr[i].value); - } - - // The + 1 is required to store the final null byte, but we will replace it - // with the required final newline - char *buf = malloc(buf_size + 1); - buf_size = sprintf(buf, "HTTP/1.1 %i %s\n", res->status, response_type_name); - - for (size_t i = 0; i < res->headers.len; i++) { - buf_size += sprintf(&buf[buf_size], "%s: %s\n", - http_header_names[res->headers.arr[i].type], - res->headers.arr[i].value); - } - - buf[buf_size] = '\n'; - - res->head = buf; - res->head_len = buf_size + 1; -} - -bool http_loop_step_write_header(event_loop_conn *conn) { - http_response *res = &((http_loop_ctx *)conn->ctx)->res; - - // Create head response - if (res->head == NULL) { - http_loop_init_header(res); - } - - // Step has finished its work - if (res->head_written == res->head_len) { - return true; - } - - size_t bytes_to_write = MIN(res->head_len - res->head_written, - EVENT_LOOP_BUFFER_SIZE - conn->wbuf_size); - memcpy(&conn->wbuf[conn->wbuf_size], &res->head[res->head_written], - bytes_to_write); - - conn->wbuf_size += bytes_to_write; - res->head_written += bytes_to_write; - - return false; -} - -bool http_loop_step_write_body(event_loop_conn *conn) { - http_response *res = &((http_loop_ctx *)conn->ctx)->res; - - if (res->body.expected_len == res->body.len) { - return true; - } - - size_t bytes_to_write = MIN(res->body.expected_len - res->body.len, - EVENT_LOOP_BUFFER_SIZE - conn->wbuf_size); - - size_t bytes_written; - - switch (res->body.type) { - case http_body_buf: - memcpy(&conn->wbuf[conn->wbuf_size], &(res->body.buf)[res->body.len], - bytes_to_write); - conn->wbuf_size += bytes_to_write; - res->body.len += bytes_to_write; - break; - case http_body_file: - bytes_written = fread(&conn->wbuf[conn->wbuf_size], sizeof(uint8_t), - bytes_to_write, res->body.file); - conn->wbuf_size += bytes_written; - res->body.len += bytes_written; - break; - } - - return false; -} - -void http_loop_handle_response(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; - - // Non-routed requests also need to be processed - const http_step *steps = - ctx->route != NULL ? ctx->route->steps_res : http_default_res_steps; - - while ((conn->state == event_loop_conn_state_res) && - (steps[ctx->current_step] != NULL) && steps[ctx->current_step](conn)) { - ctx->current_step++; - } - - // Response processing can stop early be switching the connection state - // After response processing has finished its work, we reset the context to - // prepare for a new request - if ((conn->state != event_loop_conn_state_res) || - (steps[ctx->current_step] == NULL)) { - http_loop_ctx_reset(ctx); - - conn->state = event_loop_conn_state_req; - } -} diff --git a/src/http_loop/http_loop_steps.c b/src/http_loop/http_loop_steps.c deleted file mode 100644 index 99c5cce..0000000 --- a/src/http_loop/http_loop_steps.c +++ /dev/null @@ -1,179 +0,0 @@ -#include -#include - -#include "http_loop.h" -#include "lander.h" - -// Just a naive pow implementation; might improve later -static uint64_t ipow(uint64_t base, uint64_t power) { - uint64_t res = 1; - - while (power > 0) { - res *= base; - power--; - } - - return res; -} - -/* - * Converts a string to a number, returning true if the string contained a valid - * positive number. - */ -static bool string_to_num(size_t *res, const char *s, size_t len) { - *res = 0; - - for (size_t i = 0; i < len; i++) { - int val = s[i] - '0'; - - if (val < 0 || val > 9) { - return false; - } - - *res += (uint64_t)val * ipow(10, (len - 1) - i); - } - - return true; -} - -bool http_loop_step_parse_content_length(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; - - for (size_t i = 0; i < ctx->req.num_headers; i++) { - const struct phr_header *header = &ctx->req.headers[i]; - - if (strncmp(header->name, "Content-Length", header->name_len) == 0) { - // If the content length header is present but contains an invalid - // number, we return a bad request error - if (!string_to_num(&ctx->req.body.expected_len, header->value, - header->value_len)) { - ctx->res.status = http_bad_request; - conn->state = event_loop_conn_state_res; - - return true; - } - // The content length was actually 0, so we can instantly return here - else if (ctx->req.body.expected_len == 0) { - return true; - } - } - } - - // A zero here means there's no content length header - if (ctx->req.body.expected_len == 0) { - ctx->res.status = http_length_required; - conn->state = event_loop_conn_state_res; - } - - return true; -} - -/* - * Try to find and parse the Content-Length header. This function returns true - * if it was successful. If false is returned, the underlying step should - * immediately exit. - */ -bool try_parse_content_length(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; - - for (size_t i = 0; i < ctx->req.num_headers; i++) { - const struct phr_header *header = &ctx->req.headers[i]; - - if (strncmp(header->name, "Content-Length", header->name_len) == 0) { - // If the content length header is present but contains an invalid - // number, we return a bad request error - if (!string_to_num(&ctx->req.body.expected_len, header->value, - header->value_len)) { - ctx->res.status = http_bad_request; - conn->state = event_loop_conn_state_res; - - return false; - } - // The content length was actually 0, so we can instantly return here - else if (ctx->req.body.expected_len == 0) { - return false; - } - } - } - - // A zero here means there's no content length header - if (ctx->req.body.expected_len == 0) { - ctx->res.status = http_length_required; - conn->state = event_loop_conn_state_res; - - return false; - } - - return true; -} - -bool http_loop_step_body_to_buf(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; - - if (ctx->req.body.expected_len == 0) { - if (!try_parse_content_length(conn)) { - return true; - } - - ctx->req.body.type = http_body_buf; - ctx->req.body.buf = malloc(ctx->req.body.expected_len * sizeof(char)); - ctx->req.body.len = 0; - } - - size_t bytes_to_copy = MIN(conn->rbuf_size - conn->rbuf_read, - ctx->req.body.expected_len - ctx->req.body.len); - memcpy(&ctx->req.body.buf[ctx->req.body.len], &conn->rbuf[conn->rbuf_read], - bytes_to_copy); - ctx->req.body.len += bytes_to_copy; - conn->rbuf_read += bytes_to_copy; - - return ctx->req.body.len == ctx->req.body.expected_len; -} - -bool http_loop_step_body_to_file(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; - - if (ctx->req.body.expected_len == 0) { - if (!try_parse_content_length(conn)) { - return true; - } - - ctx->req.body.type = http_body_file; - ctx->req.body.file = fopen(ctx->req.body.fname, "wb"); - ctx->req.body.len = 0; - } - - size_t bytes_to_write = MIN(conn->rbuf_size - conn->rbuf_read, - ctx->req.body.expected_len - ctx->req.body.len); - size_t bytes_written = fwrite(&conn->rbuf[conn->rbuf_read], sizeof(uint8_t), - bytes_to_write, ctx->req.body.file); - ctx->req.body.len += bytes_written; - conn->rbuf_read += bytes_written; - - return ctx->req.body.len == ctx->req.body.expected_len; -} - -bool http_loop_step_auth(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; - - for (size_t i = 0; i < ctx->req.num_headers; i++) { - const struct phr_header *header = &ctx->req.headers[i]; - - if ((strncmp("X-Api-Key", header->name, header->name_len) == 0) && - (strncmp(header->value, ctx->g->api_key, header->value_len) == 0) && - (strlen(ctx->g->api_key) == header->value_len)) { - return true; - } - } - - ctx->res.status = http_unauthorized; - conn->state = event_loop_conn_state_res; - - return true; -} - -bool http_loop_step_switch_res(event_loop_conn *conn) { - conn->state = event_loop_conn_state_res; - - return true; -} diff --git a/src/lander/lander.c b/src/lander/lander.c index 36fd6cd..d6bf3f5 100644 --- a/src/lander/lander.c +++ b/src/lander/lander.c @@ -11,55 +11,6 @@ const char lander_key_charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; -http_route lander_routes[] = { - {.type = http_route_literal, - .method = http_get, - .path = "/", - .steps = {lander_get_index, NULL}, - .steps_res = {http_loop_step_write_header, http_loop_step_write_body, - NULL}}, - { - .type = http_route_regex, - .method = http_get, - .path = "^/([^/]+)$", - .steps = {lander_get_entry, NULL}, - .steps_res = {http_loop_step_write_header, lander_stream_body_to_client, - NULL}, - }, - { - .type = http_route_regex, - .method = http_delete, - .path = "^/([^/]+)$", - .steps = {http_loop_step_auth, lander_remove_entry, NULL}, - .steps_res = {http_loop_step_write_header, http_loop_step_write_body, - NULL}, - }, - { - .type = http_route_regex, - .method = http_post, - .path = "^/s(l?)/([^/]*)$", - .steps = {http_loop_step_auth, lander_post_redirect, - http_loop_step_body_to_buf, lander_post_redirect_body_to_attr, - NULL}, - .steps_res = {http_loop_step_write_header, http_loop_step_write_body, - NULL}, - }, - {.type = http_route_regex, - .method = http_post, - .path = "^/p(l?)/([^/]*)$", - .steps = {http_loop_step_auth, http_loop_step_parse_content_length, - lander_post_paste, lander_stream_body_to_entry, NULL}, - .steps_res = {http_loop_step_write_header, http_loop_step_write_body, - NULL}}, - {.type = http_route_regex, - .method = http_post, - .path = "^/f(l?)/([^/]*)$", - .steps = {http_loop_step_auth, http_loop_step_parse_content_length, - lander_post_file, lander_stream_body_to_entry, NULL}, - .steps_res = {http_loop_step_write_header, http_loop_step_write_body, - NULL}}, -}; - void *lander_gctx_init() { return calloc(1, sizeof(lander_gctx)); } lnm_err lander_ctx_init(void **c_ctx, void *gctx) {