From 380605ea08e146b925e3524574edffbcb4ef3fe2 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Mon, 20 Nov 2023 22:25:01 +0100 Subject: [PATCH 01/45] feat(http): allow arbitrary number of response headers --- .woodpecker/build.yml | 3 +-- CHANGELOG.md | 5 +++++ include/http/res.h | 7 +++++-- src/http/res.c | 23 +++++++++++++++++++---- src/http_loop/http_loop_ctx.c | 18 ++++++++++++++---- src/http_loop/http_loop_res.c | 16 ++++++++-------- 6 files changed, 52 insertions(+), 20 deletions(-) diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml index b6063bb..eaaac5a 100644 --- a/.woodpecker/build.yml +++ b/.woodpecker/build.yml @@ -39,8 +39,7 @@ pipeline: - minio_access_key - minio_secret_key when: - branch: - exclude: [ release/* ] + branch: dev event: push publish-rel: diff --git a/CHANGELOG.md b/CHANGELOG.md index 82b5c0c..d358bd0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://git.rustybever.be/Chewing_Bever/lander/src/branch/dev) +### Changed + +* HTTP Loop + * Responses can now have an arbitrary number of headers + ## [0.2.0](https://git.rustybever.be/Chewing_Bever/lander/src/tag/0.2.0) ### Added diff --git a/include/http/res.h b/include/http/res.h index 6fda002..729958f 100644 --- a/include/http/res.h +++ b/include/http/res.h @@ -24,8 +24,11 @@ typedef struct http_response { size_t head_len; size_t head_written; http_body body; - http_response_header headers[4]; - size_t header_count; + struct { + http_response_header *arr; + size_t len; + size_t cap; + } headers; } http_response; /** diff --git a/src/http/res.c b/src/http/res.c index c3e8806..d965802 100644 --- a/src/http/res.c +++ b/src/http/res.c @@ -1,3 +1,4 @@ +#include #include #include "http/res.h" @@ -24,11 +25,25 @@ void http_res_set_body_file(http_response *res, const char *filename) { void http_res_add_header(http_response *res, http_header type, const char *value, bool owned) { - res->headers[res->header_count].type = type; - res->headers[res->header_count].value = value; - res->headers[res->header_count].owned = 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)); - res->header_count++; + 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) { diff --git a/src/http_loop/http_loop_ctx.c b/src/http_loop/http_loop_ctx.c index 8d0db1e..53a02bf 100644 --- a/src/http_loop/http_loop_ctx.c +++ b/src/http_loop/http_loop_ctx.c @@ -14,6 +14,10 @@ http_loop_ctx *http_loop_ctx_init(http_loop_gctx *g) { 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; } @@ -21,6 +25,10 @@ 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); } @@ -36,13 +44,15 @@ void http_loop_ctx_reset(http_loop_ctx *ctx) { http_body_reset(&ctx->req.body); http_body_reset(&ctx->res.body); - for (size_t i = 0; i < ctx->res.header_count; i++) { - if (ctx->res.headers[i].owned) { - free((void *)ctx->res.headers[i].value); + 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); } } - ctx->res.header_count = 0; + // 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; diff --git a/src/http_loop/http_loop_res.c b/src/http_loop/http_loop_res.c index b29550f..5516cb7 100644 --- a/src/http_loop/http_loop_res.c +++ b/src/http_loop/http_loop_res.c @@ -25,10 +25,10 @@ void http_loop_init_header(http_response *res) { response_type_name, res->body.expected_len); // We add each header's required size - for (size_t i = 0; i < res->header_count; i++) { - buf_size += - snprintf(NULL, 0, "%s: %s\n", http_header_names[res->headers[i].type], - res->headers[i].value); + 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 @@ -37,10 +37,10 @@ void http_loop_init_header(http_response *res) { buf_size = sprintf(buf, http_response_format, res->status, response_type_name, res->body.expected_len); - for (size_t i = 0; i < res->header_count; i++) { - buf_size += - sprintf(&buf[buf_size], "%s: %s\n", - http_header_names[res->headers[i].type], res->headers[i].value); + 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'; From 1587b923c1e321b8fbabd58c207fa490a24613bd Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Mon, 20 Nov 2023 22:41:49 +0100 Subject: [PATCH 02/45] refactor(http): pass Server & Content-Length as proper headers --- include/http/types.h | 4 +++- src/http/http_consts.c | 4 +++- src/http_loop/http_loop_res.c | 32 ++++++++++++++++++++++++-------- 3 files changed, 30 insertions(+), 10 deletions(-) diff --git a/include/http/types.h b/include/http/types.h index cccf0a0..fbbe160 100644 --- a/include/http/types.h +++ b/include/http/types.h @@ -125,7 +125,9 @@ typedef enum http_header { http_header_connection = 0, http_header_location, http_header_content_type, - http_header_content_disposition + http_header_content_disposition, + http_header_server, + http_header_content_length } http_header; typedef enum http_body_type { diff --git a/src/http/http_consts.c b/src/http/http_consts.c index 8aa6f4b..a072309 100644 --- a/src/http/http_consts.c +++ b/src/http/http_consts.c @@ -97,7 +97,9 @@ const char *http_header_names[] = { "Connection", "Location", "Content-Type", - "Content-Disposition" + "Content-Disposition", + "Server", + "Content-Length" }; const char *http_mime_type_names[][2] = { diff --git a/src/http_loop/http_loop_res.c b/src/http_loop/http_loop_res.c index 5516cb7..8e96b56 100644 --- a/src/http_loop/http_loop_res.c +++ b/src/http_loop/http_loop_res.c @@ -1,10 +1,19 @@ #include "http_loop.h" #include "log.h" -// cppcheck-suppress syntaxError -static const char *http_response_format = "HTTP/1.1 %i %s\n" - "Server: lander/" LANDER_VERSION "\n" - "Content-Length: %lu\n"; +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 @@ -17,12 +26,20 @@ void http_loop_init_header(http_response *res) { 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_response_format, res->status, - response_type_name, res->body.expected_len); + 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++) { @@ -34,8 +51,7 @@ void http_loop_init_header(http_response *res) { // 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_response_format, res->status, response_type_name, - res->body.expected_len); + 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", From 20b6b593eb0e736d70cca80231e2b806c6a6eaf3 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Tue, 21 Nov 2023 09:36:58 +0100 Subject: [PATCH 03/45] refactor: started lnm library --- lnm/Makefile | 127 +++++++++++++++++++++++++++ lnm/config.mk | 15 ++++ lnm/include/lnm/common.h | 25 ++++++ lnm/include/lnm/loop.h | 42 +++++++++ lnm/src/_include/lnm/loop_internal.h | 5 ++ lnm/src/loop/lnm_loop.c | 1 + lnm/src/loop/lnm_loop_conn.c | 22 +++++ 7 files changed, 237 insertions(+) create mode 100644 lnm/Makefile create mode 100644 lnm/config.mk create mode 100644 lnm/include/lnm/common.h create mode 100644 lnm/include/lnm/loop.h create mode 100644 lnm/src/_include/lnm/loop_internal.h create mode 100644 lnm/src/loop/lnm_loop.c create mode 100644 lnm/src/loop/lnm_loop_conn.c diff --git a/lnm/Makefile b/lnm/Makefile new file mode 100644 index 0000000..716d82d --- /dev/null +++ b/lnm/Makefile @@ -0,0 +1,127 @@ +# https://spin.atomicobject.com/2016/08/26/makefile-c-projects/ was a great +# base for this Makefile + +-include config.mk + +LIB := $(BUILD_DIR)/$(LIB_FILENAME) + +SRCS != find '$(SRC_DIR)' -iname '*.c' +SRCS_H != find $(INC_DIRS) -iname '*.h' +SRCS_H_INTERNAL != find $(SRC_DIR) -iname '*.h' +SRCS_TEST != find '$(TEST_DIR)' -iname '*.c' +SRCS_EXAMPLE != find '$(EXAMPLE_DIR)' -iname '*.c' + +OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) +OBJS_TEST := $(SRCS_TEST:%=$(BUILD_DIR)/%.o) +OBJS_EXAMPLE := $(SRCS_EXAMPLE:%=$(BUILD_DIR)/%.o) +DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) $(SRCS_TEST:%=$(BUILD_DIR)/%.d) $(SRCS_EXAMPLE:%=$(BUILD_DIR)/%.d) + +BINS_TEST := $(OBJS_TEST:%.c.o=%) +BINS_EXAMPLE := $(OBJS_EXAMPLE:%.c.o=%) +TARGETS_TEST := $(BINS_TEST:%=test-%) +TARGETS_EXAMPLE := $(BINS_EXAMPLE:%=test-%) +TARGETS_MEM_TEST := $(BINS_TEST:%=test-mem-%) + +_CFLAGS := $(addprefix -I,$(INC_DIRS)) $(CFLAGS) -Wall -Wextra + +.PHONY: all +all: lib + + +# =====COMPILATION===== +# Utility used by the CI to lint +.PHONY: objs +objs: $(OBJS) + +.PHONY: lib +lib: $(LIB) +$(LIB): $(OBJS) + ar -rcs $@ $(OBJS) + +$(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c + mkdir -p $(dir $@) + $(CC) -c $(_CFLAGS) $< -o $@ + + +# =====TESTING===== +.PHONY: test +test: $(TARGETS_TEST) + +.PHONY: test-mem +test-mem: $(TARGETS_MEM_TEST) + +.PHONY: $(TARGETS_TEST) +$(TARGETS_TEST): test-%: % + ./$^ + +.PHONY: $(TARGETS_MEM_TEST) +$(TARGETS_MEM_TEST): test-mem-%: % + valgrind --tool=memcheck --error-exitcode=1 --track-origins=yes --leak-check=full ./$^ + +.PHONY: build-test +build-test: $(BINS_TEST) + +$(BINS_TEST): %: %.c.o $(LIB) + $(CC) \ + $^ -o $@ + +# Along with the include directory, each test includes $(TEST_DIR) (which +# contains the acutest.h header file), and the src directory of the module it's +# testing. This allows tests to access internal methods, which aren't publicly +# exposed. +$(BUILD_DIR)/$(TEST_DIR)/%.c.o: $(TEST_DIR)/%.c + mkdir -p $(dir $@) + $(CC) $(_CFLAGS) -I$(TEST_DIR) \ + -I$(dir $(@:$(BUILD_DIR)/$(TEST_DIR)/%=$(SRC_DIR)/%)) \ + -c $< -o $@ + +# =====EXAMPLES===== +.PHONY: build-example +build-example: $(BINS_EXAMPLE) + +$(BINS_EXAMPLE): %: %.c.o $(LIB) + $(CC) \ + $^ -o $@ + +# Example binaries link the resulting library +$(BUILD_DIR)/$(EXAMPLE_DIR)/%.c.o: $(EXAMPLE_DIR)/%.c + mkdir -p $(dir $@) + $(CC) $(_CFLAGS) -I$(PUB_INC_DIR) -c $< -o $@ + +# =====MAINTENANCE===== +.PHONY: lint +lint: + clang-format -n --Werror $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) $(SRCS_EXAMPLE) + +.PHONY: fmt +fmt: + clang-format -i $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) $(SRCS_EXAMPLE) + +.PHONY: check +check: + mkdir -p $(BUILD_DIR)/cppcheck + cppcheck \ + $(addprefix -I,$(INC_DIRS)) \ + --cppcheck-build-dir=$(BUILD_DIR)/cppcheck \ + --error-exitcode=1 \ + --enable=warning,style \ + --inline-suppr \ + --check-level=exhaustive \ + --quiet \ + -j$(shell nproc) \ + $(SRCS) + +.PHONY: clean +clean: + rm -rf $(BUILD_DIR) + + +.PHONY: bear +bear: clean + bear -- make + bear --append -- make build-test + bear --append -- make build-example + + +# Make make aware of the .d files +-include $(DEPS) diff --git a/lnm/config.mk b/lnm/config.mk new file mode 100644 index 0000000..a946b42 --- /dev/null +++ b/lnm/config.mk @@ -0,0 +1,15 @@ +LIB_FILENAME = liblnm.a + +BUILD_DIR = build +SRC_DIR = src +TEST_DIR = test +EXAMPLE_DIR = example +PUB_INC_DIR = include +INC_DIRS = $(PUB_INC_DIR) src/_include + +# -MMD: generate a .d file for every source file. This file can be imported by +# make and makes make aware that a header file has been changed, ensuring an +# object file is also recompiled if only a header is changed. +# -MP: generate a dummy target for every header file (according to the docs it +# prevents some errors when removing header files) +CFLAGS ?= -MMD -MP -g diff --git a/lnm/include/lnm/common.h b/lnm/include/lnm/common.h new file mode 100644 index 0000000..658695a --- /dev/null +++ b/lnm/include/lnm/common.h @@ -0,0 +1,25 @@ +#ifndef LNM_COMMON +#define LNM_COMMON + +#define LNM_RES(x) \ + { \ + lnm_err res = x; \ + if (res != lnm_err_ok) \ + return res; \ + } + +#define LNM_RES2(x, e) \ + { \ + lnm_err res = x; \ + if (res != lnm_err_ok) { \ + e; \ + return res; \ + } \ + } + +typedef enum { + lnm_err_ok = 0, + lnm_err_failed_alloc, +} lnm_err; + +#endif diff --git a/lnm/include/lnm/loop.h b/lnm/include/lnm/loop.h new file mode 100644 index 0000000..f292ae8 --- /dev/null +++ b/lnm/include/lnm/loop.h @@ -0,0 +1,42 @@ +#ifndef LNM_LOOP +#define LNM_LOOP + +#include + +#include "lnm/common.h" + +#define LNM_LOOP_BUF_SIZE 4096 + +typedef enum { + lnm_loop_state_req = 0, + lnm_loop_state_res, + lnm_loop_state_end, +} lnm_loop_state; + +typedef struct { + int fd; + lnm_loop_state state; + void *ctx; + struct { + char buf[LNM_LOOP_BUF_SIZE]; + size_t size; + size_t read; + } r; + struct { + char buf[LNM_LOOP_BUF_SIZE]; + size_t size; + size_t written; + } w; +} lnm_loop_conn; + +typedef struct { + struct { + lnm_loop_conn **arr; + size_t len; + } conns; + void *gctx; + lnm_err (*ctx_init)(void **out, void *gctx); + void (*ctx_free)(void *ctx); +} lnm_loop; + +#endif diff --git a/lnm/src/_include/lnm/loop_internal.h b/lnm/src/_include/lnm/loop_internal.h new file mode 100644 index 0000000..afbb386 --- /dev/null +++ b/lnm/src/_include/lnm/loop_internal.h @@ -0,0 +1,5 @@ +#include "lnm/loop.h" + +lnm_err lnm_loop_conn_init(lnm_loop_conn **out, lnm_loop *l); + +void lnm_loop_conn_free(lnm_loop *l, lnm_loop_conn *conn); diff --git a/lnm/src/loop/lnm_loop.c b/lnm/src/loop/lnm_loop.c new file mode 100644 index 0000000..ef96614 --- /dev/null +++ b/lnm/src/loop/lnm_loop.c @@ -0,0 +1 @@ +#include "lnm/loop.h" diff --git a/lnm/src/loop/lnm_loop_conn.c b/lnm/src/loop/lnm_loop_conn.c new file mode 100644 index 0000000..49b86ad --- /dev/null +++ b/lnm/src/loop/lnm_loop_conn.c @@ -0,0 +1,22 @@ +#include "lnm/loop_internal.h" + +lnm_err lnm_loop_conn_init(lnm_loop_conn **out, lnm_loop *l) { + lnm_loop_conn *conn = calloc(1, sizeof(lnm_loop_conn)); + + if (conn == NULL) { + return lnm_err_failed_alloc; + } + + void *ctx; + LNM_RES2(l->ctx_init(&ctx, l->gctx), free(conn)); + + conn->ctx = ctx; + *out = conn; + + return lnm_err_ok; +} + +void lnm_loop_conn_free(lnm_loop *l, lnm_loop_conn *conn) { + l->ctx_free(conn->ctx); + free(conn); +} From 8a3be2b07c4242e3a21bfb98ad6a0bff216a5584 Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Tue, 21 Nov 2023 22:39:59 +0100 Subject: [PATCH 04/45] feat(lnm): start of event loop --- lnm/include/lnm/common.h | 2 + lnm/include/lnm/loop.h | 9 ++ lnm/src/_include/lnm/loop_internal.h | 2 + lnm/src/loop/lnm_loop.c | 135 ++++++++++++++++++++++++++- 4 files changed, 147 insertions(+), 1 deletion(-) diff --git a/lnm/include/lnm/common.h b/lnm/include/lnm/common.h index 658695a..26a3932 100644 --- a/lnm/include/lnm/common.h +++ b/lnm/include/lnm/common.h @@ -20,6 +20,8 @@ typedef enum { lnm_err_ok = 0, lnm_err_failed_alloc, + lnm_err_failed_network, + lnm_err_failed_poll, } lnm_err; #endif diff --git a/lnm/include/lnm/loop.h b/lnm/include/lnm/loop.h index f292ae8..c0bce0b 100644 --- a/lnm/include/lnm/loop.h +++ b/lnm/include/lnm/loop.h @@ -1,11 +1,13 @@ #ifndef LNM_LOOP #define LNM_LOOP +#include #include #include "lnm/common.h" #define LNM_LOOP_BUF_SIZE 4096 +#define LNM_LOOP_INITIAL_CONNS 16 typedef enum { lnm_loop_state_req = 0, @@ -33,10 +35,17 @@ typedef struct { struct { lnm_loop_conn **arr; size_t len; + size_t open; } conns; void *gctx; lnm_err (*ctx_init)(void **out, void *gctx); void (*ctx_free)(void *ctx); } lnm_loop; +lnm_err lnm_loop_init(lnm_loop **out, void *gctx, + lnm_err (*ctx_init)(void **out, void *gctx), + void (*ctx_free)(void *ctx)); + +lnm_err lnm_loop_run(lnm_loop *l, uint16_t port); + #endif diff --git a/lnm/src/_include/lnm/loop_internal.h b/lnm/src/_include/lnm/loop_internal.h index afbb386..0c91535 100644 --- a/lnm/src/_include/lnm/loop_internal.h +++ b/lnm/src/_include/lnm/loop_internal.h @@ -3,3 +3,5 @@ lnm_err lnm_loop_conn_init(lnm_loop_conn **out, lnm_loop *l); void lnm_loop_conn_free(lnm_loop *l, lnm_loop_conn *conn); + +lnm_err lnm_loop_accept(lnm_loop *l); diff --git a/lnm/src/loop/lnm_loop.c b/lnm/src/loop/lnm_loop.c index ef96614..d835824 100644 --- a/lnm/src/loop/lnm_loop.c +++ b/lnm/src/loop/lnm_loop.c @@ -1 +1,134 @@ -#include "lnm/loop.h" +#include +#include +#include + +#include "lnm/common.h" +#include "lnm/loop_internal.h" + +lnm_err lnm_loop_init(lnm_loop **out, void *gctx, + lnm_err (*ctx_init)(void **out, void *gctx), + void (*ctx_free)(void *ctx)) { + lnm_loop *l = calloc(1, sizeof(lnm_loop)); + + if (l == NULL) { + return lnm_err_failed_alloc; + } + + l->gctx = gctx; + l->ctx_init = ctx_init; + l->ctx_free = ctx_free; + + *out = l; + + return lnm_err_ok; +} + +lnm_err lnm_loop_run(lnm_loop *l, uint16_t port) { + int listen_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); + + if (listen_fd < 0) { + return lnm_err_failed_network; + } + + int val = 1; + int res = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(int)); + + if (res < 0) { + return lnm_err_failed_network; + } + + struct sockaddr_in addr = {.sin_family = AF_INET, + .sin_port = ntohs(port), + .sin_addr.s_addr = ntohl(0)}; + + res = bind(listen_fd, (const struct sockaddr *)&addr, sizeof(addr)); + + if (res < 0) { + return lnm_err_failed_network; + } + + res = listen(listen_fd, SOMAXCONN); + + if (res < 0) { + return lnm_err_failed_network; + } + + struct pollfd *poll_args = + malloc((LNM_LOOP_INITIAL_CONNS + 1) * sizeof(struct pollfd)); + size_t poll_args_cap = LNM_LOOP_INITIAL_CONNS + 1; + + if (poll_args == NULL) { + return lnm_err_failed_alloc; + } + + // First argument is listening socket + poll_args[0].fd = listen_fd; + poll_args[0].events = POLLIN; + + while (1) { + size_t poll_args_len = 1; + + // Add all open connections to the poll command + for (size_t i = 0; i < l->conns.len && poll_args_len <= l->conns.open; + i++) { + lnm_loop_conn *conn = l->conns.arr[i]; + + if (conn == NULL) { + continue; + } + + poll_args[poll_args_len].fd = conn->fd; + poll_args[poll_args_len].events = + ((conn->state == lnm_loop_state_req) ? POLLIN : POLLOUT) | POLLERR; + + poll_args_len++; + } + + int polled = poll(poll_args, poll_args_len, -1); + + if (polled < 0) { + return lnm_err_failed_poll; + } + + if (poll_args[0].revents) { + lnm_loop_accept(l); + polled--; + } + + for (size_t i = 1; i < poll_args_len && polled > 0; i++) { + if (poll_args[i].revents) { + lnm_loop_conn *conn = l->conns.arr[poll_args[i].fd]; + + // TODO actual IO + + if (conn->state == lnm_loop_state_end) { + l->conns.arr[conn->fd] = NULL; + close(conn->fd); + + l->conns.open--; + lnm_loop_conn_free(l, conn); + } + + polled--; + } + } + + if (poll_args_cap < l->conns.open + 1) { + struct pollfd *buf = malloc((l->conns.open + 1) * sizeof(struct pollfd)); + + if (buf == NULL) { + return lnm_err_failed_alloc; + } + + buf[0].fd = listen_fd; + buf[0].events = POLLIN; + + free(poll_args); + poll_args = buf; + + poll_args_cap = l->conns.open + 1; + } + } + + return lnm_err_ok; +} From f79ba2818c6f383142d5d78d4a382aa722d3155c Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 22 Nov 2023 12:59:28 +0100 Subject: [PATCH 05/45] feat(lnm): wrote rest of event loop --- lnm/include/lnm/common.h | 1 + lnm/include/lnm/loop.h | 8 ++- lnm/src/_include/lnm/loop_internal.h | 2 + lnm/src/loop/lnm_loop.c | 60 ++++++++++++++++++++-- lnm/src/loop/lnm_loop_io.c | 77 ++++++++++++++++++++++++++++ 5 files changed, 141 insertions(+), 7 deletions(-) create mode 100644 lnm/src/loop/lnm_loop_io.c diff --git a/lnm/include/lnm/common.h b/lnm/include/lnm/common.h index 26a3932..1500d73 100644 --- a/lnm/include/lnm/common.h +++ b/lnm/include/lnm/common.h @@ -22,6 +22,7 @@ typedef enum { lnm_err_failed_alloc, lnm_err_failed_network, lnm_err_failed_poll, + lnm_err_not_setup, } lnm_err; #endif diff --git a/lnm/include/lnm/loop.h b/lnm/include/lnm/loop.h index c0bce0b..d70057e 100644 --- a/lnm/include/lnm/loop.h +++ b/lnm/include/lnm/loop.h @@ -27,11 +27,11 @@ typedef struct { struct { char buf[LNM_LOOP_BUF_SIZE]; size_t size; - size_t written; } w; } lnm_loop_conn; typedef struct { + int listen_fd; struct { lnm_loop_conn **arr; size_t len; @@ -40,12 +40,16 @@ typedef struct { void *gctx; lnm_err (*ctx_init)(void **out, void *gctx); void (*ctx_free)(void *ctx); + void (*data_read)(lnm_loop_conn *conn); + void (*data_write)(lnm_loop_conn *conn); } lnm_loop; lnm_err lnm_loop_init(lnm_loop **out, void *gctx, lnm_err (*ctx_init)(void **out, void *gctx), void (*ctx_free)(void *ctx)); -lnm_err lnm_loop_run(lnm_loop *l, uint16_t port); +lnm_err lnm_loop_setup(lnm_loop *l, uint16_t port); + +lnm_err lnm_loop_run(lnm_loop *l); #endif diff --git a/lnm/src/_include/lnm/loop_internal.h b/lnm/src/_include/lnm/loop_internal.h index 0c91535..6b723fe 100644 --- a/lnm/src/_include/lnm/loop_internal.h +++ b/lnm/src/_include/lnm/loop_internal.h @@ -5,3 +5,5 @@ lnm_err lnm_loop_conn_init(lnm_loop_conn **out, lnm_loop *l); void lnm_loop_conn_free(lnm_loop *l, lnm_loop_conn *conn); lnm_err lnm_loop_accept(lnm_loop *l); + +void lnm_loop_conn_io(lnm_loop *l, lnm_loop_conn *conn); diff --git a/lnm/src/loop/lnm_loop.c b/lnm/src/loop/lnm_loop.c index d835824..63e3b34 100644 --- a/lnm/src/loop/lnm_loop.c +++ b/lnm/src/loop/lnm_loop.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -23,7 +24,46 @@ lnm_err lnm_loop_init(lnm_loop **out, void *gctx, return lnm_err_ok; } -lnm_err lnm_loop_run(lnm_loop *l, uint16_t port) { +lnm_err lnm_loop_accept(lnm_loop *l) { + int conn_fd = accept(l->listen_fd, NULL, NULL); + + if (conn_fd < 0) { + return lnm_err_failed_network; + } + + // Set socket to non-blocking + int flags = fcntl(conn_fd, F_GETFL); + flags |= O_NONBLOCK; + fcntl(conn_fd, F_SETFL, flags); + + // Append connection to list of connections + if ((size_t)conn_fd >= l->conns.len) { + lnm_loop_conn **new = + realloc(l->conns.arr, sizeof(lnm_loop_conn *) * (conn_fd + 1)); + + if (new == NULL) { + close(conn_fd); + + return lnm_err_failed_alloc; + } + + l->conns.arr = new; + l->conns.len = conn_fd + 1; + } + + lnm_loop_conn *conn; + LNM_RES2(lnm_loop_conn_init(&conn, l), close(conn_fd)); + + l->conns.arr[conn_fd] = conn; + conn->fd = conn_fd; + conn->state = lnm_loop_state_req; + + l->conns.open++; + + return lnm_err_ok; +} + +lnm_err lnm_loop_setup(lnm_loop *l, uint16_t port) { int listen_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); if (listen_fd < 0) { @@ -53,6 +93,16 @@ lnm_err lnm_loop_run(lnm_loop *l, uint16_t port) { return lnm_err_failed_network; } + l->listen_fd = listen_fd; + + return lnm_err_ok; +} + +lnm_err lnm_loop_run(lnm_loop *l) { + if (l->listen_fd == 0) { + return lnm_err_not_setup; + } + struct pollfd *poll_args = malloc((LNM_LOOP_INITIAL_CONNS + 1) * sizeof(struct pollfd)); size_t poll_args_cap = LNM_LOOP_INITIAL_CONNS + 1; @@ -62,14 +112,14 @@ lnm_err lnm_loop_run(lnm_loop *l, uint16_t port) { } // First argument is listening socket - poll_args[0].fd = listen_fd; + poll_args[0].fd = l->listen_fd; poll_args[0].events = POLLIN; while (1) { size_t poll_args_len = 1; // Add all open connections to the poll command - for (size_t i = 0; i < l->conns.len && poll_args_len <= l->conns.open; + for (size_t i = 0; i < l->conns.len && poll_args_len < l->conns.open + 1; i++) { lnm_loop_conn *conn = l->conns.arr[i]; @@ -99,7 +149,7 @@ lnm_err lnm_loop_run(lnm_loop *l, uint16_t port) { if (poll_args[i].revents) { lnm_loop_conn *conn = l->conns.arr[poll_args[i].fd]; - // TODO actual IO + lnm_loop_conn_io(l, conn); if (conn->state == lnm_loop_state_end) { l->conns.arr[conn->fd] = NULL; @@ -120,7 +170,7 @@ lnm_err lnm_loop_run(lnm_loop *l, uint16_t port) { return lnm_err_failed_alloc; } - buf[0].fd = listen_fd; + buf[0].fd = l->listen_fd; buf[0].events = POLLIN; free(poll_args); diff --git a/lnm/src/loop/lnm_loop_io.c b/lnm/src/loop/lnm_loop_io.c new file mode 100644 index 0000000..5d89191 --- /dev/null +++ b/lnm/src/loop/lnm_loop_io.c @@ -0,0 +1,77 @@ +#include +#include +#include + +#include "lnm/loop.h" +#include "lnm/loop_internal.h" + +void lnm_loop_conn_io_req(lnm_loop *l, lnm_loop_conn *conn) { + do { + // Move remaining data to front of buffer + memmove(conn->r.buf, &conn->r.buf[conn->r.read], + conn->r.size - conn->r.read); + conn->r.size -= conn->r.read; + conn->r.read = 0; + + ssize_t res; + size_t cap = LNM_LOOP_BUF_SIZE - conn->r.size; + + do { + res = read(conn->fd, &conn->r.buf[conn->r.size], cap); + } while (res < 0 && errno == EINTR); + + // Read can't be performed without blocking; we come back later + if (res < 0 && errno == EAGAIN) { + return; + } + + if (res <= 0) { + conn->state = lnm_loop_state_end; + + return; + } + + conn->r.size += res; + l->data_read(conn); + } while (conn->state == lnm_loop_state_req); +} + +void lnm_loop_conn_io_res(lnm_loop *l, lnm_loop_conn *conn) { + do { + ssize_t res; + + do { + res = write(conn->fd, conn->w.buf, conn->w.size); + } while (res < 0 && errno == EINTR); + + // Write can't be performed without blocking; we come back later + if (res < 0 && errno == EAGAIN) { + return; + } + + if (res <= 0) { + conn->state = lnm_loop_state_end; + + return; + } + + // Move remaining data to front of buffer. Doing this here gives the data + // writer function more space to work with + memmove(conn->w.buf, &conn->w.buf[res], conn->w.size - res); + conn->w.size -= res; + + l->data_write(conn); + } while (conn->state == lnm_loop_state_res); +} + +void lnm_loop_conn_io(lnm_loop *l, lnm_loop_conn *conn) { + switch (conn->state) { + case lnm_loop_state_req: + lnm_loop_conn_io_req(l, conn); + break; + case lnm_loop_state_res: + lnm_loop_conn_io_res(l, conn); + break; + default:; + } +} From c7b3f68c2ea6f2d5a2842d6df7b87c6c54002abf Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 22 Nov 2023 13:17:46 +0100 Subject: [PATCH 06/45] chore(lnm): copied over http constants --- lnm/include/lnm/http/consts.h | 97 ++++++++++++++++++++++++++ lnm/include/lnm/loop.h | 4 +- lnm/src/_include/lnm/loop_internal.h | 5 ++ lnm/src/http/lnm_http_consts.c | 100 +++++++++++++++++++++++++++ lnm/src/loop/lnm_loop.c | 8 ++- 5 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 lnm/include/lnm/http/consts.h create mode 100644 lnm/src/http/lnm_http_consts.c diff --git a/lnm/include/lnm/http/consts.h b/lnm/include/lnm/http/consts.h new file mode 100644 index 0000000..4b6bc92 --- /dev/null +++ b/lnm/include/lnm/http/consts.h @@ -0,0 +1,97 @@ +#ifndef LNM_HTTP_CONSTS +#define LNM_HTTP_CONSTS + +#include + +extern const char *lnm_http_method_names[]; +extern const size_t lnm_http_method_names_len; + +typedef enum lnm_http_method { + http_method_get = 0, + http_method_post, + http_method_put, + http_method_patch, + http_method_delete +} http_method; + +extern const char *lnm_http_status_names[][32]; + +typedef enum lnm_http_status { + // 1xx + lnm_http_status_continue = 100, + lnm_http_status_switching_protocols = 101, + lnm_http_status_processing = 102, + lnm_http_status_early_hints = 103, + // 2xx + lnm_http_status_ok = 200, + lnm_http_status_created = 201, + lnm_http_status_accepted = 202, + lnm_http_status_non_authoritative_information = 203, + lnm_http_status_no_content = 204, + lnm_http_status_reset_content = 205, + lnm_http_status_partial_content = 206, + lnm_http_status_multi_status = 207, + lnm_http_status_already_reported = 208, + // 3xx + lnm_http_status_multiple_choices = 300, + lnm_http_status_moved_permanently = 301, + lnm_http_status_found = 302, + lnm_http_status_see_other = 303, + lnm_http_status_not_modified = 304, + lnm_http_status_temporary_redirect = 307, + lnm_http_status_permanent_redirect = 308, + // 4xx + lnm_http_status_bad_request = 400, + lnm_http_status_unauthorized = 401, + lnm_http_status_payment_required = 402, + lnm_http_status_forbidden = 403, + lnm_http_status_not_found = 404, + lnm_http_status_method_not_allowed = 405, + lnm_http_status_not_acceptable = 406, + lnm_http_status_proxy_authentication_required = 407, + lnm_http_status_request_timeout = 408, + lnm_http_status_conflict = 409, + lnm_http_status_gone = 410, + lnm_http_status_length_required = 411, + lnm_http_status_precondition_failed = 412, + lnm_http_status_content_too_large = 413, + lnm_http_status_uri_too_long = 414, + lnm_http_status_unsupported_media_type = 415, + lnm_http_status_range_not_satisfiable = 416, + lnm_http_status_expection_failed = 417, + lnm_http_status_im_a_teapot = 418, + lnm_http_status_misdirected_request = 421, + lnm_http_status_unprocessable_content = 422, + lnm_http_status_locked = 423, + lnm_http_status_failed_dependency = 424, + lnm_http_status_too_early = 425, + lnm_http_status_upgrade_required = 426, + lnm_http_status_precondition_required = 428, + lnm_http_status_too_many_requests = 429, + lnm_http_status_request_header_fields_too_large = 431, + // 5xx + lnm_http_status_internal_server_error = 500, + lnm_http_status_method_not_implemented = 501, + lnm_http_status_bad_gateway = 502, + lnm_http_status_service_unavailable = 503, + lnm_http_status_gateway_timeout = 504, + lnm_http_status_http_status_version_not_supported = 505, + lnm_http_status_variant_also_negotiates = 506, + lnm_http_status_insufficient_storage = 507, + lnm_http_status_loop_detected = 508, + lnm_http_status_not_extended = 510, + lnm_http_status_network_authentication_required = 511 +} lnm_http_status; + +extern const char *lnm_http_header_names[]; + +typedef enum lnm_http_header { + lnm_http_header_connection = 0, + lnm_http_header_location, + lnm_http_header_content_type, + lnm_http_header_content_disposition, + lnm_http_header_server, + lnm_http_header_content_length +} lnm_http_header; + +#endif diff --git a/lnm/include/lnm/loop.h b/lnm/include/lnm/loop.h index d70057e..a3625c6 100644 --- a/lnm/include/lnm/loop.h +++ b/lnm/include/lnm/loop.h @@ -46,7 +46,9 @@ typedef struct { lnm_err lnm_loop_init(lnm_loop **out, void *gctx, lnm_err (*ctx_init)(void **out, void *gctx), - void (*ctx_free)(void *ctx)); + void (*ctx_free)(void *ctx), + void (*data_read)(lnm_loop_conn *conn), + void (*data_write)(lnm_loop_conn *conn)); lnm_err lnm_loop_setup(lnm_loop *l, uint16_t port); diff --git a/lnm/src/_include/lnm/loop_internal.h b/lnm/src/_include/lnm/loop_internal.h index 6b723fe..a5e70a8 100644 --- a/lnm/src/_include/lnm/loop_internal.h +++ b/lnm/src/_include/lnm/loop_internal.h @@ -1,3 +1,6 @@ +#ifndef LNM_LOOP_INTERNAL +#define LNM_LOOP_INTERNAL + #include "lnm/loop.h" lnm_err lnm_loop_conn_init(lnm_loop_conn **out, lnm_loop *l); @@ -7,3 +10,5 @@ void lnm_loop_conn_free(lnm_loop *l, lnm_loop_conn *conn); lnm_err lnm_loop_accept(lnm_loop *l); void lnm_loop_conn_io(lnm_loop *l, lnm_loop_conn *conn); + +#endif diff --git a/lnm/src/http/lnm_http_consts.c b/lnm/src/http/lnm_http_consts.c new file mode 100644 index 0000000..834816a --- /dev/null +++ b/lnm/src/http/lnm_http_consts.c @@ -0,0 +1,100 @@ +#include "lnm/http/consts.h" + +const char *lnm_http_method_names[] = {"GET", "POST", "PUT", "PATCH", "DELETE"}; +const size_t lnm_http_method_names_len = + sizeof(lnm_http_method_names) / sizeof(lnm_http_method_names[0]); + +// clang-format off + +const char *lnm_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 *lnm_http_header_names[] = { + "Connection", + "Location", + "Content-Type", + "Content-Disposition", + "Server", + "Content-Length" +}; diff --git a/lnm/src/loop/lnm_loop.c b/lnm/src/loop/lnm_loop.c index 63e3b34..c550009 100644 --- a/lnm/src/loop/lnm_loop.c +++ b/lnm/src/loop/lnm_loop.c @@ -8,7 +8,9 @@ lnm_err lnm_loop_init(lnm_loop **out, void *gctx, lnm_err (*ctx_init)(void **out, void *gctx), - void (*ctx_free)(void *ctx)) { + void (*ctx_free)(void *ctx), + void (*data_read)(lnm_loop_conn *conn), + void (*data_write)(lnm_loop_conn *conn)) { lnm_loop *l = calloc(1, sizeof(lnm_loop)); if (l == NULL) { @@ -18,6 +20,8 @@ lnm_err lnm_loop_init(lnm_loop **out, void *gctx, l->gctx = gctx; l->ctx_init = ctx_init; l->ctx_free = ctx_free; + l->data_read = data_read; + l->data_write = data_write; *out = l; @@ -121,7 +125,7 @@ lnm_err lnm_loop_run(lnm_loop *l) { // Add all open connections to the poll command for (size_t i = 0; i < l->conns.len && poll_args_len < l->conns.open + 1; i++) { - lnm_loop_conn *conn = l->conns.arr[i]; + const lnm_loop_conn *conn = l->conns.arr[i]; if (conn == NULL) { continue; From 3c74200bbdb4bf9afaadd8847df7cb2e456539c2 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 22 Nov 2023 15:19:21 +0100 Subject: [PATCH 07/45] chore(lnm): add picohttpparser --- lnm/Makefile | 12 +- lnm/config.mk | 5 +- lnm/thirdparty/include/picohttpparser.h | 87 ++++ lnm/thirdparty/src/picohttpparser.c | 665 ++++++++++++++++++++++++ 4 files changed, 761 insertions(+), 8 deletions(-) create mode 100644 lnm/thirdparty/include/picohttpparser.h create mode 100644 lnm/thirdparty/src/picohttpparser.c diff --git a/lnm/Makefile b/lnm/Makefile index 716d82d..78e1e0e 100644 --- a/lnm/Makefile +++ b/lnm/Makefile @@ -9,17 +9,14 @@ SRCS != find '$(SRC_DIR)' -iname '*.c' SRCS_H != find $(INC_DIRS) -iname '*.h' SRCS_H_INTERNAL != find $(SRC_DIR) -iname '*.h' SRCS_TEST != find '$(TEST_DIR)' -iname '*.c' -SRCS_EXAMPLE != find '$(EXAMPLE_DIR)' -iname '*.c' +SRCS_THIRDPARTY != find '$(THIRDPARTY_DIR)/src' -iname '*.c' -OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) +OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) $(SRCS_THIRDPARTY:%=$(BUILD_DIR)/%.o) OBJS_TEST := $(SRCS_TEST:%=$(BUILD_DIR)/%.o) -OBJS_EXAMPLE := $(SRCS_EXAMPLE:%=$(BUILD_DIR)/%.o) -DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) $(SRCS_TEST:%=$(BUILD_DIR)/%.d) $(SRCS_EXAMPLE:%=$(BUILD_DIR)/%.d) +DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) $(SRCS_TEST:%=$(BUILD_DIR)/%.d) $(SRCS_THIRDPARTY:%=$(BUILD_DIR)/%.d) BINS_TEST := $(OBJS_TEST:%.c.o=%) -BINS_EXAMPLE := $(OBJS_EXAMPLE:%.c.o=%) TARGETS_TEST := $(BINS_TEST:%=test-%) -TARGETS_EXAMPLE := $(BINS_EXAMPLE:%=test-%) TARGETS_MEM_TEST := $(BINS_TEST:%=test-mem-%) _CFLAGS := $(addprefix -I,$(INC_DIRS)) $(CFLAGS) -Wall -Wextra @@ -42,6 +39,9 @@ $(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c mkdir -p $(dir $@) $(CC) -c $(_CFLAGS) $< -o $@ +$(BUILD_DIR)/$(THIRDPARTY_DIR)/%.c.o: $(THIRDPARTY_DIR)/%.c + mkdir -p $(dir $@) + $(CC) $(_CFLAGS) -c $< -o $@ # =====TESTING===== .PHONY: test diff --git a/lnm/config.mk b/lnm/config.mk index a946b42..fde7e0e 100644 --- a/lnm/config.mk +++ b/lnm/config.mk @@ -3,9 +3,10 @@ LIB_FILENAME = liblnm.a BUILD_DIR = build SRC_DIR = src TEST_DIR = test -EXAMPLE_DIR = example +THIRDPARTY_DIR = thirdparty + PUB_INC_DIR = include -INC_DIRS = $(PUB_INC_DIR) src/_include +INC_DIRS = $(PUB_INC_DIR) src/_include $(THIRDPARTY_DIR)/include # -MMD: generate a .d file for every source file. This file can be imported by # make and makes make aware that a header file has been changed, ensuring an diff --git a/lnm/thirdparty/include/picohttpparser.h b/lnm/thirdparty/include/picohttpparser.h new file mode 100644 index 0000000..07537cf --- /dev/null +++ b/lnm/thirdparty/include/picohttpparser.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, + * Shigeo Mitsunari + * + * The software is licensed under either the MIT License (below) or the Perl + * license. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#ifndef picohttpparser_h +#define picohttpparser_h + +#include + +#ifdef _MSC_VER +#define ssize_t intptr_t +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/* contains name and value of a header (name == NULL if is a continuing line + * of a multiline header */ +struct phr_header { + const char *name; + size_t name_len; + const char *value; + size_t value_len; +}; + +/* returns number of bytes consumed if successful, -2 if request is partial, + * -1 if failed */ +int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len, + int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len); + +/* ditto */ +int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len, + struct phr_header *headers, size_t *num_headers, size_t last_len); + +/* ditto */ +int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len); + +/* should be zero-filled before start */ +struct phr_chunked_decoder { + size_t bytes_left_in_chunk; /* number of bytes left in current chunk */ + char consume_trailer; /* if trailing headers should be consumed */ + char _hex_count; + char _state; +}; + +/* the function rewrites the buffer given as (buf, bufsz) removing the chunked- + * encoding headers. When the function returns without an error, bufsz is + * updated to the length of the decoded data available. Applications should + * repeatedly call the function while it returns -2 (incomplete) every time + * supplying newly arrived data. If the end of the chunked-encoded data is + * found, the function returns a non-negative number indicating the number of + * octets left undecoded, that starts from the offset returned by `*bufsz`. + * Returns -1 on error. + */ +ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz); + +/* returns if the chunked decoder is in middle of chunked data */ +int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/lnm/thirdparty/src/picohttpparser.c b/lnm/thirdparty/src/picohttpparser.c new file mode 100644 index 0000000..5e5783a --- /dev/null +++ b/lnm/thirdparty/src/picohttpparser.c @@ -0,0 +1,665 @@ +/* + * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, + * Shigeo Mitsunari + * + * The software is licensed under either the MIT License (below) or the Perl + * license. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include +#include +#include +#ifdef __SSE4_2__ +#ifdef _MSC_VER +#include +#else +#include +#endif +#endif +#include "picohttpparser.h" + +#if __GNUC__ >= 3 +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) +#else +#define likely(x) (x) +#define unlikely(x) (x) +#endif + +#ifdef _MSC_VER +#define ALIGNED(n) _declspec(align(n)) +#else +#define ALIGNED(n) __attribute__((aligned(n))) +#endif + +#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u) + +#define CHECK_EOF() \ + if (buf == buf_end) { \ + *ret = -2; \ + return NULL; \ + } + +#define EXPECT_CHAR_NO_CHECK(ch) \ + if (*buf++ != ch) { \ + *ret = -1; \ + return NULL; \ + } + +#define EXPECT_CHAR(ch) \ + CHECK_EOF(); \ + EXPECT_CHAR_NO_CHECK(ch); + +#define ADVANCE_TOKEN(tok, toklen) \ + do { \ + const char *tok_start = buf; \ + static const char ALIGNED(16) ranges2[16] = "\000\040\177\177"; \ + int found2; \ + buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \ + if (!found2) { \ + CHECK_EOF(); \ + } \ + while (1) { \ + if (*buf == ' ') { \ + break; \ + } else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \ + if ((unsigned char)*buf < '\040' || *buf == '\177') { \ + *ret = -1; \ + return NULL; \ + } \ + } \ + ++buf; \ + CHECK_EOF(); \ + } \ + tok = tok_start; \ + toklen = buf - tok_start; \ + } while (0) + +static const char *token_char_map = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0" + "\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1" + "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" + "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; + +static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, size_t ranges_size, int *found) +{ + *found = 0; +#if __SSE4_2__ + if (likely(buf_end - buf >= 16)) { + __m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges); + + size_t left = (buf_end - buf) & ~15; + do { + __m128i b16 = _mm_loadu_si128((const __m128i *)buf); + int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS); + if (unlikely(r != 16)) { + buf += r; + *found = 1; + break; + } + buf += 16; + left -= 16; + } while (likely(left != 0)); + } +#else + /* suppress unused parameter warning */ + (void)buf_end; + (void)ranges; + (void)ranges_size; +#endif + return buf; +} + +static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret) +{ + const char *token_start = buf; + +#ifdef __SSE4_2__ + static const char ALIGNED(16) ranges1[16] = "\0\010" /* allow HT */ + "\012\037" /* allow SP and up to but not including DEL */ + "\177\177"; /* allow chars w. MSB set */ + int found; + buf = findchar_fast(buf, buf_end, ranges1, 6, &found); + if (found) + goto FOUND_CTL; +#else + /* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */ + while (likely(buf_end - buf >= 8)) { +#define DOIT() \ + do { \ + if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \ + goto NonPrintable; \ + ++buf; \ + } while (0) + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); + DOIT(); +#undef DOIT + continue; + NonPrintable: + if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { + goto FOUND_CTL; + } + ++buf; + } +#endif + for (;; ++buf) { + CHECK_EOF(); + if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { + if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { + goto FOUND_CTL; + } + } + } +FOUND_CTL: + if (likely(*buf == '\015')) { + ++buf; + EXPECT_CHAR('\012'); + *token_len = buf - 2 - token_start; + } else if (*buf == '\012') { + *token_len = buf - token_start; + ++buf; + } else { + *ret = -1; + return NULL; + } + *token = token_start; + + return buf; +} + +static const char *is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret) +{ + int ret_cnt = 0; + buf = last_len < 3 ? buf : buf + last_len - 3; + + while (1) { + CHECK_EOF(); + if (*buf == '\015') { + ++buf; + CHECK_EOF(); + EXPECT_CHAR('\012'); + ++ret_cnt; + } else if (*buf == '\012') { + ++buf; + ++ret_cnt; + } else { + ++buf; + ret_cnt = 0; + } + if (ret_cnt == 2) { + return buf; + } + } + + *ret = -2; + return NULL; +} + +#define PARSE_INT(valp_, mul_) \ + if (*buf < '0' || '9' < *buf) { \ + buf++; \ + *ret = -1; \ + return NULL; \ + } \ + *(valp_) = (mul_) * (*buf++ - '0'); + +#define PARSE_INT_3(valp_) \ + do { \ + int res_ = 0; \ + PARSE_INT(&res_, 100) \ + *valp_ = res_; \ + PARSE_INT(&res_, 10) \ + *valp_ += res_; \ + PARSE_INT(&res_, 1) \ + *valp_ += res_; \ + } while (0) + +/* returned pointer is always within [buf, buf_end), or null */ +static const char *parse_token(const char *buf, const char *buf_end, const char **token, size_t *token_len, char next_char, + int *ret) +{ + /* We use pcmpestri to detect non-token characters. This instruction can take no more than eight character ranges (8*2*8=128 + * bits that is the size of a SSE register). Due to this restriction, characters `|` and `~` are handled in the slow loop. */ + static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up to SP */ + "\"\"" /* 0x22 */ + "()" /* 0x28,0x29 */ + ",," /* 0x2c */ + "//" /* 0x2f */ + ":@" /* 0x3a-0x40 */ + "[]" /* 0x5b-0x5d */ + "{\xff"; /* 0x7b-0xff */ + const char *buf_start = buf; + int found; + buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found); + if (!found) { + CHECK_EOF(); + } + while (1) { + if (*buf == next_char) { + break; + } else if (!token_char_map[(unsigned char)*buf]) { + *ret = -1; + return NULL; + } + ++buf; + CHECK_EOF(); + } + *token = buf_start; + *token_len = buf - buf_start; + return buf; +} + +/* returned pointer is always within [buf, buf_end), or null */ +static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret) +{ + /* we want at least [HTTP/1.] to try to parse */ + if (buf_end - buf < 9) { + *ret = -2; + return NULL; + } + EXPECT_CHAR_NO_CHECK('H'); + EXPECT_CHAR_NO_CHECK('T'); + EXPECT_CHAR_NO_CHECK('T'); + EXPECT_CHAR_NO_CHECK('P'); + EXPECT_CHAR_NO_CHECK('/'); + EXPECT_CHAR_NO_CHECK('1'); + EXPECT_CHAR_NO_CHECK('.'); + PARSE_INT(minor_version, 1); + return buf; +} + +static const char *parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, size_t *num_headers, + size_t max_headers, int *ret) +{ + for (;; ++*num_headers) { + CHECK_EOF(); + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + break; + } else if (*buf == '\012') { + ++buf; + break; + } + if (*num_headers == max_headers) { + *ret = -1; + return NULL; + } + if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) { + /* parsing name, but do not discard SP before colon, see + * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */ + if ((buf = parse_token(buf, buf_end, &headers[*num_headers].name, &headers[*num_headers].name_len, ':', ret)) == NULL) { + return NULL; + } + if (headers[*num_headers].name_len == 0) { + *ret = -1; + return NULL; + } + ++buf; + for (;; ++buf) { + CHECK_EOF(); + if (!(*buf == ' ' || *buf == '\t')) { + break; + } + } + } else { + headers[*num_headers].name = NULL; + headers[*num_headers].name_len = 0; + } + const char *value; + size_t value_len; + if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len, ret)) == NULL) { + return NULL; + } + /* remove trailing SPs and HTABs */ + const char *value_end = value + value_len; + for (; value_end != value; --value_end) { + const char c = *(value_end - 1); + if (!(c == ' ' || c == '\t')) { + break; + } + } + headers[*num_headers].value = value; + headers[*num_headers].value_len = value_end - value; + } + return buf; +} + +static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path, + size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, + size_t max_headers, int *ret) +{ + /* skip first empty line (some clients add CRLF after POST content) */ + CHECK_EOF(); + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + } else if (*buf == '\012') { + ++buf; + } + + /* parse request line */ + if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == NULL) { + return NULL; + } + do { + ++buf; + CHECK_EOF(); + } while (*buf == ' '); + ADVANCE_TOKEN(*path, *path_len); + do { + ++buf; + CHECK_EOF(); + } while (*buf == ' '); + if (*method_len == 0 || *path_len == 0) { + *ret = -1; + return NULL; + } + if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { + return NULL; + } + if (*buf == '\015') { + ++buf; + EXPECT_CHAR('\012'); + } else if (*buf == '\012') { + ++buf; + } else { + *ret = -1; + return NULL; + } + + return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); +} + +int phr_parse_request(const char *buf_start, size_t len, const char **method, size_t *method_len, const char **path, + size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len) +{ + const char *buf = buf_start, *buf_end = buf_start + len; + size_t max_headers = *num_headers; + int r; + + *method = NULL; + *method_len = 0; + *path = NULL; + *path_len = 0; + *minor_version = -1; + *num_headers = 0; + + /* if last_len != 0, check if the request is complete (a fast countermeasure + againt slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers, + &r)) == NULL) { + return r; + } + + return (int)(buf - buf_start); +} + +static const char *parse_response(const char *buf, const char *buf_end, int *minor_version, int *status, const char **msg, + size_t *msg_len, struct phr_header *headers, size_t *num_headers, size_t max_headers, int *ret) +{ + /* parse "HTTP/1.x" */ + if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { + return NULL; + } + /* skip space */ + if (*buf != ' ') { + *ret = -1; + return NULL; + } + do { + ++buf; + CHECK_EOF(); + } while (*buf == ' '); + /* parse status code, we want at least [:digit:][:digit:][:digit:] to try to parse */ + if (buf_end - buf < 4) { + *ret = -2; + return NULL; + } + PARSE_INT_3(status); + + /* get message including preceding space */ + if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) { + return NULL; + } + if (*msg_len == 0) { + /* ok */ + } else if (**msg == ' ') { + /* Remove preceding space. Successful return from `get_token_to_eol` guarantees that we would hit something other than SP + * before running past the end of the given buffer. */ + do { + ++*msg; + --*msg_len; + } while (**msg == ' '); + } else { + /* garbage found after status code */ + *ret = -1; + return NULL; + } + + return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); +} + +int phr_parse_response(const char *buf_start, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len, + struct phr_header *headers, size_t *num_headers, size_t last_len) +{ + const char *buf = buf_start, *buf_end = buf + len; + size_t max_headers = *num_headers; + int r; + + *minor_version = -1; + *status = 0; + *msg = NULL; + *msg_len = 0; + *num_headers = 0; + + /* if last_len != 0, check if the response is complete (a fast countermeasure + against slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) == NULL) { + return r; + } + + return (int)(buf - buf_start); +} + +int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len) +{ + const char *buf = buf_start, *buf_end = buf + len; + size_t max_headers = *num_headers; + int r; + + *num_headers = 0; + + /* if last_len != 0, check if the response is complete (a fast countermeasure + against slowloris */ + if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { + return r; + } + + if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) { + return r; + } + + return (int)(buf - buf_start); +} + +enum { + CHUNKED_IN_CHUNK_SIZE, + CHUNKED_IN_CHUNK_EXT, + CHUNKED_IN_CHUNK_DATA, + CHUNKED_IN_CHUNK_CRLF, + CHUNKED_IN_TRAILERS_LINE_HEAD, + CHUNKED_IN_TRAILERS_LINE_MIDDLE +}; + +static int decode_hex(int ch) +{ + if ('0' <= ch && ch <= '9') { + return ch - '0'; + } else if ('A' <= ch && ch <= 'F') { + return ch - 'A' + 0xa; + } else if ('a' <= ch && ch <= 'f') { + return ch - 'a' + 0xa; + } else { + return -1; + } +} + +ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *_bufsz) +{ + size_t dst = 0, src = 0, bufsz = *_bufsz; + ssize_t ret = -2; /* incomplete */ + + while (1) { + switch (decoder->_state) { + case CHUNKED_IN_CHUNK_SIZE: + for (;; ++src) { + int v; + if (src == bufsz) + goto Exit; + if ((v = decode_hex(buf[src])) == -1) { + if (decoder->_hex_count == 0) { + ret = -1; + goto Exit; + } + break; + } + if (decoder->_hex_count == sizeof(size_t) * 2) { + ret = -1; + goto Exit; + } + decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v; + ++decoder->_hex_count; + } + decoder->_hex_count = 0; + decoder->_state = CHUNKED_IN_CHUNK_EXT; + /* fallthru */ + case CHUNKED_IN_CHUNK_EXT: + /* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */ + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] == '\012') + break; + } + ++src; + if (decoder->bytes_left_in_chunk == 0) { + if (decoder->consume_trailer) { + decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; + break; + } else { + goto Complete; + } + } + decoder->_state = CHUNKED_IN_CHUNK_DATA; + /* fallthru */ + case CHUNKED_IN_CHUNK_DATA: { + size_t avail = bufsz - src; + if (avail < decoder->bytes_left_in_chunk) { + if (dst != src) + memmove(buf + dst, buf + src, avail); + src += avail; + dst += avail; + decoder->bytes_left_in_chunk -= avail; + goto Exit; + } + if (dst != src) + memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk); + src += decoder->bytes_left_in_chunk; + dst += decoder->bytes_left_in_chunk; + decoder->bytes_left_in_chunk = 0; + decoder->_state = CHUNKED_IN_CHUNK_CRLF; + } + /* fallthru */ + case CHUNKED_IN_CHUNK_CRLF: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] != '\015') + break; + } + if (buf[src] != '\012') { + ret = -1; + goto Exit; + } + ++src; + decoder->_state = CHUNKED_IN_CHUNK_SIZE; + break; + case CHUNKED_IN_TRAILERS_LINE_HEAD: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] != '\015') + break; + } + if (buf[src++] == '\012') + goto Complete; + decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE; + /* fallthru */ + case CHUNKED_IN_TRAILERS_LINE_MIDDLE: + for (;; ++src) { + if (src == bufsz) + goto Exit; + if (buf[src] == '\012') + break; + } + ++src; + decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; + break; + default: + assert(!"decoder is corrupt"); + } + } + +Complete: + ret = bufsz - src; +Exit: + if (dst != src) + memmove(buf + dst, buf + src, bufsz - src); + *_bufsz = dst; + return ret; +} + +int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder) +{ + return decoder->_state == CHUNKED_IN_CHUNK_DATA; +} + +#undef CHECK_EOF +#undef EXPECT_CHAR +#undef ADVANCE_TOKEN From e59ee38ae2537a4d80baec59985531cb4e2f866d Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Fri, 24 Nov 2023 10:39:22 +0100 Subject: [PATCH 08/45] feat(lnm): write server init code --- lnm/Makefile | 6 +- lnm/include/lnm/common.h | 1 + lnm/include/lnm/http/loop.h | 70 ++++++++++++++++++ lnm/include/lnm/loop.h | 2 +- lnm/src/_include/lnm/http/loop_internal.h | 34 +++++++++ lnm/src/http/lnm_http_loop.c | 86 +++++++++++++++++++++++ 6 files changed, 195 insertions(+), 4 deletions(-) create mode 100644 lnm/include/lnm/http/loop.h create mode 100644 lnm/src/_include/lnm/http/loop_internal.h create mode 100644 lnm/src/http/lnm_http_loop.c diff --git a/lnm/Makefile b/lnm/Makefile index 78e1e0e..7371c9f 100644 --- a/lnm/Makefile +++ b/lnm/Makefile @@ -6,7 +6,7 @@ LIB := $(BUILD_DIR)/$(LIB_FILENAME) SRCS != find '$(SRC_DIR)' -iname '*.c' -SRCS_H != find $(INC_DIRS) -iname '*.h' +SRCS_H != find include -iname '*.h' SRCS_H_INTERNAL != find $(SRC_DIR) -iname '*.h' SRCS_TEST != find '$(TEST_DIR)' -iname '*.c' SRCS_THIRDPARTY != find '$(THIRDPARTY_DIR)/src' -iname '*.c' @@ -91,11 +91,11 @@ $(BUILD_DIR)/$(EXAMPLE_DIR)/%.c.o: $(EXAMPLE_DIR)/%.c # =====MAINTENANCE===== .PHONY: lint lint: - clang-format -n --Werror $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) $(SRCS_EXAMPLE) + clang-format -n --Werror $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) .PHONY: fmt fmt: - clang-format -i $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) $(SRCS_EXAMPLE) + clang-format -i $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) .PHONY: check check: diff --git a/lnm/include/lnm/common.h b/lnm/include/lnm/common.h index 1500d73..381b4c9 100644 --- a/lnm/include/lnm/common.h +++ b/lnm/include/lnm/common.h @@ -23,6 +23,7 @@ typedef enum { lnm_err_failed_network, lnm_err_failed_poll, lnm_err_not_setup, + lnm_err_bad_regex } lnm_err; #endif diff --git a/lnm/include/lnm/http/loop.h b/lnm/include/lnm/http/loop.h new file mode 100644 index 0000000..9b6f0d3 --- /dev/null +++ b/lnm/include/lnm/http/loop.h @@ -0,0 +1,70 @@ +#ifndef LNM_HTTP_LOOP +#define LNM_HTTP_LOOP + +#include "lnm/common.h" + +typedef struct lnm_loop lnm_http_loop; + +typedef struct lnm_http_conn lnm_http_conn; + +typedef struct lnm_http_step lnm_http_step; + +typedef struct lnm_http_route lnm_http_route; + +typedef lnm_err (*lnm_http_step_fn)(lnm_http_conn *conn); + +/** + * Initialize a new `lnm_http_loop`. + * + * @param out where to store pointer to new `lnm_http_loop` + */ +lnm_err lnm_http_loop_init(lnm_http_loop **out); + +/** + * Initialize a first step + * + * @param out where to store pointer to new `lnm_http_step` + * @param fn step function associated with the step + */ +lnm_err lnm_http_step_init(lnm_http_step **out, lnm_http_step_fn fn); + +/** + * Append the given step fn to the step. + * + * @param out where to store pointer to new `lnm_http_step` + * @param step step to append new step to + * @param fn step funcitonn + */ +lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step *step, + lnm_http_step_fn fn); + +/** + * Initialize a new route of type literal. + * + * @param out where to store pointer to new `lnm_http_route` + * @param path literal path to match + * @param step step to process request with + */ +lnm_err lnm_http_route_init_literal(lnm_http_route **out, const char *path, + lnm_http_step *step); + +/** + * Initialize a new route of type regex. + * + * @param out where to store pointer to new `lnm_http_route` + * @param pattern regex pattern + * @param regex_group_count how many regex groups are contained in the pattern + * @param step step to process request with + */ +lnm_err lnm_http_route_init_regex(lnm_http_route **out, const char *pattern, + int regex_group_count, lnm_http_step *step); + +/** + * Add a new route to the HTTP route. + * + * @param hl HTTP loop to modify + * @param route route to add + */ +void lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route); + +#endif diff --git a/lnm/include/lnm/loop.h b/lnm/include/lnm/loop.h index a3625c6..a84055a 100644 --- a/lnm/include/lnm/loop.h +++ b/lnm/include/lnm/loop.h @@ -30,7 +30,7 @@ typedef struct { } w; } lnm_loop_conn; -typedef struct { +typedef struct lnm_loop { int listen_fd; struct { lnm_loop_conn **arr; diff --git a/lnm/src/_include/lnm/http/loop_internal.h b/lnm/src/_include/lnm/http/loop_internal.h new file mode 100644 index 0000000..5d86a03 --- /dev/null +++ b/lnm/src/_include/lnm/http/loop_internal.h @@ -0,0 +1,34 @@ +#ifndef LNM_HTTP_LOOP_INTERNAL +#define LNM_HTTP_LOOP_INTERNAL + +#include + +#include "lnm/http/loop.h" + +typedef struct lnm_http_conn { + +} lnm_http_conn; + +typedef struct lnm_http_step { + lnm_http_step_fn fn; + struct lnm_http_step *next; +} lnm_http_step; + +typedef enum lnm_http_route_type { + lnm_http_route_type_literal = 0, + lnm_http_route_type_regex, +} lnm_http_route_type; + +typedef struct lnm_http_route { + union { + regex_t *regex; + const char *s; + } route; + lnm_http_route_type type; + int regex_group_count; + lnm_http_step *step; +} lnm_http_route; + +lnm_err lnm_http_route_init(lnm_http_route **out); + +#endif diff --git a/lnm/src/http/lnm_http_loop.c b/lnm/src/http/lnm_http_loop.c new file mode 100644 index 0000000..f082fe7 --- /dev/null +++ b/lnm/src/http/lnm_http_loop.c @@ -0,0 +1,86 @@ +#include + +#include "lnm/common.h" +#include "lnm/http/loop.h" +#include "lnm/http/loop_internal.h" +#include "lnm/loop_internal.h" + +lnm_err lnm_http_loop_init(lnm_http_loop **out) { + lnm_http_loop *hl = calloc(1, sizeof(lnm_http_loop)); + + if (hl == NULL) { + return lnm_err_failed_alloc; + } + + *out = hl; + + return lnm_err_ok; +} + +lnm_err lnm_http_step_init(lnm_http_step **out, lnm_http_step_fn fn) { + lnm_http_step *step = calloc(1, sizeof(lnm_http_step)); + + if (step == NULL) { + return lnm_err_failed_alloc; + } + + step->fn = fn; + *out = step; + + return lnm_err_ok; +} + +lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step *step, + lnm_http_step_fn fn) { + LNM_RES(lnm_http_step_init(out, fn)); + + step->next = *out; + + return lnm_err_ok; +} + +lnm_err lnm_http_route_init(lnm_http_route **out) { + lnm_http_route *route = calloc(1, sizeof(lnm_http_route)); + + if (route == NULL) { + return lnm_err_failed_alloc; + } + + *out = route; + + return lnm_err_ok; +} + +lnm_err lnm_http_route_init_literal(lnm_http_route **out, const char *path, + lnm_http_step *step) { + LNM_RES(lnm_http_route_init(out)); + + (*out)->type = lnm_http_route_type_literal; + (*out)->route.s = path; + (*out)->step = step; + + return lnm_err_ok; +} + +lnm_err lnm_http_route_init_regex(lnm_http_route **out, const char *pattern, + int regex_group_count, lnm_http_step *step) { + regex_t *regex = calloc(1, sizeof(regex_t)); + + if (regex == NULL) { + return lnm_err_failed_alloc; + } + + if (regcomp(regex, pattern, REG_EXTENDED) != 0) { + free(regex); + return lnm_err_bad_regex; + } + + LNM_RES2(lnm_http_route_init(out), free(regex)); + + (*out)->type = lnm_http_route_type_regex; + (*out)->route.regex = regex; + (*out)->regex_group_count = regex_group_count; + (*out)->step = step; + + return lnm_err_ok; +} From 4c2b85d43662a15f19d2a125c3a0e2394aa20add Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Mon, 27 Nov 2023 13:50:08 +0100 Subject: [PATCH 09/45] feat(lnm): add request parser --- lnm/include/lnm/http/consts.h | 2 +- lnm/include/lnm/http/loop.h | 23 +++++---- lnm/include/lnm/http/req.h | 48 +++++++++++++++++++ lnm/src/_include/lnm/http/loop_internal.h | 17 +++++-- lnm/src/http/req.c | 58 +++++++++++++++++++++++ 5 files changed, 134 insertions(+), 14 deletions(-) create mode 100644 lnm/include/lnm/http/req.h create mode 100644 lnm/src/http/req.c diff --git a/lnm/include/lnm/http/consts.h b/lnm/include/lnm/http/consts.h index 4b6bc92..37f3239 100644 --- a/lnm/include/lnm/http/consts.h +++ b/lnm/include/lnm/http/consts.h @@ -12,7 +12,7 @@ typedef enum lnm_http_method { http_method_put, http_method_patch, http_method_delete -} http_method; +} lnm_http_method; extern const char *lnm_http_status_names[][32]; diff --git a/lnm/include/lnm/http/loop.h b/lnm/include/lnm/http/loop.h index 9b6f0d3..f84a1ea 100644 --- a/lnm/include/lnm/http/loop.h +++ b/lnm/include/lnm/http/loop.h @@ -3,9 +3,11 @@ #include "lnm/common.h" +#define LNM_HTTP_MAX_REQ_HEADERS 32 + typedef struct lnm_loop lnm_http_loop; -typedef struct lnm_http_conn lnm_http_conn; +typedef struct lnm_conn lnm_http_conn; typedef struct lnm_http_step lnm_http_step; @@ -20,14 +22,6 @@ typedef lnm_err (*lnm_http_step_fn)(lnm_http_conn *conn); */ lnm_err lnm_http_loop_init(lnm_http_loop **out); -/** - * Initialize a first step - * - * @param out where to store pointer to new `lnm_http_step` - * @param fn step function associated with the step - */ -lnm_err lnm_http_step_init(lnm_http_step **out, lnm_http_step_fn fn); - /** * Append the given step fn to the step. * @@ -67,4 +61,15 @@ lnm_err lnm_http_route_init_regex(lnm_http_route **out, const char *pattern, */ void lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route); +/** + * Represents what state an HTTP loop request is currently in. + */ +typedef enum lnm_http_loop_state { + lnm_http_loop_state_parse_req = 0, +} lnm_http_loop_state; + +typedef struct lnm_http_loop_ctx { + lnm_http_loop_state state; +} lnm_http_loop_ctx; + #endif diff --git a/lnm/include/lnm/http/req.h b/lnm/include/lnm/http/req.h new file mode 100644 index 0000000..4741f4d --- /dev/null +++ b/lnm/include/lnm/http/req.h @@ -0,0 +1,48 @@ +#ifndef LNM_HTTP_REQ +#define LNM_HTTP_REQ + +#include + +#include "picohttpparser.h" + +#include "lnm/http/consts.h" +#include "lnm/http/loop.h" + +/** + * Represents the parsed HTTP request + */ +typedef struct lnm_http_req { + size_t len; + int minor_version; + lnm_http_method method; + struct { + const char *s; + size_t len; + } path; + struct { + const char *s; + size_t len; + } query; + struct { + struct phr_header arr[LNM_HTTP_MAX_REQ_HEADERS]; + size_t len; + } headers; +} lnm_http_req; + +typedef enum lnm_http_parse_err { + lnm_http_parse_err_ok = 0, + lnm_http_parse_err_incomplete, + lnm_http_parse_err_invalid, + lnm_http_parse_err_unknown_method, +} lnm_http_parse_err; + +/** + * Try to parse the given buffer into an HTTP request. + * + * @param req request to store parsed data in + * @param buf buffer to parse; might be modified + * @param len length of buf + */ +lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, size_t len); + +#endif diff --git a/lnm/src/_include/lnm/http/loop_internal.h b/lnm/src/_include/lnm/http/loop_internal.h index 5d86a03..7410ade 100644 --- a/lnm/src/_include/lnm/http/loop_internal.h +++ b/lnm/src/_include/lnm/http/loop_internal.h @@ -5,10 +5,6 @@ #include "lnm/http/loop.h" -typedef struct lnm_http_conn { - -} lnm_http_conn; - typedef struct lnm_http_step { lnm_http_step_fn fn; struct lnm_http_step *next; @@ -29,6 +25,19 @@ typedef struct lnm_http_route { lnm_http_step *step; } lnm_http_route; +/** + * Initialize a new empty route. + * + * @param out where to store pointer to new `lnm_http_route` + */ lnm_err lnm_http_route_init(lnm_http_route **out); +/** + * Initialize a first step. + * + * @param out where to store pointer to new `lnm_http_step` + * @param fn step function associated with the step + */ +lnm_err lnm_http_step_init(lnm_http_step **out, lnm_http_step_fn fn); + #endif diff --git a/lnm/src/http/req.c b/lnm/src/http/req.c new file mode 100644 index 0000000..2327ce6 --- /dev/null +++ b/lnm/src/http/req.c @@ -0,0 +1,58 @@ +#include +#include + +#include "lnm/http/consts.h" +#include "lnm/http/loop.h" +#include "lnm/http/req.h" + +lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, + size_t len) { + const char *method; + char *path; + size_t method_len, path_len; + + req->headers.len = LNM_HTTP_MAX_REQ_HEADERS; + + int req_len = phr_parse_request( + buf, len, &method, &method_len, (const char **)&path, &path_len, + &req->minor_version, req->headers.arr, &req->headers.len, 0); + + if (req_len == -1) { + return lnm_http_parse_err_invalid; + } else if (req_len == -2) { + return lnm_http_parse_err_incomplete; + } + + bool known_method = false; + + for (size_t i = 0; i < lnm_http_method_names_len && !known_method; i++) { + if (strncmp(method, lnm_http_method_names[i], method_len) == 0) { + req->method = i; + known_method = true; + } + } + + if (!known_method) { + return lnm_http_parse_err_unknown_method; + } + + char *question_mark = strchr(path, '?'); + + // Only store query if the path doesn't simply end with a question mark + if ((question_mark != NULL) && (path_len - (question_mark + 1 - path) > 0)) { + req->query.s = question_mark + 1; + req->query.len = path_len - (question_mark + 1 - path); + + path_len = question_mark - path; + } + + // All parsed strings should be null-terminated. This character is either a + // newline (if at the end of the path), or a question mark (if a query is + // present). + path[path_len] = '\0'; + req->path.len = path_len; + req->path.s = path; + req->len = req_len; + + return lnm_http_parse_err_ok; +} From e04f6e170e191b8574438f10d5930d63315b456b Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Mon, 27 Nov 2023 19:54:53 +0100 Subject: [PATCH 10/45] feat(lnm): start of processing code --- lnm/include/lnm/http/loop.h | 28 ++++++++++++++--- lnm/include/lnm/http/req.h | 3 +- lnm/include/lnm/loop.h | 2 +- lnm/src/http/loop_ctx.c | 0 lnm/src/http/loop_process.c | 60 +++++++++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 lnm/src/http/loop_ctx.c create mode 100644 lnm/src/http/loop_process.c diff --git a/lnm/include/lnm/http/loop.h b/lnm/include/lnm/http/loop.h index f84a1ea..2a253d3 100644 --- a/lnm/include/lnm/http/loop.h +++ b/lnm/include/lnm/http/loop.h @@ -1,19 +1,26 @@ #ifndef LNM_HTTP_LOOP #define LNM_HTTP_LOOP -#include "lnm/common.h" +#include -#define LNM_HTTP_MAX_REQ_HEADERS 32 +#include "lnm/common.h" +#include "lnm/http/req.h" typedef struct lnm_loop lnm_http_loop; -typedef struct lnm_conn lnm_http_conn; +typedef struct lnm_loop_conn lnm_http_conn; typedef struct lnm_http_step lnm_http_step; typedef struct lnm_http_route lnm_http_route; -typedef lnm_err (*lnm_http_step_fn)(lnm_http_conn *conn); +typedef enum lnm_step_err { + lnm_step_err_done = 0, + lnm_step_err_io_needed, + lnm_step_err_close, +} lnm_step_err; + +typedef lnm_step_err (*lnm_http_step_fn)(lnm_http_conn *conn); /** * Initialize a new `lnm_http_loop`. @@ -66,10 +73,23 @@ void lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route); */ typedef enum lnm_http_loop_state { lnm_http_loop_state_parse_req = 0, + lnm_http_loop_state_route, + lnm_http_loop_state_steps, } lnm_http_loop_state; +typedef struct lnm_http_loop_gctx { + struct { + lnm_http_route **arr; + size_t len; + } routes; + void *c; +} lnm_http_loop_gctx; + typedef struct lnm_http_loop_ctx { lnm_http_loop_state state; + lnm_http_req req; + lnm_http_loop_gctx *g; + void *c; } lnm_http_loop_ctx; #endif diff --git a/lnm/include/lnm/http/req.h b/lnm/include/lnm/http/req.h index 4741f4d..35bd21a 100644 --- a/lnm/include/lnm/http/req.h +++ b/lnm/include/lnm/http/req.h @@ -6,7 +6,8 @@ #include "picohttpparser.h" #include "lnm/http/consts.h" -#include "lnm/http/loop.h" + +#define LNM_HTTP_MAX_REQ_HEADERS 32 /** * Represents the parsed HTTP request diff --git a/lnm/include/lnm/loop.h b/lnm/include/lnm/loop.h index a84055a..8493170 100644 --- a/lnm/include/lnm/loop.h +++ b/lnm/include/lnm/loop.h @@ -15,7 +15,7 @@ typedef enum { lnm_loop_state_end, } lnm_loop_state; -typedef struct { +typedef struct lnm_loop_conn { int fd; lnm_loop_state state; void *ctx; diff --git a/lnm/src/http/loop_ctx.c b/lnm/src/http/loop_ctx.c new file mode 100644 index 0000000..e69de29 diff --git a/lnm/src/http/loop_process.c b/lnm/src/http/loop_process.c new file mode 100644 index 0000000..e93aca7 --- /dev/null +++ b/lnm/src/http/loop_process.c @@ -0,0 +1,60 @@ +#include "lnm/http/loop.h" +#include "lnm/http/loop_internal.h" +#include "lnm/http/req.h" +#include "lnm/loop.h" +#include "lnm/loop_internal.h" + +void lnm_http_loop_process_parse_req(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; + + lnm_http_parse_err res = lnm_http_req_parse( + &ctx->req, &conn->r.buf[conn->r.read], conn->r.size - conn->r.read); + + switch (res) { + case lnm_http_parse_err_ok: + ctx->state = lnm_http_loop_state_steps; + break; + case lnm_http_parse_err_incomplete: + // If the request is already the size of the read buffer, we close the + // request. Otherwise, we wait for anything read + if (conn->r.size - conn->r.read == LNM_LOOP_BUF_SIZE) { + conn->state = lnm_loop_state_end; + } + break; + case lnm_http_parse_err_invalid: + conn->state = lnm_loop_state_end; + break; + case lnm_http_parse_err_unknown_method: + // TODO set status code here + conn->state = lnm_loop_state_end; + break; + } +} + +void lnm_http_loop_process_route(lnm_http_conn *conn) {} + +void lnm_http_loop_process_steps(lnm_http_conn *conn) { + /* lnm_http_loop_ctx *ctx = conn->ctx; */ + + /* while () */ +} + +void (*process_fns[])(lnm_http_conn *conn) = { + lnm_http_loop_process_parse_req, + lnm_http_loop_process_route, + lnm_http_loop_process_steps, +}; + +void lnm_http_loop_process(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; + + lnm_http_loop_state cur_state; + + // As long as the processing is able to advance to a next state, we keep + // progressing + do { + cur_state = ctx->state; + + process_fns[cur_state](conn); + } while (conn->state == lnm_loop_state_req && cur_state != ctx->state); +} From d119f85260d19266d87d7227ee056b6acd125fef Mon Sep 17 00:00:00 2001 From: Jef Roosens Date: Tue, 28 Nov 2023 10:32:08 +0100 Subject: [PATCH 11/45] feat(lnm): wrote part of context code --- lnm/include/lnm/http/loop.h | 15 ++++++- lnm/src/_include/lnm/http/loop_internal.h | 23 +++++++++++ lnm/src/http/lnm_http_loop.c | 9 ++++- lnm/src/http/loop_ctx.c | 49 +++++++++++++++++++++++ 4 files changed, 94 insertions(+), 2 deletions(-) diff --git a/lnm/include/lnm/http/loop.h b/lnm/include/lnm/http/loop.h index 2a253d3..550d046 100644 --- a/lnm/include/lnm/http/loop.h +++ b/lnm/include/lnm/http/loop.h @@ -18,16 +18,26 @@ typedef enum lnm_step_err { lnm_step_err_done = 0, lnm_step_err_io_needed, lnm_step_err_close, + lnm_step_err_res, } lnm_step_err; typedef lnm_step_err (*lnm_http_step_fn)(lnm_http_conn *conn); +typedef lnm_err (*lnm_http_ctx_init_fn)(void **c_ctx, void *gctx); + +typedef void (*lnm_http_ctx_reset_fn)(void *c_ctx); + +typedef void (*lnm_http_ctx_free_fn)(void *c_ctx); + /** * Initialize a new `lnm_http_loop`. * * @param out where to store pointer to new `lnm_http_loop` */ -lnm_err lnm_http_loop_init(lnm_http_loop **out); +lnm_err lnm_http_loop_init(lnm_http_loop **out, void *c_gctx, + lnm_http_ctx_init_fn ctx_init, + lnm_http_ctx_reset_fn ctx_reset, + lnm_http_ctx_free_fn ctx_free); /** * Append the given step fn to the step. @@ -82,6 +92,9 @@ typedef struct lnm_http_loop_gctx { lnm_http_route **arr; size_t len; } routes; + lnm_http_ctx_init_fn ctx_init; + lnm_http_ctx_reset_fn ctx_reset; + lnm_http_ctx_free_fn ctx_free; void *c; } lnm_http_loop_gctx; diff --git a/lnm/src/_include/lnm/http/loop_internal.h b/lnm/src/_include/lnm/http/loop_internal.h index 7410ade..c6bc128 100644 --- a/lnm/src/_include/lnm/http/loop_internal.h +++ b/lnm/src/_include/lnm/http/loop_internal.h @@ -40,4 +40,27 @@ lnm_err lnm_http_route_init(lnm_http_route **out); */ lnm_err lnm_http_step_init(lnm_http_step **out, lnm_http_step_fn fn); +/** + * Initialize a new global context object. + * + * @param out where to store pointer to new `lnm_http_loop_gctx` + */ +lnm_err lnm_http_loop_gctx_init(lnm_http_loop_gctx **out, void *c_gctx, + lnm_http_ctx_init_fn ctx_init, + lnm_http_ctx_reset_fn ctx_reset, + lnm_http_ctx_free_fn ctx_free); + +/** + * Initialize a new context. + * + * @param out where to store pointer to new object + * @param gctx global context for the loop + */ +lnm_err lnm_http_loop_ctx_init(lnm_http_loop_ctx **out, + lnm_http_loop_gctx *gctx); + +void lnm_http_loop_ctx_reset(lnm_http_loop_ctx *ctx); + +void lnm_http_loop_ctx_free(lnm_http_loop_ctx *ctx); + #endif diff --git a/lnm/src/http/lnm_http_loop.c b/lnm/src/http/lnm_http_loop.c index f082fe7..024da9d 100644 --- a/lnm/src/http/lnm_http_loop.c +++ b/lnm/src/http/lnm_http_loop.c @@ -5,13 +5,20 @@ #include "lnm/http/loop_internal.h" #include "lnm/loop_internal.h" -lnm_err lnm_http_loop_init(lnm_http_loop **out) { +lnm_err lnm_http_loop_init(lnm_http_loop **out, void *c_gctx, + lnm_http_ctx_init_fn ctx_init, + lnm_http_ctx_reset_fn ctx_reset, + lnm_http_ctx_free_fn ctx_free) { lnm_http_loop *hl = calloc(1, sizeof(lnm_http_loop)); if (hl == NULL) { return lnm_err_failed_alloc; } + LNM_RES2(lnm_http_loop_gctx_init((lnm_http_loop_gctx **)&hl->gctx, c_gctx, + ctx_init, ctx_reset, ctx_free), + free(hl)); + *out = hl; return lnm_err_ok; diff --git a/lnm/src/http/loop_ctx.c b/lnm/src/http/loop_ctx.c index e69de29..64ac8eb 100644 --- a/lnm/src/http/loop_ctx.c +++ b/lnm/src/http/loop_ctx.c @@ -0,0 +1,49 @@ +#include "lnm/http/loop_internal.h" + +lnm_err lnm_http_loop_gctx_init(lnm_http_loop_gctx **out, void *c_gctx, + lnm_http_ctx_init_fn ctx_init, + lnm_http_ctx_reset_fn ctx_reset, + lnm_http_ctx_free_fn ctx_free) { + lnm_http_loop_gctx *gctx = calloc(1, sizeof(lnm_http_loop_gctx)); + + if (gctx == NULL) { + return lnm_err_failed_alloc; + } + + gctx->c = c_gctx; + gctx->ctx_init = ctx_init; + gctx->ctx_reset = ctx_reset; + gctx->ctx_free = ctx_free; + + *out = gctx; + + return lnm_err_ok; +} + +lnm_err lnm_http_loop_ctx_init(lnm_http_loop_ctx **out, + lnm_http_loop_gctx *gctx) { + lnm_http_loop_ctx *ctx = calloc(1, sizeof(lnm_http_loop_ctx)); + + if (ctx == NULL) { + return lnm_err_failed_alloc; + } + + LNM_RES2(gctx->ctx_init(&ctx->c, gctx), free(ctx)); + + ctx->g = gctx; + *out = ctx; + + return lnm_err_ok; +} + +void lnm_http_loop_ctx_reset(lnm_http_loop_ctx *ctx) { + ctx->g->ctx_reset(ctx->c); + // TODO actual reset stuff +} + +void lnm_http_loop_ctx_free(lnm_http_loop_ctx *ctx) { + ctx->g->ctx_free(ctx->c); + // TODO actual free stuff + + free(ctx); +} From 8c21ccf58b8b82c19e79b13e038d0b91580b36e9 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Tue, 28 Nov 2023 20:54:40 +0100 Subject: [PATCH 12/45] feat(lnm): part of routing code --- lnm/include/lnm/http/loop.h | 9 ++++-- lnm/include/lnm/http/req.h | 3 ++ lnm/src/_include/lnm/http/loop_internal.h | 3 ++ lnm/src/http/lnm_http_loop.c | 11 +++++-- lnm/src/http/loop_process.c | 37 ++++++++++++++++++++++- 5 files changed, 56 insertions(+), 7 deletions(-) diff --git a/lnm/include/lnm/http/loop.h b/lnm/include/lnm/http/loop.h index 550d046..20d746b 100644 --- a/lnm/include/lnm/http/loop.h +++ b/lnm/include/lnm/http/loop.h @@ -56,7 +56,8 @@ lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step *step, * @param path literal path to match * @param step step to process request with */ -lnm_err lnm_http_route_init_literal(lnm_http_route **out, const char *path, +lnm_err lnm_http_route_init_literal(lnm_http_route **out, + lnm_http_method method, const char *path, lnm_http_step *step); /** @@ -67,8 +68,9 @@ lnm_err lnm_http_route_init_literal(lnm_http_route **out, const char *path, * @param regex_group_count how many regex groups are contained in the pattern * @param step step to process request with */ -lnm_err lnm_http_route_init_regex(lnm_http_route **out, const char *pattern, - int regex_group_count, lnm_http_step *step); +lnm_err lnm_http_route_init_regex(lnm_http_route **out, lnm_http_method method, + const char *pattern, int regex_group_count, + lnm_http_step *step); /** * Add a new route to the HTTP route. @@ -101,6 +103,7 @@ typedef struct lnm_http_loop_gctx { typedef struct lnm_http_loop_ctx { lnm_http_loop_state state; lnm_http_req req; + lnm_http_route *route; lnm_http_loop_gctx *g; void *c; } lnm_http_loop_ctx; diff --git a/lnm/include/lnm/http/req.h b/lnm/include/lnm/http/req.h index 35bd21a..def3ad7 100644 --- a/lnm/include/lnm/http/req.h +++ b/lnm/include/lnm/http/req.h @@ -1,6 +1,7 @@ #ifndef LNM_HTTP_REQ #define LNM_HTTP_REQ +#include #include #include "picohttpparser.h" @@ -8,6 +9,7 @@ #include "lnm/http/consts.h" #define LNM_HTTP_MAX_REQ_HEADERS 32 +#define LNM_HTTP_MAX_REGEX_GROUPS 4 /** * Represents the parsed HTTP request @@ -19,6 +21,7 @@ typedef struct lnm_http_req { struct { const char *s; size_t len; + regmatch_t groups[LNM_HTTP_MAX_REGEX_GROUPS]; } path; struct { const char *s; diff --git a/lnm/src/_include/lnm/http/loop_internal.h b/lnm/src/_include/lnm/http/loop_internal.h index c6bc128..4d9e865 100644 --- a/lnm/src/_include/lnm/http/loop_internal.h +++ b/lnm/src/_include/lnm/http/loop_internal.h @@ -20,6 +20,7 @@ typedef struct lnm_http_route { regex_t *regex; const char *s; } route; + lnm_http_method method; lnm_http_route_type type; int regex_group_count; lnm_http_step *step; @@ -63,4 +64,6 @@ void lnm_http_loop_ctx_reset(lnm_http_loop_ctx *ctx); void lnm_http_loop_ctx_free(lnm_http_loop_ctx *ctx); +void lnm_http_loop_process(lnm_http_conn *conn); + #endif diff --git a/lnm/src/http/lnm_http_loop.c b/lnm/src/http/lnm_http_loop.c index 024da9d..e79d3c3 100644 --- a/lnm/src/http/lnm_http_loop.c +++ b/lnm/src/http/lnm_http_loop.c @@ -19,6 +19,7 @@ lnm_err lnm_http_loop_init(lnm_http_loop **out, void *c_gctx, ctx_init, ctx_reset, ctx_free), free(hl)); + hl->data_read = lnm_http_loop_process; *out = hl; return lnm_err_ok; @@ -58,19 +59,22 @@ lnm_err lnm_http_route_init(lnm_http_route **out) { return lnm_err_ok; } -lnm_err lnm_http_route_init_literal(lnm_http_route **out, const char *path, +lnm_err lnm_http_route_init_literal(lnm_http_route **out, + lnm_http_method method, const char *path, lnm_http_step *step) { LNM_RES(lnm_http_route_init(out)); (*out)->type = lnm_http_route_type_literal; + (*out)->method = method; (*out)->route.s = path; (*out)->step = step; return lnm_err_ok; } -lnm_err lnm_http_route_init_regex(lnm_http_route **out, const char *pattern, - int regex_group_count, lnm_http_step *step) { +lnm_err lnm_http_route_init_regex(lnm_http_route **out, lnm_http_method method, + const char *pattern, int regex_group_count, + lnm_http_step *step) { regex_t *regex = calloc(1, sizeof(regex_t)); if (regex == NULL) { @@ -84,6 +88,7 @@ lnm_err lnm_http_route_init_regex(lnm_http_route **out, const char *pattern, LNM_RES2(lnm_http_route_init(out), free(regex)); + (*out)->method = method; (*out)->type = lnm_http_route_type_regex; (*out)->route.regex = regex; (*out)->regex_group_count = regex_group_count; diff --git a/lnm/src/http/loop_process.c b/lnm/src/http/loop_process.c index e93aca7..5568647 100644 --- a/lnm/src/http/loop_process.c +++ b/lnm/src/http/loop_process.c @@ -1,3 +1,6 @@ +#include +#include + #include "lnm/http/loop.h" #include "lnm/http/loop_internal.h" #include "lnm/http/req.h" @@ -12,6 +15,7 @@ void lnm_http_loop_process_parse_req(lnm_http_conn *conn) { switch (res) { case lnm_http_parse_err_ok: + conn->r.read += ctx->req.len; ctx->state = lnm_http_loop_state_steps; break; case lnm_http_parse_err_incomplete: @@ -31,7 +35,38 @@ void lnm_http_loop_process_parse_req(lnm_http_conn *conn) { } } -void lnm_http_loop_process_route(lnm_http_conn *conn) {} +void lnm_http_loop_process_route(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; + lnm_http_loop_gctx *gctx = ctx->g; + + // 0: no match + // 1: matched route, but not method + // 2: fully matched route + int match_level = 0; + lnm_http_route *route; + + for (size_t i = 0; i < gctx->routes.len && match_level < 2; i++) { + route = gctx->routes.arr[i]; + bool matched_path; + + switch (route->type) { + case lnm_http_route_type_literal: + matched_path = + strncmp(route->route.s, ctx->req.path.s, ctx->req.path.len) == 0; + break; + case lnm_http_route_type_regex: + matched_path = + regexec(route->route.regex, ctx->req.path.s, + LNM_HTTP_MAX_REGEX_GROUPS, ctx->req.path.groups, 0) == 0; + } + + // Remember the previous match levels + int new_match_level = matched_path + (route->method == ctx->req.method); + match_level = match_level < new_match_level ? new_match_level : match_level; + } + + ctx->route = match_level == 2 ? route : NULL; +} void lnm_http_loop_process_steps(lnm_http_conn *conn) { /* lnm_http_loop_ctx *ctx = conn->ctx; */ From 77b62825a6c2da2837673e76328871f015f48bcf Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Thu, 30 Nov 2023 21:04:13 +0100 Subject: [PATCH 13/45] feat(lnm): more request processing code --- lnm/include/lnm/http/loop.h | 30 +++++-- lnm/include/lnm/http/res.h | 30 +++++++ .../http/{loop_ctx.c => lnm_http_loop_ctx.c} | 0 ...loop_process.c => lnm_http_loop_process.c} | 79 ++++++++++++++++--- lnm/src/http/{req.c => lnm_http_req.c} | 0 5 files changed, 120 insertions(+), 19 deletions(-) create mode 100644 lnm/include/lnm/http/res.h rename lnm/src/http/{loop_ctx.c => lnm_http_loop_ctx.c} (100%) rename lnm/src/http/{loop_process.c => lnm_http_loop_process.c} (51%) rename lnm/src/http/{req.c => lnm_http_req.c} (100%) diff --git a/lnm/include/lnm/http/loop.h b/lnm/include/lnm/http/loop.h index 20d746b..de0edcd 100644 --- a/lnm/include/lnm/http/loop.h +++ b/lnm/include/lnm/http/loop.h @@ -5,6 +5,7 @@ #include "lnm/common.h" #include "lnm/http/req.h" +#include "lnm/http/res.h" typedef struct lnm_loop lnm_http_loop; @@ -14,14 +15,14 @@ typedef struct lnm_http_step lnm_http_step; typedef struct lnm_http_route lnm_http_route; -typedef enum lnm_step_err { - lnm_step_err_done = 0, - lnm_step_err_io_needed, - lnm_step_err_close, - lnm_step_err_res, -} lnm_step_err; +typedef enum lnm_http_step_err { + lnm_http_step_err_done = 0, + lnm_http_step_err_io_needed, + lnm_http_step_err_close, + lnm_http_step_err_res, +} lnm_http_step_err; -typedef lnm_step_err (*lnm_http_step_fn)(lnm_http_conn *conn); +typedef lnm_http_step_err (*lnm_http_step_fn)(lnm_http_conn *conn); typedef lnm_err (*lnm_http_ctx_init_fn)(void **c_ctx, void *gctx); @@ -84,9 +85,22 @@ void lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route); * Represents what state an HTTP loop request is currently in. */ typedef enum lnm_http_loop_state { + // Parse the HTTP request lnm_http_loop_state_parse_req = 0, + // Route the request lnm_http_loop_state_route, + // Parse specific headers (e.g. Content-Length) + lnm_http_loop_state_parse_headers, + // Execute the various steps defined for the route lnm_http_loop_state_steps, + // Write the response status line + lnm_http_loop_state_write_status_line, + // Write the various response headers + lnm_http_loop_state_write_headers, + // Write the request body + lnm_http_loop_state_write_body, + // Clean up the request and reset the state for a next request + lnm_http_loop_state_finish, } lnm_http_loop_state; typedef struct lnm_http_loop_gctx { @@ -103,7 +117,9 @@ typedef struct lnm_http_loop_gctx { typedef struct lnm_http_loop_ctx { lnm_http_loop_state state; lnm_http_req req; + lnm_http_res res; lnm_http_route *route; + lnm_http_step *cur_step; lnm_http_loop_gctx *g; void *c; } lnm_http_loop_ctx; diff --git a/lnm/include/lnm/http/res.h b/lnm/include/lnm/http/res.h new file mode 100644 index 0000000..18ae914 --- /dev/null +++ b/lnm/include/lnm/http/res.h @@ -0,0 +1,30 @@ +#ifndef LNM_HTTP_RES +#define LNM_HTTP_RES + +#include + +#include "lnm/http/consts.h" + +typedef struct lnm_http_res_header { + struct { + char *s; + size_t len; + bool owned; + } name; + struct { + char *s; + size_t len; + bool owned; + } value; + struct lnm_http_res_header *next; +} lnm_http_res_header; + +typedef struct lnm_http_res { + lnm_http_status status; + struct { + lnm_http_res_header *head; + lnm_http_res_header *current; + } headers; +} lnm_http_res; + +#endif diff --git a/lnm/src/http/loop_ctx.c b/lnm/src/http/lnm_http_loop_ctx.c similarity index 100% rename from lnm/src/http/loop_ctx.c rename to lnm/src/http/lnm_http_loop_ctx.c diff --git a/lnm/src/http/loop_process.c b/lnm/src/http/lnm_http_loop_process.c similarity index 51% rename from lnm/src/http/loop_process.c rename to lnm/src/http/lnm_http_loop_process.c index 5568647..5fa4f21 100644 --- a/lnm/src/http/loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -1,12 +1,18 @@ #include #include +#include "lnm/http/consts.h" #include "lnm/http/loop.h" #include "lnm/http/loop_internal.h" #include "lnm/http/req.h" #include "lnm/loop.h" #include "lnm/loop_internal.h" +/* static const lnm_http_loop_state lnm_http_loop_state_first_req = + * lnm_http_loop_state_parse_req; */ +static const lnm_http_loop_state lnm_http_loop_state_first_res = + lnm_http_loop_state_write_headers; + void lnm_http_loop_process_parse_req(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; @@ -16,7 +22,7 @@ void lnm_http_loop_process_parse_req(lnm_http_conn *conn) { switch (res) { case lnm_http_parse_err_ok: conn->r.read += ctx->req.len; - ctx->state = lnm_http_loop_state_steps; + ctx->state = lnm_http_loop_state_route; break; case lnm_http_parse_err_incomplete: // If the request is already the size of the read buffer, we close the @@ -29,8 +35,8 @@ void lnm_http_loop_process_parse_req(lnm_http_conn *conn) { conn->state = lnm_loop_state_end; break; case lnm_http_parse_err_unknown_method: - // TODO set status code here - conn->state = lnm_loop_state_end; + ctx->res.status = lnm_http_status_method_not_implemented; + ctx->state = lnm_http_loop_state_first_res; break; } } @@ -65,31 +71,80 @@ void lnm_http_loop_process_route(lnm_http_conn *conn) { match_level = match_level < new_match_level ? new_match_level : match_level; } - ctx->route = match_level == 2 ? route : NULL; + switch (match_level) { + case 0: + ctx->res.status = lnm_http_status_not_found; + ctx->state = lnm_http_loop_state_first_res; + break; + case 1: + ctx->res.status = lnm_http_status_method_not_allowed; + ctx->state = lnm_http_loop_state_first_res; + break; + case 2: + ctx->route = route; + ctx->cur_step = route->step; + ctx->state = lnm_http_loop_state_parse_headers; + break; + } +} + +void lnm_http_loop_process_parse_headers(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; + + // TODO + + ctx->state = lnm_http_loop_state_steps; } void lnm_http_loop_process_steps(lnm_http_conn *conn) { - /* lnm_http_loop_ctx *ctx = conn->ctx; */ + lnm_http_loop_ctx *ctx = conn->ctx; + lnm_http_step *step; - /* while () */ + do { + step = ctx->cur_step; + + switch (step->fn(conn)) { + case lnm_http_step_err_done: + ctx->cur_step = ctx->cur_step->next; + break; + case lnm_http_step_err_io_needed: + break; + case lnm_http_step_err_close: + conn->state = lnm_loop_state_end; + break; + case lnm_http_step_err_res: + ctx->state = lnm_http_loop_state_first_res; + break; + } + } while ((step != ctx->cur_step) && (ctx->cur_step != NULL)); + + if (ctx->cur_step == NULL) { + ctx->state = lnm_http_loop_state_write_headers; + } } void (*process_fns[])(lnm_http_conn *conn) = { lnm_http_loop_process_parse_req, lnm_http_loop_process_route, + lnm_http_loop_process_parse_headers, lnm_http_loop_process_steps, }; void lnm_http_loop_process(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; - lnm_http_loop_state cur_state; + lnm_http_loop_state http_loop_state; + lnm_loop_state loop_state; - // As long as the processing is able to advance to a next state, we keep - // progressing + // We stop processing if: + // - the event loop state has changed, as we need to switch to the other I/O + // loop + // - the process fn returned without changing the HTTP loop state, indicating + // it's waiting for I/O do { - cur_state = ctx->state; + http_loop_state = ctx->state; + loop_state = conn->state; - process_fns[cur_state](conn); - } while (conn->state == lnm_loop_state_req && cur_state != ctx->state); + process_fns[http_loop_state](conn); + } while ((conn->state == loop_state) && (http_loop_state != ctx->state)); } diff --git a/lnm/src/http/req.c b/lnm/src/http/lnm_http_req.c similarity index 100% rename from lnm/src/http/req.c rename to lnm/src/http/lnm_http_req.c From e8bb089f5ca83b04e936e45b20ecb38fc4a239f5 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Fri, 1 Dec 2023 22:00:49 +0100 Subject: [PATCH 14/45] feat(lnm): add most of the writing code --- lnm/include/lnm/common.h | 11 +++ lnm/include/lnm/http/loop.h | 8 -- lnm/include/lnm/http/res.h | 81 ++++++++++++++++++ lnm/src/http/lnm_http_loop_process.c | 120 ++++++++++++++++++++++++++- lnm/src/http/lnm_http_res.c | 64 ++++++++++++++ 5 files changed, 273 insertions(+), 11 deletions(-) create mode 100644 lnm/src/http/lnm_http_res.c diff --git a/lnm/include/lnm/common.h b/lnm/include/lnm/common.h index 381b4c9..4dc789d 100644 --- a/lnm/include/lnm/common.h +++ b/lnm/include/lnm/common.h @@ -17,6 +17,9 @@ } \ } +#define LNM_MIN(x, y) ((x) < (y) ? (x) : (y)) +#define LNM_MAX(x, y) ((x) > (y) ? (x) : (y)) + typedef enum { lnm_err_ok = 0, lnm_err_failed_alloc, @@ -26,4 +29,12 @@ typedef enum { lnm_err_bad_regex } lnm_err; +typedef struct lnm_loop lnm_http_loop; + +typedef struct lnm_loop_conn lnm_http_conn; + +typedef struct lnm_http_step lnm_http_step; + +typedef struct lnm_http_route lnm_http_route; + #endif diff --git a/lnm/include/lnm/http/loop.h b/lnm/include/lnm/http/loop.h index de0edcd..12ff602 100644 --- a/lnm/include/lnm/http/loop.h +++ b/lnm/include/lnm/http/loop.h @@ -7,14 +7,6 @@ #include "lnm/http/req.h" #include "lnm/http/res.h" -typedef struct lnm_loop lnm_http_loop; - -typedef struct lnm_loop_conn lnm_http_conn; - -typedef struct lnm_http_step lnm_http_step; - -typedef struct lnm_http_route lnm_http_route; - typedef enum lnm_http_step_err { lnm_http_step_err_done = 0, lnm_http_step_err_io_needed, diff --git a/lnm/include/lnm/http/res.h b/lnm/include/lnm/http/res.h index 18ae914..8ea7636 100644 --- a/lnm/include/lnm/http/res.h +++ b/lnm/include/lnm/http/res.h @@ -2,9 +2,17 @@ #define LNM_HTTP_RES #include +#include +#include "lnm/common.h" #include "lnm/http/consts.h" +typedef lnm_err (*data_fn)(size_t *written, char *buf, lnm_http_conn *conn, + size_t offset, size_t len); + +/** + * Linked list elements used to store the response headers + */ typedef struct lnm_http_res_header { struct { char *s; @@ -19,12 +27,85 @@ typedef struct lnm_http_res_header { struct lnm_http_res_header *next; } lnm_http_res_header; +typedef enum lnm_http_res_body_type { + lnm_http_res_body_type_file = 0, + lnm_http_res_body_type_buf, + lnm_http_res_body_type_fn, +} lnm_http_res_body_type; + typedef struct lnm_http_res { lnm_http_status status; struct { lnm_http_res_header *head; lnm_http_res_header *current; } headers; + struct { + struct { + char *buf; + FILE *f; + data_fn fn; + } data; + size_t len; + bool owned; + lnm_http_res_body_type type; + } body; + // General-purpose; meaning depends on the current state + size_t written; } lnm_http_res; +/** + * Add a new header of a known type to the response + * + * @param type type of header + * @param value null-terminated string containing the value of the header + * @param value_owned whether to take ownership of the value pointer; if false, + * free'ing the buffer is the caller's responsibility + */ +lnm_err lnm_http_res_add_header(lnm_http_res *res, lnm_http_header type, + char *value, bool value_owned); + +/** + * Add a new header of a known type to the response with a given value length. + * + * @param type type of header + * @param value string of length `value_len` containing the value of the header + * @param value_len length of value + * @param value_owned whether to take ownership of the value pointer; if false, + * free'ing the buffer is the caller's responsibility + */ +lnm_err lnm_http_res_add_header_len(lnm_http_res *res, lnm_http_header type, + char *value, size_t value_len, + bool value_owned); + +/** + * Set the request body to the given file pointer. + * + * @param res response to modify + * @param f file pointer to use as data + * @param len expected length of the file + * @param owned whether to take ownership of the file pointer + */ +void lnm_http_res_body_set_file(lnm_http_res *res, FILE *f, size_t len, + bool owned); + +/** + * Set the request body to the given buffer. + * + * @param res response to modify + * @param buf buffer to use as data + * @param len length of the buffer + * @param owned whether to take ownership of the file pointer + */ +void lnm_http_res_body_set_buf(lnm_http_res *res, char *buf, size_t len, + bool owned); + +/** + * Set the request body to be read from the given data function. + * + * @param res response to modify + * @param fn data reader function + * @param len expected length of the response + */ +void lnm_http_res_body_set_fn(lnm_http_res *res, data_fn fn, size_t len); + #endif diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index 5fa4f21..c8aed94 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -1,4 +1,5 @@ #include +#include #include #include "lnm/http/consts.h" @@ -116,25 +117,139 @@ void lnm_http_loop_process_steps(lnm_http_conn *conn) { ctx->state = lnm_http_loop_state_first_res; break; } - } while ((step != ctx->cur_step) && (ctx->cur_step != NULL)); + } + // Loop until we either: + // - reach the end of the chain of steps, indicated by NULL + // - have a step that's waiting for I/O + while ((ctx->cur_step != NULL) && (step != ctx->cur_step)); if (ctx->cur_step == NULL) { ctx->state = lnm_http_loop_state_write_headers; } } +// This function is intentionally written inefficiently for now, as it will most +// likely only have to run once for each response +void lnm_http_loop_process_write_status_line(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; + lnm_http_res *res = &ctx->res; + + const char *response_type_name = + lnm_http_status_names[res->status / 100 - 1][res->status % 100]; + + // First we calculate the size of the start of the header + size_t buf_size = + snprintf(NULL, 0, "HTTP/1.1 %i %s\n", res->status, response_type_name); + char buf[buf_size + 1]; + sprintf(buf, "HTTP/1.1 %i %s\n", res->status, response_type_name); + + size_t to_write = + LNM_MIN(buf_size - res->written, LNM_LOOP_BUF_SIZE - conn->w.size); + memcpy(&conn->w.buf[conn->w.size], &buf[res->written], to_write); + + conn->w.size += to_write; + res->written += to_write; + + if (res->written == buf_size) { + res->written = 0; + ctx->state = lnm_http_loop_state_write_headers; + } +} + +void lnm_http_loop_process_write_headers(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; + lnm_http_res *res = &ctx->res; + + lnm_http_res_header *header = res->headers.current; + + // Loop as long as we can still write new data and have headers to write + while ((conn->w.size < LNM_LOOP_BUF_SIZE) && + ((header = res->headers.current) != NULL)) { + size_t buf_len = header->name.len + 2 + header->value.len + 1; + + // Here, we also constantly calculate the entire buffer as we assume each + // header will be written in one go + char buf[buf_len]; + memcpy(buf, header->name.s, header->name.len); + memcpy(&buf[header->name.len + 2], header->value.s, header->value.len); + buf[header->name.len] = ':'; + buf[header->name.len + 1] = ' '; + buf[buf_len - 1] = '\n'; + + size_t to_write = + LNM_MIN(buf_len - res->written, LNM_LOOP_BUF_SIZE - conn->w.size); + memcpy(&conn->w.buf[conn->w.size], &buf[res->written], to_write); + + conn->w.size += to_write; + res->written += to_write; + + if (res->written == buf_len) { + res->written = 0; + res->headers.current = res->headers.current->next; + } + } + + if (res->headers.current == NULL) { + ctx->state = ctx->res.body.len > 0 ? lnm_http_loop_state_write_body + : lnm_http_loop_state_finish; + } +} + +void lnm_http_loop_process_write_body(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; + lnm_http_res *res = &ctx->res; + + size_t to_write = + LNM_MIN(res->body.len - res->written, LNM_LOOP_BUF_SIZE - conn->w.size); + size_t written; + + switch (res->body.type) { + case lnm_http_res_body_type_buf: + memcpy(&conn->w.buf[conn->w.size], &res->body.data.buf[res->written], + to_write); + written = to_write; + break; + case lnm_http_res_body_type_file: + written = fread(&conn->w.buf[conn->w.size], 1, to_write, res->body.data.f); + + if ((written == 0) && (!ferror(res->body.data.f))) { + ctx->state = lnm_http_loop_state_finish; + } + break; + case lnm_http_res_body_type_fn: + if (res->body.data.fn(&written, &conn->w.buf[conn->w.size], conn, + res->written, to_write) != lnm_err_ok) { + ctx->state = lnm_http_loop_state_finish; + } + break; + } + + conn->w.size += written; + res->written += written; + + if (res->written == res->body.len) { + ctx->state = lnm_http_loop_state_finish; + } +} + +void lnm_http_loop_process_finish(lnm_http_conn *conn) {} + void (*process_fns[])(lnm_http_conn *conn) = { lnm_http_loop_process_parse_req, lnm_http_loop_process_route, lnm_http_loop_process_parse_headers, lnm_http_loop_process_steps, + lnm_http_loop_process_write_status_line, + lnm_http_loop_process_write_headers, + lnm_http_loop_process_write_body, + lnm_http_loop_process_finish, }; void lnm_http_loop_process(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_loop_state http_loop_state; - lnm_loop_state loop_state; + lnm_loop_state loop_state = conn->state; // We stop processing if: // - the event loop state has changed, as we need to switch to the other I/O @@ -143,7 +258,6 @@ void lnm_http_loop_process(lnm_http_conn *conn) { // it's waiting for I/O do { http_loop_state = ctx->state; - loop_state = conn->state; process_fns[http_loop_state](conn); } while ((conn->state == loop_state) && (http_loop_state != ctx->state)); diff --git a/lnm/src/http/lnm_http_res.c b/lnm/src/http/lnm_http_res.c new file mode 100644 index 0000000..adf65f0 --- /dev/null +++ b/lnm/src/http/lnm_http_res.c @@ -0,0 +1,64 @@ +#include + +#include "lnm/http/res.h" + +lnm_err lnm_http_res_add_header(lnm_http_res *res, lnm_http_header type, + char *value, bool value_owned) { + return lnm_http_res_add_header_len(res, type, value, strlen(value), + value_owned); +} + +lnm_err lnm_http_res_add_header_len(lnm_http_res *res, lnm_http_header type, + char *value, size_t value_len, + bool value_owned) { + lnm_http_res_header *header = calloc(1, sizeof(lnm_http_res_header)); + + if (header == NULL) { + return lnm_err_failed_alloc; + } + + lnm_http_res_header **next_ptr = &res->headers.head; + + while ((*next_ptr) != NULL) { + next_ptr = &(*next_ptr)->next; + } + + *next_ptr = header; + + // Initialize the current pointer to the head of the linked list + if (res->headers.current == NULL) { + res->headers.current = header; + } + + header->name.s = (char *)lnm_http_header_names[type]; + header->name.len = strlen(lnm_http_header_names[type]); + header->name.owned = false; + + header->value.s = value; + header->value.len = value_len; + header->value.owned = value_owned; + + return lnm_err_ok; +} + +void lnm_http_res_body_set_file(lnm_http_res *res, FILE *f, size_t len, + bool owned) { + res->body.data.f = f; + res->body.len = len; + res->body.owned = owned; + res->body.type = lnm_http_res_body_type_file; +} + +void lnm_http_res_body_set_buf(lnm_http_res *res, char *buf, size_t len, + bool owned) { + res->body.data.buf = buf; + res->body.len = len; + res->body.owned = owned; + res->body.type = lnm_http_res_body_type_buf; +} + +void lnm_http_res_body_set_fn(lnm_http_res *res, data_fn fn, size_t len) { + res->body.data.fn = fn; + res->body.len = len; + res->body.type = lnm_http_res_body_type_fn; +} From 3c1e62330c7f0d65c11a8e1582a3fd597ed076f4 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 2 Dec 2023 13:24:22 +0100 Subject: [PATCH 15/45] feat(lnm): implement context resetting --- lnm/include/lnm/http/req.h | 8 +++++++ lnm/include/lnm/http/res.h | 8 +++++++ lnm/src/http/lnm_http_loop_ctx.c | 10 ++++++-- lnm/src/http/lnm_http_loop_process.c | 17 ++++++++++---- lnm/src/http/lnm_http_req.c | 4 ++++ lnm/src/http/lnm_http_res.c | 35 ++++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 6 deletions(-) diff --git a/lnm/include/lnm/http/req.h b/lnm/include/lnm/http/req.h index def3ad7..1eddb26 100644 --- a/lnm/include/lnm/http/req.h +++ b/lnm/include/lnm/http/req.h @@ -49,4 +49,12 @@ typedef enum lnm_http_parse_err { */ lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, size_t len); +/** + * Reset the given request object, free'ing all its relevant parts and allowing + * it to be reused as a new object. + * + * @param req object to reset + */ +void lnm_http_req_reset(lnm_http_req *req); + #endif diff --git a/lnm/include/lnm/http/res.h b/lnm/include/lnm/http/res.h index 8ea7636..4364597 100644 --- a/lnm/include/lnm/http/res.h +++ b/lnm/include/lnm/http/res.h @@ -108,4 +108,12 @@ void lnm_http_res_body_set_buf(lnm_http_res *res, char *buf, size_t len, */ void lnm_http_res_body_set_fn(lnm_http_res *res, data_fn fn, size_t len); +/** + * Reset the given response object, properly free'ing any allocated buffers, + * allowing it to be reused for later connections. + * + * @param res res to reset + */ +void lnm_http_res_reset(lnm_http_res *res); + #endif diff --git a/lnm/src/http/lnm_http_loop_ctx.c b/lnm/src/http/lnm_http_loop_ctx.c index 64ac8eb..87741e2 100644 --- a/lnm/src/http/lnm_http_loop_ctx.c +++ b/lnm/src/http/lnm_http_loop_ctx.c @@ -38,12 +38,18 @@ lnm_err lnm_http_loop_ctx_init(lnm_http_loop_ctx **out, void lnm_http_loop_ctx_reset(lnm_http_loop_ctx *ctx) { ctx->g->ctx_reset(ctx->c); - // TODO actual reset stuff + + lnm_http_req_reset(&ctx->req); + lnm_http_res_reset(&ctx->res); + + ctx->route = NULL; + ctx->cur_step = NULL; } void lnm_http_loop_ctx_free(lnm_http_loop_ctx *ctx) { + lnm_http_loop_ctx_reset(ctx); + ctx->g->ctx_free(ctx->c); - // TODO actual free stuff free(ctx); } diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index c8aed94..5952538 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -160,7 +160,7 @@ void lnm_http_loop_process_write_headers(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_res *res = &ctx->res; - lnm_http_res_header *header = res->headers.current; + lnm_http_res_header *header; // Loop as long as we can still write new data and have headers to write while ((conn->w.size < LNM_LOOP_BUF_SIZE) && @@ -212,7 +212,7 @@ void lnm_http_loop_process_write_body(lnm_http_conn *conn) { case lnm_http_res_body_type_file: written = fread(&conn->w.buf[conn->w.size], 1, to_write, res->body.data.f); - if ((written == 0) && (!ferror(res->body.data.f))) { + if ((written == 0) && (ferror(res->body.data.f) != 0)) { ctx->state = lnm_http_loop_state_finish; } break; @@ -232,7 +232,16 @@ void lnm_http_loop_process_write_body(lnm_http_conn *conn) { } } -void lnm_http_loop_process_finish(lnm_http_conn *conn) {} +void lnm_http_loop_process_finish(lnm_http_conn *conn) { + // First we ensure the write buffer is fully flushed + if (conn->w.size > 0) { + return; + } + + lnm_http_loop_ctx_reset(conn->ctx); + + conn->state = lnm_loop_state_req; +} void (*process_fns[])(lnm_http_conn *conn) = { lnm_http_loop_process_parse_req, @@ -246,7 +255,7 @@ void (*process_fns[])(lnm_http_conn *conn) = { }; void lnm_http_loop_process(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; + const lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_loop_state http_loop_state; lnm_loop_state loop_state = conn->state; diff --git a/lnm/src/http/lnm_http_req.c b/lnm/src/http/lnm_http_req.c index 2327ce6..33c522c 100644 --- a/lnm/src/http/lnm_http_req.c +++ b/lnm/src/http/lnm_http_req.c @@ -56,3 +56,7 @@ lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, return lnm_http_parse_err_ok; } + +void lnm_http_req_reset(lnm_http_req *req) { + memset(req, 0, sizeof(lnm_http_req)); +} diff --git a/lnm/src/http/lnm_http_res.c b/lnm/src/http/lnm_http_res.c index adf65f0..3f2b0c0 100644 --- a/lnm/src/http/lnm_http_res.c +++ b/lnm/src/http/lnm_http_res.c @@ -62,3 +62,38 @@ void lnm_http_res_body_set_fn(lnm_http_res *res, data_fn fn, size_t len) { res->body.len = len; res->body.type = lnm_http_res_body_type_fn; } + +void lnm_http_res_reset(lnm_http_res *res) { + lnm_http_res_header *header = res->headers.head; + + while (header != NULL) { + lnm_http_res_header *next = header->next; + + if (header->name.owned) { + free(header->name.s); + } + + if (header->value.owned) { + free(header->value.s); + } + + free(header); + + header = next; + } + + if (res->body.owned) { + switch (res->body.type) { + case lnm_http_res_body_type_file: + fclose(res->body.data.f); + break; + case lnm_http_res_body_type_buf: + free(res->body.data.buf); + break; + case lnm_http_res_body_type_fn: + break; + } + } + + memset(res, 0, sizeof(lnm_http_res)); +} From 13ccfef94d52f9fca90aaa6a4ae64c7c135c34d9 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 2 Dec 2023 16:28:52 +0100 Subject: [PATCH 16/45] feat(lnm): implement content-length header parsing --- lnm/include/lnm/common.h | 46 ++++++++++++++++++++++++++-- lnm/include/lnm/http/req.h | 25 +++++++++++++++ lnm/src/http/lnm_http_loop_process.c | 12 +++++++- lnm/src/http/lnm_http_req.c | 25 +++++++++++++++ lnm/src/lnm_utils.c | 45 +++++++++++++++++++++++++++ 5 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 lnm/src/lnm_utils.c diff --git a/lnm/include/lnm/common.h b/lnm/include/lnm/common.h index 4dc789d..9c95d52 100644 --- a/lnm/include/lnm/common.h +++ b/lnm/include/lnm/common.h @@ -1,6 +1,10 @@ #ifndef LNM_COMMON #define LNM_COMMON +#include +#include +#include + #define LNM_RES(x) \ { \ lnm_err res = x; \ @@ -20,13 +24,14 @@ #define LNM_MIN(x, y) ((x) < (y) ? (x) : (y)) #define LNM_MAX(x, y) ((x) > (y) ? (x) : (y)) -typedef enum { +typedef enum lnm_err { lnm_err_ok = 0, lnm_err_failed_alloc, lnm_err_failed_network, lnm_err_failed_poll, lnm_err_not_setup, - lnm_err_bad_regex + lnm_err_bad_regex, + lnm_err_not_found, } lnm_err; typedef struct lnm_loop lnm_http_loop; @@ -37,4 +42,41 @@ typedef struct lnm_http_step lnm_http_step; typedef struct lnm_http_route lnm_http_route; +/** + * Returns whether the two strings are equal. + * + * @param s1 first string to compare + * @param s1_len length of `s1` + * @param s2 second string to compare + * @param s2_len length of `s2` + */ +bool lnm_strneq(const char *s1, size_t s1_len, const char *s2, size_t s2_len); + +/** + * Returns whether the two strings are equal, ignoring capitalisation. + * + * @param s1 first string to compare + * @param s1_len length of `s1` + * @param s2 second string to compare + * @param s2_len length of `s2` + */ +bool lnm_strnieq(const char *s1, size_t s1_len, const char *s2, size_t s2_len); + +/** + * Calculate integer exponentation. + * + * @param base + * @param power + */ +uint64_t lnm_ipow(uint64_t base, uint64_t power); + +/** + * Parse the given string into a number. + * + * @param s string to parse + * @param len length of s + * @return the parsed number, or 0 if the number is invalid + */ +uint64_t lnm_atoi(const char *s, size_t len); + #endif diff --git a/lnm/include/lnm/http/req.h b/lnm/include/lnm/http/req.h index 1eddb26..e43063c 100644 --- a/lnm/include/lnm/http/req.h +++ b/lnm/include/lnm/http/req.h @@ -2,10 +2,12 @@ #define LNM_HTTP_REQ #include +#include #include #include "picohttpparser.h" +#include "lnm/common.h" #include "lnm/http/consts.h" #define LNM_HTTP_MAX_REQ_HEADERS 32 @@ -31,6 +33,7 @@ typedef struct lnm_http_req { struct phr_header arr[LNM_HTTP_MAX_REQ_HEADERS]; size_t len; } headers; + uint64_t content_length; } lnm_http_req; typedef enum lnm_http_parse_err { @@ -57,4 +60,26 @@ lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, size_t len); */ void lnm_http_req_reset(lnm_http_req *req); +/** + * Retrieve a specific header from the request. + * + * @param out where to write pointer to header value + * @param out_len where to store length of out value + * @param req request to look for header in + * @param type type of header to look for + */ +lnm_err lnm_http_req_header_get(const char **out, size_t *out_len, + lnm_http_req *req, lnm_http_header type); + +/** + * Retrieve a specific header from the request by specifying its name. + * + * @param out where to write pointer to header value + * @param out_len where to store length of out value + * @param req request to look for header in + * @param name name of the header; matches case-insensitive + */ +lnm_err lnm_http_req_header_get_s(const char **out, size_t *out_len, + lnm_http_req *req, const char *name); + #endif diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index 5952538..9090df2 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -91,8 +91,14 @@ void lnm_http_loop_process_route(lnm_http_conn *conn) { void lnm_http_loop_process_parse_headers(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; + lnm_http_req *req = &ctx->req; - // TODO + const char *value; + size_t value_len; + if (lnm_http_req_header_get(&value, &value_len, req, + lnm_http_header_content_length) == lnm_err_ok) { + req->content_length = lnm_atoi(value, value_len); + } ctx->state = lnm_http_loop_state_steps; } @@ -134,6 +140,10 @@ void lnm_http_loop_process_write_status_line(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_res *res = &ctx->res; + if (res->status == 0) { + res->status = lnm_http_status_ok; + } + const char *response_type_name = lnm_http_status_names[res->status / 100 - 1][res->status % 100]; diff --git a/lnm/src/http/lnm_http_req.c b/lnm/src/http/lnm_http_req.c index 33c522c..1588bcd 100644 --- a/lnm/src/http/lnm_http_req.c +++ b/lnm/src/http/lnm_http_req.c @@ -1,6 +1,7 @@ #include #include +#include "lnm/common.h" #include "lnm/http/consts.h" #include "lnm/http/loop.h" #include "lnm/http/req.h" @@ -60,3 +61,27 @@ lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, void lnm_http_req_reset(lnm_http_req *req) { memset(req, 0, sizeof(lnm_http_req)); } + +lnm_err lnm_http_req_header_get(const char **out, size_t *out_len, + lnm_http_req *req, lnm_http_header type) { + return lnm_http_req_header_get_s(out, out_len, req, + lnm_http_header_names[type]); +} + +lnm_err lnm_http_req_header_get_s(const char **out, size_t *out_len, + lnm_http_req *req, const char *name) { + size_t name_len = strlen(name); + + for (size_t i = 0; i < req->headers.len; i++) { + const struct phr_header *header = &req->headers.arr[i]; + + if (lnm_strnieq(header->name, header->name_len, name, name_len)) { + *out = header->value; + *out_len = header->value_len; + + return lnm_err_ok; + } + } + + return lnm_err_not_found; +} diff --git a/lnm/src/lnm_utils.c b/lnm/src/lnm_utils.c new file mode 100644 index 0000000..ce60803 --- /dev/null +++ b/lnm/src/lnm_utils.c @@ -0,0 +1,45 @@ +#include +#include + +#include "lnm/common.h" + +bool lnm_strneq(const char *s1, size_t s1_len, const char *s2, size_t s2_len) { + return (s1_len == s2_len) && (memcmp(s1, s2, s1_len) == 0); +} + +bool lnm_strnieq(const char *s1, size_t s1_len, const char *s2, size_t s2_len) { + bool equal = s1_len == s2_len; + + for (size_t i = 0; i < s1_len && equal; i++) { + equal = s1[i] == s2[i] || + (('a' <= s1[i]) && (s1[i] <= 'z') && (s1[i] - 32 == s2[i])); + } + + return equal; +} + +uint64_t lnm_ipow(uint64_t base, uint64_t power) { + uint64_t res = 1; + + while (power > 0) { + res *= base; + power--; + } + + return res; +} + +uint64_t lnm_atoi(const char *s, size_t len) { + uint64_t res = 0; + + for (size_t i = 0; i < len; i++) { + if (s[i] < '0' || '9' < s[i]) { + return 0; + } + + uint64_t val = s[i] - '0'; + res += val * lnm_ipow(10, (len - 1) - i); + } + + return res; +} From 799821d9fc0d8e6d101a11054f4f0760b1e77f18 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 2 Dec 2023 16:45:37 +0100 Subject: [PATCH 17/45] feat(lnm): implement event loop state switching --- lnm/src/http/lnm_http_loop_process.c | 32 +++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index 9090df2..7fb2cf9 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -264,6 +264,25 @@ void (*process_fns[])(lnm_http_conn *conn) = { lnm_http_loop_process_finish, }; +lnm_loop_state state_map[] = { + // parse_req + lnm_loop_state_req, + // route + lnm_loop_state_req, + // parse_headers + lnm_loop_state_req, + // steps + lnm_loop_state_req, + // write_status_line + lnm_loop_state_res, + // write_headers + lnm_loop_state_res, + // write_body + lnm_loop_state_res, + // finish + lnm_loop_state_res, +}; + void lnm_http_loop_process(lnm_http_conn *conn) { const lnm_http_loop_ctx *ctx = conn->ctx; @@ -271,13 +290,20 @@ void lnm_http_loop_process(lnm_http_conn *conn) { lnm_loop_state loop_state = conn->state; // We stop processing if: - // - the event loop state has changed, as we need to switch to the other I/O - // loop + // - the event loop state has been explicitely changed inside the executed + // step, as we need to switch to the other I/O loop + // - the event loop state needs to be changed because the next step should be + // run in another event loop state // - the process fn returned without changing the HTTP loop state, indicating // it's waiting for I/O do { http_loop_state = ctx->state; process_fns[http_loop_state](conn); - } while ((conn->state == loop_state) && (http_loop_state != ctx->state)); + } while ((conn->state == loop_state) && + (conn->state == state_map[loop_state]) && + (http_loop_state != ctx->state)); + + // Check required to prevent overwriting manually set event loop state + conn->state = conn->state == loop_state ? state_map[loop_state] : conn->state; } From 8ec667af3b0eb2773d15fbae356552e8ba0f60b8 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 2 Dec 2023 20:22:05 +0100 Subject: [PATCH 18/45] feat(lander): initial lnm integration test --- Makefile | 8 +++-- config.mk | 6 ++-- include/lander.h | 9 +++-- lnm/include/lnm/common.h | 7 ++++ lnm/include/lnm/http/consts.h | 10 +++--- lnm/include/lnm/http/loop.h | 16 +++++++-- lnm/include/lnm/http/res.h | 4 +-- lnm/src/http/lnm_http_loop.c | 32 ++++++++++++++++- lnm/src/http/lnm_http_loop_process.c | 54 ++++++++++++++++++++++------ lnm/src/http/lnm_http_req.c | 2 ++ lnm/src/lnm_utils.c | 12 +++++++ lnm/src/loop/lnm_loop.c | 5 ++- src/lander/lander.c | 16 +++++++-- src/lander/lander_get.c | 18 ++++++---- src/main.c | 32 +++++++++++++---- 15 files changed, 187 insertions(+), 44 deletions(-) diff --git a/Makefile b/Makefile index f2c448c..d6d9364 100644 --- a/Makefile +++ b/Makefile @@ -39,7 +39,11 @@ objs: $(OBJS) liblsm: $(MAKE) -C lsm -$(BIN): liblsm $(OBJS) +.PHONY: liblnm +liblnm: + $(MAKE) -C lnm + +$(BIN): liblsm liblnm $(OBJS) $(CC) -o $@ $(OBJS) $(_LDFLAGS) $(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c @@ -68,7 +72,7 @@ run: $(BIN) valgrind: $(BIN) LANDER_API_KEY=test \ LANDER_DATA_DIR=data \ - valgrind '$(BUILD_DIR)/$(BIN_FILENAME)' + valgrind --track-origins=yes '$(BUILD_DIR)/$(BIN_FILENAME)' .PHONY: test test: $(TARGETS_TEST) diff --git a/config.mk b/config.mk index da3e0b8..244a7b2 100644 --- a/config.mk +++ b/config.mk @@ -7,9 +7,9 @@ SRC_DIR = src TEST_DIR = test THIRDPARTY_DIR = thirdparty -INC_DIRS = include $(THIRDPARTY_DIR)/include lsm/include -LIBS = m lsm -LIB_DIRS = ./lsm/build +INC_DIRS = include $(THIRDPARTY_DIR)/include lsm/include lnm/include +LIBS = m lsm lnm +LIB_DIRS = ./lsm/build ./lnm/build # -MMD: generate a .d file for every source file. This file can be imported by # make and makes make aware that a header file has been changed, ensuring an diff --git a/include/lander.h b/include/lander.h index a30c32d..7220559 100644 --- a/include/lander.h +++ b/include/lander.h @@ -1,9 +1,12 @@ #ifndef LANDER #define LANDER -#include "http_loop.h" +#include "lnm/common.h" +#include "lnm/http/loop.h" #include "lsm/store.h" +#include "http_loop.h" + extern http_route lander_routes[6]; extern const char lander_key_charset[]; @@ -31,13 +34,13 @@ typedef enum lander_entry_type : uint8_t { void *lander_gctx_init(); -void *lander_ctx_init(); +lnm_err lander_ctx_init(void **c_ctx, void *gctx); void lander_ctx_reset(lander_ctx *ctx); void lander_ctx_free(lander_ctx *ctx); -bool lander_get_index(event_loop_conn *conn); +lnm_http_step_err lander_get_index(lnm_http_conn *conn); bool lander_get_entry(event_loop_conn *conn); diff --git a/lnm/include/lnm/common.h b/lnm/include/lnm/common.h index 9c95d52..8cc982e 100644 --- a/lnm/include/lnm/common.h +++ b/lnm/include/lnm/common.h @@ -79,4 +79,11 @@ uint64_t lnm_ipow(uint64_t base, uint64_t power); */ uint64_t lnm_atoi(const char *s, size_t len); +/** + * Calculate how many base 10 digits the given number consists of. + * + * @param num number to use + */ +uint64_t lnm_digits(uint64_t num); + #endif diff --git a/lnm/include/lnm/http/consts.h b/lnm/include/lnm/http/consts.h index 37f3239..89622a3 100644 --- a/lnm/include/lnm/http/consts.h +++ b/lnm/include/lnm/http/consts.h @@ -7,11 +7,11 @@ extern const char *lnm_http_method_names[]; extern const size_t lnm_http_method_names_len; typedef enum lnm_http_method { - http_method_get = 0, - http_method_post, - http_method_put, - http_method_patch, - http_method_delete + lnm_http_method_get = 0, + lnm_http_method_post, + lnm_http_method_put, + lnm_http_method_patch, + lnm_http_method_delete } lnm_http_method; extern const char *lnm_http_status_names[][32]; diff --git a/lnm/include/lnm/http/loop.h b/lnm/include/lnm/http/loop.h index 12ff602..482560d 100644 --- a/lnm/include/lnm/http/loop.h +++ b/lnm/include/lnm/http/loop.h @@ -32,12 +32,20 @@ lnm_err lnm_http_loop_init(lnm_http_loop **out, void *c_gctx, lnm_http_ctx_reset_fn ctx_reset, lnm_http_ctx_free_fn ctx_free); +/** + * Initialize a new step. + * + * @param out where to store pointer to new `lnm_http_step` + * @param fn step function + */ +lnm_err lnm_http_step_init(lnm_http_step **out, lnm_http_step_fn fn); + /** * Append the given step fn to the step. * * @param out where to store pointer to new `lnm_http_step` * @param step step to append new step to - * @param fn step funcitonn + * @param fn step function */ lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step *step, lnm_http_step_fn fn); @@ -71,7 +79,9 @@ lnm_err lnm_http_route_init_regex(lnm_http_route **out, lnm_http_method method, * @param hl HTTP loop to modify * @param route route to add */ -void lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route); +lnm_err lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route); + +lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port); /** * Represents what state an HTTP loop request is currently in. @@ -85,6 +95,8 @@ typedef enum lnm_http_loop_state { lnm_http_loop_state_parse_headers, // Execute the various steps defined for the route lnm_http_loop_state_steps, + // Add certain automatically added headers + lnm_http_loop_state_add_headers, // Write the response status line lnm_http_loop_state_write_status_line, // Write the various response headers diff --git a/lnm/include/lnm/http/res.h b/lnm/include/lnm/http/res.h index 4364597..b34f5da 100644 --- a/lnm/include/lnm/http/res.h +++ b/lnm/include/lnm/http/res.h @@ -45,12 +45,12 @@ typedef struct lnm_http_res { FILE *f; data_fn fn; } data; - size_t len; + uint64_t len; bool owned; lnm_http_res_body_type type; } body; // General-purpose; meaning depends on the current state - size_t written; + uint64_t written; } lnm_http_res; /** diff --git a/lnm/src/http/lnm_http_loop.c b/lnm/src/http/lnm_http_loop.c index e79d3c3..6cd7d53 100644 --- a/lnm/src/http/lnm_http_loop.c +++ b/lnm/src/http/lnm_http_loop.c @@ -20,6 +20,9 @@ lnm_err lnm_http_loop_init(lnm_http_loop **out, void *c_gctx, free(hl)); hl->data_read = lnm_http_loop_process; + hl->data_write = lnm_http_loop_process; + hl->ctx_init = (lnm_err(*)(void **, void *))lnm_http_loop_ctx_init; + hl->ctx_free = (void (*)(void *))lnm_http_loop_ctx_free; *out = hl; return lnm_err_ok; @@ -42,7 +45,9 @@ lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step *step, lnm_http_step_fn fn) { LNM_RES(lnm_http_step_init(out, fn)); - step->next = *out; + if (step != NULL) { + step->next = *out; + } return lnm_err_ok; } @@ -96,3 +101,28 @@ lnm_err lnm_http_route_init_regex(lnm_http_route **out, lnm_http_method method, return lnm_err_ok; } + +lnm_err lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route) { + lnm_http_loop_gctx *gctx = hl->gctx; + + lnm_http_route **new_routes = + gctx->routes.len > 0 + ? realloc(gctx->routes.arr, + (gctx->routes.len + 1) * sizeof(lnm_http_route *)) + : malloc(sizeof(lnm_http_route *)); + + if (new_routes == NULL) { + return lnm_err_failed_alloc; + } + + new_routes[gctx->routes.len] = route; + gctx->routes.arr = new_routes; + gctx->routes.len++; + + return lnm_err_ok; +} + +lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port) { + LNM_RES(lnm_loop_setup(hl, port)); + return lnm_loop_run(hl); +} diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index 7fb2cf9..32bc840 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -12,7 +12,7 @@ /* static const lnm_http_loop_state lnm_http_loop_state_first_req = * lnm_http_loop_state_parse_req; */ static const lnm_http_loop_state lnm_http_loop_state_first_res = - lnm_http_loop_state_write_headers; + lnm_http_loop_state_write_status_line; void lnm_http_loop_process_parse_req(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; @@ -65,6 +65,7 @@ void lnm_http_loop_process_route(lnm_http_conn *conn) { matched_path = regexec(route->route.regex, ctx->req.path.s, LNM_HTTP_MAX_REGEX_GROUPS, ctx->req.path.groups, 0) == 0; + break; } // Remember the previous match levels @@ -105,9 +106,12 @@ void lnm_http_loop_process_parse_headers(lnm_http_conn *conn) { void lnm_http_loop_process_steps(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; - lnm_http_step *step; + lnm_http_step *step = NULL; - do { + // Loop until we either: + // - reach the end of the chain of steps, indicated by NULL + // - have a step that's waiting for I/O + while ((ctx->cur_step != NULL) && (step != ctx->cur_step)) { step = ctx->cur_step; switch (step->fn(conn)) { @@ -124,16 +128,34 @@ void lnm_http_loop_process_steps(lnm_http_conn *conn) { break; } } - // Loop until we either: - // - reach the end of the chain of steps, indicated by NULL - // - have a step that's waiting for I/O - while ((ctx->cur_step != NULL) && (step != ctx->cur_step)); if (ctx->cur_step == NULL) { - ctx->state = lnm_http_loop_state_write_headers; + ctx->state = lnm_http_loop_state_add_headers; } } +void lnm_http_loop_state_process_add_headers(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; + lnm_http_res *res = &ctx->res; + + if (res->body.len > 0) { + uint64_t digits = lnm_digits(res->body.len); + char *buf = malloc(digits + 1); + + if (buf == NULL) { + conn->state = lnm_loop_state_end; + + return; + } + + sprintf(buf, "%lu", res->body.len); + lnm_http_res_add_header_len(res, lnm_http_header_content_length, buf, + digits, true); + } + + ctx->state = lnm_http_loop_state_write_status_line; +} + // This function is intentionally written inefficiently for now, as it will most // likely only have to run once for each response void lnm_http_loop_process_write_status_line(lnm_http_conn *conn) { @@ -199,7 +221,13 @@ void lnm_http_loop_process_write_headers(lnm_http_conn *conn) { } } - if (res->headers.current == NULL) { + // The headers should end with an additional newline. If there's no space left + // in the write buffer, we don't switch states so we can re-try this write + // later + if ((res->headers.current == NULL) && (conn->w.size < LNM_LOOP_BUF_SIZE)) { + conn->w.buf[conn->w.size] = '\n'; + conn->w.size++; + ctx->state = ctx->res.body.len > 0 ? lnm_http_loop_state_write_body : lnm_http_loop_state_finish; } @@ -258,6 +286,7 @@ void (*process_fns[])(lnm_http_conn *conn) = { lnm_http_loop_process_route, lnm_http_loop_process_parse_headers, lnm_http_loop_process_steps, + lnm_http_loop_state_process_add_headers, lnm_http_loop_process_write_status_line, lnm_http_loop_process_write_headers, lnm_http_loop_process_write_body, @@ -273,6 +302,8 @@ lnm_loop_state state_map[] = { lnm_loop_state_req, // steps lnm_loop_state_req, + // add_headers + lnm_loop_state_req, // write_status_line lnm_loop_state_res, // write_headers @@ -301,9 +332,10 @@ void lnm_http_loop_process(lnm_http_conn *conn) { process_fns[http_loop_state](conn); } while ((conn->state == loop_state) && - (conn->state == state_map[loop_state]) && + (conn->state == state_map[http_loop_state]) && (http_loop_state != ctx->state)); // Check required to prevent overwriting manually set event loop state - conn->state = conn->state == loop_state ? state_map[loop_state] : conn->state; + conn->state = + conn->state == loop_state ? state_map[http_loop_state] : conn->state; } diff --git a/lnm/src/http/lnm_http_req.c b/lnm/src/http/lnm_http_req.c index 1588bcd..ba7cccc 100644 --- a/lnm/src/http/lnm_http_req.c +++ b/lnm/src/http/lnm_http_req.c @@ -37,6 +37,8 @@ lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, return lnm_http_parse_err_unknown_method; } + // Path will always end with a newline, which we can safely set to nul + path[path_len] = '\0'; char *question_mark = strchr(path, '?'); // Only store query if the path doesn't simply end with a question mark diff --git a/lnm/src/lnm_utils.c b/lnm/src/lnm_utils.c index ce60803..85094bd 100644 --- a/lnm/src/lnm_utils.c +++ b/lnm/src/lnm_utils.c @@ -43,3 +43,15 @@ uint64_t lnm_atoi(const char *s, size_t len) { return res; } + +uint64_t lnm_digits(uint64_t num) { + int digits = 1; + + while (num > 9) { + digits++; + + num /= 10; + } + + return digits; +} diff --git a/lnm/src/loop/lnm_loop.c b/lnm/src/loop/lnm_loop.c index c550009..f44c829 100644 --- a/lnm/src/loop/lnm_loop.c +++ b/lnm/src/loop/lnm_loop.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include "lnm/common.h" @@ -43,7 +44,9 @@ lnm_err lnm_loop_accept(lnm_loop *l) { // Append connection to list of connections if ((size_t)conn_fd >= l->conns.len) { lnm_loop_conn **new = - realloc(l->conns.arr, sizeof(lnm_loop_conn *) * (conn_fd + 1)); + l->conns.len == 0 + ? calloc(sizeof(lnm_loop_conn *), conn_fd + 1) + : realloc(l->conns.arr, sizeof(lnm_loop_conn *) * (conn_fd + 1)); if (new == NULL) { close(conn_fd); diff --git a/src/lander/lander.c b/src/lander/lander.c index 5d1c1fe..6d316d2 100644 --- a/src/lander/lander.c +++ b/src/lander/lander.c @@ -1,10 +1,12 @@ #include #include +#include "lnm/common.h" +#include "lsm/store.h" + #include "http/types.h" #include "http_loop.h" #include "lander.h" -#include "lsm/store.h" const char lander_key_charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; @@ -60,7 +62,17 @@ http_route lander_routes[] = { void *lander_gctx_init() { return calloc(1, sizeof(lander_gctx)); } -void *lander_ctx_init() { return calloc(1, sizeof(lander_ctx)); } +lnm_err lander_ctx_init(void **c_ctx, void *gctx) { + lander_ctx *ctx = calloc(1, sizeof(lander_ctx)); + + if (ctx == NULL) { + return lnm_err_failed_alloc; + } + + *c_ctx = ctx; + + return lnm_err_ok; +} void lander_ctx_reset(lander_ctx *ctx) { if (ctx->entry != NULL) { diff --git a/src/lander/lander_get.c b/src/lander/lander_get.c index 102c631..3d05c4d 100644 --- a/src/lander/lander_get.c +++ b/src/lander/lander_get.c @@ -1,5 +1,7 @@ #include +#include "lnm/loop.h" + #include "event_loop.h" #include "http/res.h" #include "http/types.h" @@ -17,14 +19,18 @@ static const char index_page[] = " \n" "\n"; -bool lander_get_index(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; +lnm_http_step_err lander_get_index(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; - http_res_set_body_buf(&ctx->res, index_page, sizeof(index_page) - 1, false); - http_res_set_mime_type(&ctx->res, http_mime_html); + lnm_http_res_body_set_buf(&ctx->res, (char *)index_page, + sizeof(index_page) - 1, false); - conn->state = event_loop_conn_state_res; - return true; + /* http_res_set_body_buf(&ctx->res, index_page, sizeof(index_page) - 1, + * false); */ + /* http_res_set_mime_type(&ctx->res, http_mime_html); */ + + /* conn->state = event_loop_conn_state_res; */ + return lnm_http_step_err_done; } void lander_get_redirect(event_loop_conn *conn) { diff --git a/src/main.c b/src/main.c index 2f52fc1..78ead69 100644 --- a/src/main.c +++ b/src/main.c @@ -2,9 +2,26 @@ #include #include +#include "lnm/http/loop.h" + #include "lander.h" #include "log.h" +lnm_http_loop *loop_init(lander_gctx *gctx) { + lnm_http_loop *hl; + lnm_http_step *step = NULL; + lnm_http_route *route; + lnm_http_loop_init(&hl, gctx, lander_ctx_init, + (lnm_http_ctx_reset_fn)lander_ctx_reset, + (lnm_http_ctx_free_fn)lander_ctx_free); + + lnm_http_step_init(&step, lander_get_index); + lnm_http_route_init_literal(&route, lnm_http_method_get, "/", step); + lnm_http_loop_route_add(hl, route); + + return hl; +} + #define ENV(var, env_var) \ const char *var = getenv(env_var); \ if (var == NULL) { \ @@ -44,12 +61,15 @@ int main() { } info("Store loaded containing %lu entries", lsm_store_size(c_gctx->store)); + lnm_http_loop *hl = loop_init(c_gctx); + lnm_http_loop_run(hl, port); - http_loop *hl = http_loop_init( - lander_routes, sizeof(lander_routes) / sizeof(lander_routes[0]), c_gctx, - lander_ctx_init, (void (*)(void *))lander_ctx_reset, - (void (*)(void *))lander_ctx_free); - http_loop_set_api_key(hl, api_key); + /* http_loop *hl = http_loop_init( */ + /* lander_routes, sizeof(lander_routes) / sizeof(lander_routes[0]), + * c_gctx, */ + /* lander_ctx_init, (void (*)(void *))lander_ctx_reset, */ + /* (void (*)(void *))lander_ctx_free); */ + /* http_loop_set_api_key(hl, api_key); */ - http_loop_run(hl, port); + /* http_loop_run(hl, port); */ } From ad243ea9f5279b1526c92f61567f873a7414d9c9 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Mon, 4 Dec 2023 21:46:10 +0100 Subject: [PATCH 19/45] feat(lnm): better routing detection --- lnm/include/lnm/http/req.h | 3 +++ lnm/include/lnm/http/res.h | 4 ++-- lnm/src/http/lnm_http_loop_process.c | 31 ++++++++++++++-------------- lnm/src/http/lnm_http_req.c | 4 +++- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/lnm/include/lnm/http/req.h b/lnm/include/lnm/http/req.h index e43063c..5d8f46a 100644 --- a/lnm/include/lnm/http/req.h +++ b/lnm/include/lnm/http/req.h @@ -33,6 +33,9 @@ typedef struct lnm_http_req { struct phr_header arr[LNM_HTTP_MAX_REQ_HEADERS]; size_t len; } headers; + struct { + uint64_t expected_len; + } body; uint64_t content_length; } lnm_http_req; diff --git a/lnm/include/lnm/http/res.h b/lnm/include/lnm/http/res.h index b34f5da..ee2a079 100644 --- a/lnm/include/lnm/http/res.h +++ b/lnm/include/lnm/http/res.h @@ -7,8 +7,8 @@ #include "lnm/common.h" #include "lnm/http/consts.h" -typedef lnm_err (*data_fn)(size_t *written, char *buf, lnm_http_conn *conn, - size_t offset, size_t len); +typedef lnm_err (*data_fn)(uint64_t *written, char *buf, lnm_http_conn *conn, + uint64_t offset, uint64_t len); /** * Linked list elements used to store the response headers diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index 32bc840..2a6649a 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -12,7 +12,7 @@ /* static const lnm_http_loop_state lnm_http_loop_state_first_req = * lnm_http_loop_state_parse_req; */ static const lnm_http_loop_state lnm_http_loop_state_first_res = - lnm_http_loop_state_write_status_line; + lnm_http_loop_state_add_headers; void lnm_http_loop_process_parse_req(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; @@ -52,7 +52,7 @@ void lnm_http_loop_process_route(lnm_http_conn *conn) { int match_level = 0; lnm_http_route *route; - for (size_t i = 0; i < gctx->routes.len && match_level < 2; i++) { + for (size_t i = 0; i < gctx->routes.len && match_level < 3; i++) { route = gctx->routes.arr[i]; bool matched_path; @@ -69,20 +69,21 @@ void lnm_http_loop_process_route(lnm_http_conn *conn) { } // Remember the previous match levels - int new_match_level = matched_path + (route->method == ctx->req.method); + int new_match_level = 2 * matched_path + (route->method == ctx->req.method); match_level = match_level < new_match_level ? new_match_level : match_level; } switch (match_level) { case 0: + case 1: ctx->res.status = lnm_http_status_not_found; ctx->state = lnm_http_loop_state_first_res; break; - case 1: + case 2: ctx->res.status = lnm_http_status_method_not_allowed; ctx->state = lnm_http_loop_state_first_res; break; - case 2: + case 3: ctx->route = route; ctx->cur_step = route->step; ctx->state = lnm_http_loop_state_parse_headers; @@ -138,21 +139,19 @@ void lnm_http_loop_state_process_add_headers(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_res *res = &ctx->res; - if (res->body.len > 0) { - uint64_t digits = lnm_digits(res->body.len); - char *buf = malloc(digits + 1); + uint64_t digits = lnm_digits(res->body.len); + char *buf = malloc(digits + 1); - if (buf == NULL) { - conn->state = lnm_loop_state_end; + if (buf == NULL) { + conn->state = lnm_loop_state_end; - return; - } - - sprintf(buf, "%lu", res->body.len); - lnm_http_res_add_header_len(res, lnm_http_header_content_length, buf, - digits, true); + return; } + sprintf(buf, "%lu", res->body.len); + lnm_http_res_add_header_len(res, lnm_http_header_content_length, buf, + digits, true); + ctx->state = lnm_http_loop_state_write_status_line; } diff --git a/lnm/src/http/lnm_http_req.c b/lnm/src/http/lnm_http_req.c index ba7cccc..fa6e1da 100644 --- a/lnm/src/http/lnm_http_req.c +++ b/lnm/src/http/lnm_http_req.c @@ -16,9 +16,11 @@ lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, int req_len = phr_parse_request( buf, len, &method, &method_len, (const char **)&path, &path_len, - &req->minor_version, req->headers.arr, &req->headers.len, 0); + &req->minor_version, req->headers.arr, &req->headers.len, req->len); if (req_len == -1) { + req->len = len; + return lnm_http_parse_err_invalid; } else if (req_len == -2) { return lnm_http_parse_err_incomplete; From f3da5c78efcbb00a9bd3be50fffe72a169892c1c Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Tue, 5 Dec 2023 19:12:19 +0100 Subject: [PATCH 20/45] feat(lander): implement redirect posting using lnm --- include/lander.h | 12 ++-- lnm/include/lnm/http/loop.h | 2 + lnm/include/lnm/http/req.h | 4 +- lnm/src/http/lnm_http_loop_process.c | 6 +- lnm/src/http/lnm_http_loop_steps.c | 24 +++++++ lnm/src/http/lnm_http_req.c | 4 ++ src/lander/lander.c | 10 +-- src/lander/lander_get.c | 96 +++++++++++++++------------- src/lander/lander_post.c | 36 +++++------ src/main.c | 10 +++ 10 files changed, 123 insertions(+), 81 deletions(-) create mode 100644 lnm/src/http/lnm_http_loop_steps.c diff --git a/include/lander.h b/include/lander.h index 7220559..82d41a6 100644 --- a/include/lander.h +++ b/include/lander.h @@ -42,21 +42,19 @@ void lander_ctx_free(lander_ctx *ctx); lnm_http_step_err lander_get_index(lnm_http_conn *conn); -bool lander_get_entry(event_loop_conn *conn); +lnm_http_step_err lander_get_entry(lnm_http_conn *conn); -bool lander_post_redirect(event_loop_conn *conn); +lnm_http_step_err lander_post_redirect(lnm_http_conn *conn); bool lander_post_paste(event_loop_conn *conn); bool lander_post_paste(event_loop_conn *conn); -bool lander_post_redirect(event_loop_conn *conn); - bool lander_stream_body_to_entry(event_loop_conn *conn); bool lander_stream_body_to_client(event_loop_conn *conn); -bool lander_post_redirect_body_to_attr(event_loop_conn *conn); +lnm_http_step_err lander_post_redirect_body_to_attr(lnm_http_conn *conn); bool lander_remove_entry(event_loop_conn *conn); @@ -71,7 +69,7 @@ void lander_header_to_attr(http_loop_ctx *ctx, const char *header, /** * Store the attribute's value as the provided header, if present. */ -void lander_attr_to_header(http_loop_ctx *ctx, lander_attr_type attr_type, - http_header header_type); +void lander_attr_to_header(lnm_http_loop_ctx *ctx, lander_attr_type attr_type, + lnm_http_header header_type); #endif diff --git a/lnm/include/lnm/http/loop.h b/lnm/include/lnm/http/loop.h index 482560d..867a93e 100644 --- a/lnm/include/lnm/http/loop.h +++ b/lnm/include/lnm/http/loop.h @@ -128,4 +128,6 @@ typedef struct lnm_http_loop_ctx { void *c; } lnm_http_loop_ctx; +lnm_http_step_err lnm_http_loop_step_body_to_buf(lnm_http_conn *conn); + #endif diff --git a/lnm/include/lnm/http/req.h b/lnm/include/lnm/http/req.h index 5d8f46a..e40d388 100644 --- a/lnm/include/lnm/http/req.h +++ b/lnm/include/lnm/http/req.h @@ -35,8 +35,10 @@ typedef struct lnm_http_req { } headers; struct { uint64_t expected_len; + uint64_t len; + char *buf; + bool owned; } body; - uint64_t content_length; } lnm_http_req; typedef enum lnm_http_parse_err { diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index 2a6649a..8122058 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -99,7 +99,7 @@ void lnm_http_loop_process_parse_headers(lnm_http_conn *conn) { size_t value_len; if (lnm_http_req_header_get(&value, &value_len, req, lnm_http_header_content_length) == lnm_err_ok) { - req->content_length = lnm_atoi(value, value_len); + req->body.expected_len = lnm_atoi(value, value_len); } ctx->state = lnm_http_loop_state_steps; @@ -149,8 +149,8 @@ void lnm_http_loop_state_process_add_headers(lnm_http_conn *conn) { } sprintf(buf, "%lu", res->body.len); - lnm_http_res_add_header_len(res, lnm_http_header_content_length, buf, - digits, true); + lnm_http_res_add_header_len(res, lnm_http_header_content_length, buf, digits, + true); ctx->state = lnm_http_loop_state_write_status_line; } diff --git a/lnm/src/http/lnm_http_loop_steps.c b/lnm/src/http/lnm_http_loop_steps.c new file mode 100644 index 0000000..fde3d70 --- /dev/null +++ b/lnm/src/http/lnm_http_loop_steps.c @@ -0,0 +1,24 @@ +#include + +#include "lnm/http/loop.h" +#include "lnm/loop.h" + +lnm_http_step_err lnm_http_loop_step_body_to_buf(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; + + if (ctx->req.body.buf == NULL) { + ctx->req.body.buf = malloc(ctx->req.body.expected_len * sizeof(char)); + ctx->req.body.len = 0; + } + + size_t to_read = LNM_MIN(conn->r.size - conn->r.read, + ctx->req.body.expected_len - ctx->req.body.len); + memcpy(&ctx->req.body.buf[ctx->req.body.len], &conn->r.buf[conn->r.read], + to_read); + ctx->req.body.len += to_read; + conn->r.read += to_read; + + return ctx->req.body.len == ctx->req.body.expected_len + ? lnm_http_step_err_done + : lnm_http_step_err_io_needed; +} diff --git a/lnm/src/http/lnm_http_req.c b/lnm/src/http/lnm_http_req.c index fa6e1da..a601e32 100644 --- a/lnm/src/http/lnm_http_req.c +++ b/lnm/src/http/lnm_http_req.c @@ -63,6 +63,10 @@ lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, } void lnm_http_req_reset(lnm_http_req *req) { + if (req->body.owned) { + free(req->body.buf); + } + memset(req, 0, sizeof(lnm_http_req)); } diff --git a/src/lander/lander.c b/src/lander/lander.c index 6d316d2..471c280 100644 --- a/src/lander/lander.c +++ b/src/lander/lander.c @@ -104,16 +104,12 @@ void lander_header_to_attr(http_loop_ctx *ctx, const char *header_name, } } -void lander_attr_to_header(http_loop_ctx *ctx, lander_attr_type attr_type, - http_header header_type) { +void lander_attr_to_header(lnm_http_loop_ctx *ctx, lander_attr_type attr_type, + lnm_http_header header_type) { lander_ctx *c_ctx = ctx->c; lsm_str *value; if (lsm_entry_attr_get(&value, c_ctx->entry, attr_type) == lsm_error_ok) { - char *buf = malloc(lsm_str_len(value) + 1); - memcpy(buf, lsm_str_ptr(value), lsm_str_len(value)); - buf[lsm_str_len(value)] = '\0'; - - http_res_add_header(&ctx->res, header_type, buf, true); + lnm_http_res_add_header_len(&ctx->res, header_type, (char *)lsm_str_ptr(value), lsm_str_len(value), false); } } diff --git a/src/lander/lander_get.c b/src/lander/lander_get.c index 3d05c4d..66ea77a 100644 --- a/src/lander/lander_get.c +++ b/src/lander/lander_get.c @@ -1,5 +1,7 @@ #include +#include "lnm/http/consts.h" +#include "lnm/http/loop.h" #include "lnm/loop.h" #include "event_loop.h" @@ -24,17 +26,13 @@ lnm_http_step_err lander_get_index(lnm_http_conn *conn) { lnm_http_res_body_set_buf(&ctx->res, (char *)index_page, sizeof(index_page) - 1, false); + lnm_http_res_add_header(&ctx->res, lnm_http_header_content_type, "text/html", false); - /* http_res_set_body_buf(&ctx->res, index_page, sizeof(index_page) - 1, - * false); */ - /* http_res_set_mime_type(&ctx->res, http_mime_html); */ - - /* conn->state = event_loop_conn_state_res; */ return lnm_http_step_err_done; } -void lander_get_redirect(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; +lnm_http_step_err lander_get_redirect(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; // For redirects, the URL is stored as an in-memory attribute @@ -45,43 +43,49 @@ void lander_get_redirect(event_loop_conn *conn) { lsm_error_ok) { error("Entry of type redirect detected without URL attribute"); - ctx->res.status = http_internal_server_error; + ctx->res.status = lnm_http_status_internal_server_error; lsm_entry_close(c_ctx->entry); c_ctx->entry = NULL; - return; + return lnm_http_step_err_res; } - char *buf = malloc(lsm_str_len(url_attr_val) + 1); - memcpy(buf, lsm_str_ptr(url_attr_val), lsm_str_len(url_attr_val)); + lnm_http_res_add_header_len(&ctx->res, lnm_http_header_location, (char *)lsm_str_ptr(url_attr_val), lsm_str_len(url_attr_val), false); - buf[lsm_str_len(url_attr_val)] = '\0'; + ctx->res.status = lnm_http_status_moved_permanently; - ctx->res.status = http_moved_permanently; - http_res_add_header(&ctx->res, http_header_location, buf, true); - - // We no longer need the entry at this point, so we can unlock it early - // This will also signal to the response code not to read any data from - // the entry - lsm_entry_close(c_ctx->entry); - c_ctx->entry = NULL; + return lnm_http_step_err_done; } -void lander_get_paste(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; +lnm_err lander_entry_data_streamer(uint64_t *written, char *buf, lnm_http_conn *conn, + uint64_t offset, uint64_t len) { + // TODO respect offset variable + + lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; - ctx->res.body.expected_len = lsm_entry_data_len(c_ctx->entry); - http_res_set_mime_type(&ctx->res, http_mime_txt); + lsm_entry_data_read(written, buf, c_ctx->entry, len); + + return lnm_err_ok; } -void lander_get_file(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; +lnm_http_step_err lander_get_paste(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; - ctx->res.body.expected_len = lsm_entry_data_len(c_ctx->entry); + lnm_http_res_body_set_fn(&ctx->res, lander_entry_data_streamer, lsm_entry_data_len(c_ctx->entry)); + lnm_http_res_add_header(&ctx->res, lnm_http_header_content_type, "text/plain", false); + + return lnm_http_step_err_done; +} + +lnm_http_step_err lander_get_file(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; + lander_ctx *c_ctx = ctx->c; + + lnm_http_res_body_set_fn(&ctx->res, lander_entry_data_streamer, lsm_entry_data_len(c_ctx->entry)); lander_attr_to_header(ctx, lander_attr_type_content_type, - http_header_content_type); + lnm_http_header_content_type); lsm_str *value; char *buf; @@ -90,23 +94,25 @@ void lander_get_file(event_loop_conn *conn) { lsm_error_ok) { buf = malloc(24 + lsm_str_len(value)); int len = lsm_str_len(value); - sprintf(buf, "attachment; filename=\"%*s\"", len, lsm_str_ptr(value)); + sprintf(buf, "attachment; filename=\"%.*s\"", len, lsm_str_ptr(value)); } else { buf = malloc(11); strcpy(buf, "attachment"); } - http_res_add_header(&ctx->res, http_header_content_disposition, buf, true); + lnm_http_res_add_header(&ctx->res, lnm_http_header_content_disposition, buf, true); + + return lnm_http_step_err_done; } -bool lander_get_entry(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; +lnm_http_step_err lander_get_entry(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; - http_loop_gctx *gctx = ctx->g; + lnm_http_loop_gctx *gctx = ctx->g; lander_gctx *c_gctx = gctx->c; - const char *key_s = &ctx->req.path[ctx->req.regex_groups[1].rm_so]; - int key_len = ctx->req.regex_groups[1].rm_eo - ctx->req.regex_groups[1].rm_so; + const char *key_s = &ctx->req.path.s[ctx->req.path.groups[1].rm_so]; + int key_len = ctx->req.path.groups[1].rm_eo - ctx->req.path.groups[1].rm_so; lsm_str *key; lsm_str_init_copy_n(&key, (char *)key_s, key_len); @@ -115,32 +121,32 @@ bool lander_get_entry(event_loop_conn *conn) { case lsm_error_ok: break; case lsm_error_not_found: - ctx->res.status = http_not_found; - conn->state = event_loop_conn_state_res; - return true; + ctx->res.status = lnm_http_status_not_found; + return lnm_http_step_err_res; default: - ctx->res.status = http_internal_server_error; - conn->state = event_loop_conn_state_res; - return true; + ctx->res.status = lnm_http_status_internal_server_error; + return lnm_http_step_err_res; } lander_entry_type t; lsm_entry_attr_get_uint8_t((uint8_t *)&t, c_ctx->entry, lander_attr_type_entry_type); + lnm_http_step_err res; + switch (t) { case lander_entry_type_redirect: - lander_get_redirect(conn); + res = lander_get_redirect(conn); break; case lander_entry_type_paste: - lander_get_paste(conn); + res = lander_get_paste(conn); break; case lander_entry_type_file: - lander_get_file(conn); + res = lander_get_file(conn); break; } - return true; + return res; } bool lander_stream_body_to_client(event_loop_conn *conn) { diff --git a/src/lander/lander_post.c b/src/lander/lander_post.c index 9711d03..5800737 100644 --- a/src/lander/lander_post.c +++ b/src/lander/lander_post.c @@ -3,6 +3,7 @@ #include "lander.h" #include "log.h" #include "lsm/store.h" +#include "lnm/loop.h" static void randomize_key(char *key, int len) { size_t charset_len = strlen(lander_key_charset); @@ -19,26 +20,26 @@ static void randomize_key(char *key, int len) { * * @return true on success, false otherwise */ -bool lander_insert_entry(http_loop_ctx *ctx) { - http_loop_gctx *gctx = ctx->g; +bool lander_insert_entry(lnm_http_loop_ctx *ctx) { + lnm_http_loop_gctx *gctx = ctx->g; lander_gctx *c_gctx = gctx->c; lander_ctx *c_ctx = ctx->c; lsm_str *key; int key_len; - if (ctx->req.regex_groups[2].rm_eo == ctx->req.regex_groups[2].rm_so) { + if (ctx->req.path.groups[2].rm_eo == ctx->req.path.groups[2].rm_so) { // Generate a random key to insert bool secure = - (ctx->req.regex_groups[1].rm_eo - ctx->req.regex_groups[1].rm_so) == 1; + (ctx->req.path.groups[1].rm_eo - ctx->req.path.groups[1].rm_so) == 1; key_len = secure ? 16 : 4; char *key_s = malloc((key_len + 1) * sizeof(char)); randomize_key(key_s, key_len); lsm_str_init(&key, key_s); } else { - const char *key_s = &ctx->req.path[ctx->req.regex_groups[2].rm_so]; - key_len = ctx->req.regex_groups[2].rm_eo - ctx->req.regex_groups[2].rm_so; + const char *key_s = &ctx->req.path.s[ctx->req.path.groups[2].rm_so]; + key_len = ctx->req.path.groups[2].rm_eo - ctx->req.path.groups[2].rm_so; lsm_str_init_copy_n(&key, key_s, key_len); } @@ -46,12 +47,12 @@ bool lander_insert_entry(http_loop_ctx *ctx) { // TODO free key on error switch (lsm_store_insert(&c_ctx->entry, c_gctx->store, key)) { case lsm_error_already_present: - ctx->res.status = http_conflict; + ctx->res.status = lnm_http_status_conflict; return false; case lsm_error_ok: break; default: - ctx->res.status = http_internal_server_error; + ctx->res.status = lnm_http_status_internal_server_error; return false; } @@ -61,36 +62,35 @@ bool lander_insert_entry(http_loop_ctx *ctx) { buf[0] = '/'; buf[key_len + 1] = '\0'; - http_res_add_header(&ctx->res, http_header_location, buf, true); - ctx->res.status = http_created; + lnm_http_res_add_header(&ctx->res, lnm_http_header_location, buf, true); + ctx->res.status = lnm_http_status_created; return true; } -bool lander_post_redirect(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; +lnm_http_step_err lander_post_redirect(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; if (!lander_insert_entry(ctx)) { - conn->state = event_loop_conn_state_res; - return true; + return lnm_http_step_err_res; } lsm_entry_attr_insert_uint8_t(c_ctx->entry, lander_attr_type_entry_type, lander_entry_type_redirect); - return true; + return lnm_http_step_err_done; } -bool lander_post_redirect_body_to_attr(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; +lnm_http_step_err lander_post_redirect_body_to_attr(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; lsm_str *attr_value; lsm_str_init_copy_n(&attr_value, ctx->req.body.buf, ctx->req.body.len); lsm_entry_attr_insert(c_ctx->entry, lander_attr_type_url, attr_value); - return true; + return lnm_http_step_err_done; } bool lander_post_paste(event_loop_conn *conn) { diff --git a/src/main.c b/src/main.c index 78ead69..21e8a82 100644 --- a/src/main.c +++ b/src/main.c @@ -19,6 +19,16 @@ lnm_http_loop *loop_init(lander_gctx *gctx) { lnm_http_route_init_literal(&route, lnm_http_method_get, "/", step); lnm_http_loop_route_add(hl, route); + lnm_http_step_init(&step, lander_get_entry); + lnm_http_route_init_regex(&route, lnm_http_method_get, "^/([^/]+)$", 1, step); + lnm_http_loop_route_add(hl, route); + + lnm_http_step_init(&step, lander_post_redirect); + lnm_http_route_init_regex(&route, lnm_http_method_post, "^/s(l?)/([^/]*)$", 2, step); + lnm_http_step_append(&step, step, lnm_http_loop_step_body_to_buf); + lnm_http_step_append(&step, step, lander_post_redirect_body_to_attr); + lnm_http_loop_route_add(hl, route); + return hl; } From 8ae59f1031f5ad439e462f0de3da1144afa0d095 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Tue, 5 Dec 2023 19:36:54 +0100 Subject: [PATCH 21/45] feat(lander): full initial migration to lnm --- include/lander.h | 12 +++++------- src/lander/lander.c | 24 +++++++++++------------- src/lander/lander_delete.c | 25 +++++++++++++------------ src/lander/lander_get.c | 26 +++++++++++++++++--------- src/lander/lander_post.c | 20 +++++++++----------- src/lander/lander_steps.c | 16 ++++++++++------ src/main.c | 20 +++++++++++++++++++- 7 files changed, 84 insertions(+), 59 deletions(-) diff --git a/include/lander.h b/include/lander.h index 82d41a6..c61b7b6 100644 --- a/include/lander.h +++ b/include/lander.h @@ -46,24 +46,22 @@ lnm_http_step_err lander_get_entry(lnm_http_conn *conn); lnm_http_step_err lander_post_redirect(lnm_http_conn *conn); -bool lander_post_paste(event_loop_conn *conn); +lnm_http_step_err lander_post_paste(lnm_http_conn *conn); -bool lander_post_paste(event_loop_conn *conn); - -bool lander_stream_body_to_entry(event_loop_conn *conn); +lnm_http_step_err lander_stream_body_to_entry(lnm_http_conn *conn); bool lander_stream_body_to_client(event_loop_conn *conn); lnm_http_step_err lander_post_redirect_body_to_attr(lnm_http_conn *conn); -bool lander_remove_entry(event_loop_conn *conn); +lnm_http_step_err lander_remove_entry(lnm_http_conn *conn); -bool lander_post_file(event_loop_conn *conn); +lnm_http_step_err lander_post_file(lnm_http_conn *conn); /** * Store the requested header as an attribute, if it's present. */ -void lander_header_to_attr(http_loop_ctx *ctx, const char *header, +void lander_header_to_attr(lnm_http_loop_ctx *ctx, const char *header, lander_attr_type attr_type); /** diff --git a/src/lander/lander.c b/src/lander/lander.c index 471c280..36fd6cd 100644 --- a/src/lander/lander.c +++ b/src/lander/lander.c @@ -84,23 +84,19 @@ void lander_ctx_reset(lander_ctx *ctx) { void lander_ctx_free(lander_ctx *ctx) { free(ctx); } -void lander_header_to_attr(http_loop_ctx *ctx, const char *header_name, +void lander_header_to_attr(lnm_http_loop_ctx *ctx, const char *header_name, lander_attr_type attr_type) { lander_ctx *c_ctx = ctx->c; - for (size_t i = 0; i < ctx->req.num_headers; i++) { - const struct phr_header *header = &ctx->req.headers[i]; + const char *header_value; + size_t header_value_len; - if (strncmp(header->name, header_name, header->name_len) == 0) { - if (header->value_len > 0) { - lsm_str *value; - lsm_str_init_copy_n(&value, (char *)header->value, header->value_len); + if (lnm_http_req_header_get_s(&header_value, &header_value_len, &ctx->req, + header_name) == lnm_err_ok) { + lsm_str *value; + lsm_str_init_copy_n(&value, (char *)header_value, header_value_len); - lsm_entry_attr_insert(c_ctx->entry, attr_type, value); - } - - return; - } + lsm_entry_attr_insert(c_ctx->entry, attr_type, value); } } @@ -110,6 +106,8 @@ void lander_attr_to_header(lnm_http_loop_ctx *ctx, lander_attr_type attr_type, lsm_str *value; if (lsm_entry_attr_get(&value, c_ctx->entry, attr_type) == lsm_error_ok) { - lnm_http_res_add_header_len(&ctx->res, header_type, (char *)lsm_str_ptr(value), lsm_str_len(value), false); + lnm_http_res_add_header_len(&ctx->res, header_type, + (char *)lsm_str_ptr(value), lsm_str_len(value), + false); } } diff --git a/src/lander/lander_delete.c b/src/lander/lander_delete.c index e91b6c9..349fac6 100644 --- a/src/lander/lander_delete.c +++ b/src/lander/lander_delete.c @@ -1,29 +1,30 @@ +#include "lnm/loop.h" + #include "lander.h" -bool lander_remove_entry(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; +lnm_http_step_err lander_remove_entry(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; - http_loop_gctx *gctx = ctx->g; + lnm_http_loop_gctx *gctx = ctx->g; lander_gctx *c_gctx = gctx->c; - const char *key_s = &ctx->req.path[ctx->req.regex_groups[1].rm_so]; - int key_len = ctx->req.regex_groups[1].rm_eo - ctx->req.regex_groups[1].rm_so; + const char *key_s = &ctx->req.path.s[ctx->req.path.groups[1].rm_so]; + int key_len = ctx->req.path.groups[1].rm_eo - ctx->req.path.groups[1].rm_so; lsm_str *key; lsm_str_init_copy_n(&key, (char *)key_s, key_len); switch (lsm_store_open_write(&c_ctx->entry, c_gctx->store, key)) { case lsm_error_ok: + lsm_entry_remove(c_ctx->entry); break; case lsm_error_not_found: - ctx->res.status = http_not_found; - return true; + ctx->res.status = lnm_http_status_not_found; + break; default: - ctx->res.status = http_internal_server_error; - return true; + ctx->res.status = lnm_http_status_internal_server_error; + break; } - lsm_entry_remove(c_ctx->entry); - - return true; + return lnm_http_step_err_done; } diff --git a/src/lander/lander_get.c b/src/lander/lander_get.c index 66ea77a..1bf4da0 100644 --- a/src/lander/lander_get.c +++ b/src/lander/lander_get.c @@ -26,7 +26,8 @@ lnm_http_step_err lander_get_index(lnm_http_conn *conn) { lnm_http_res_body_set_buf(&ctx->res, (char *)index_page, sizeof(index_page) - 1, false); - lnm_http_res_add_header(&ctx->res, lnm_http_header_content_type, "text/html", false); + lnm_http_res_add_header(&ctx->res, lnm_http_header_content_type, "text/html", + false); return lnm_http_step_err_done; } @@ -50,17 +51,20 @@ lnm_http_step_err lander_get_redirect(lnm_http_conn *conn) { return lnm_http_step_err_res; } - lnm_http_res_add_header_len(&ctx->res, lnm_http_header_location, (char *)lsm_str_ptr(url_attr_val), lsm_str_len(url_attr_val), false); + lnm_http_res_add_header_len(&ctx->res, lnm_http_header_location, + (char *)lsm_str_ptr(url_attr_val), + lsm_str_len(url_attr_val), false); ctx->res.status = lnm_http_status_moved_permanently; return lnm_http_step_err_done; } -lnm_err lander_entry_data_streamer(uint64_t *written, char *buf, lnm_http_conn *conn, - uint64_t offset, uint64_t len) { +lnm_err lander_entry_data_streamer(uint64_t *written, char *buf, + lnm_http_conn *conn, uint64_t offset, + uint64_t len) { // TODO respect offset variable - + lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; @@ -73,8 +77,10 @@ lnm_http_step_err lander_get_paste(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; - lnm_http_res_body_set_fn(&ctx->res, lander_entry_data_streamer, lsm_entry_data_len(c_ctx->entry)); - lnm_http_res_add_header(&ctx->res, lnm_http_header_content_type, "text/plain", false); + lnm_http_res_body_set_fn(&ctx->res, lander_entry_data_streamer, + lsm_entry_data_len(c_ctx->entry)); + lnm_http_res_add_header(&ctx->res, lnm_http_header_content_type, "text/plain", + false); return lnm_http_step_err_done; } @@ -83,7 +89,8 @@ lnm_http_step_err lander_get_file(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; - lnm_http_res_body_set_fn(&ctx->res, lander_entry_data_streamer, lsm_entry_data_len(c_ctx->entry)); + lnm_http_res_body_set_fn(&ctx->res, lander_entry_data_streamer, + lsm_entry_data_len(c_ctx->entry)); lander_attr_to_header(ctx, lander_attr_type_content_type, lnm_http_header_content_type); @@ -100,7 +107,8 @@ lnm_http_step_err lander_get_file(lnm_http_conn *conn) { strcpy(buf, "attachment"); } - lnm_http_res_add_header(&ctx->res, lnm_http_header_content_disposition, buf, true); + lnm_http_res_add_header(&ctx->res, lnm_http_header_content_disposition, buf, + true); return lnm_http_step_err_done; } diff --git a/src/lander/lander_post.c b/src/lander/lander_post.c index 5800737..882cb04 100644 --- a/src/lander/lander_post.c +++ b/src/lander/lander_post.c @@ -1,9 +1,9 @@ #include "http/res.h" #include "http/types.h" #include "lander.h" +#include "lnm/loop.h" #include "log.h" #include "lsm/store.h" -#include "lnm/loop.h" static void randomize_key(char *key, int len) { size_t charset_len = strlen(lander_key_charset); @@ -93,29 +93,27 @@ lnm_http_step_err lander_post_redirect_body_to_attr(lnm_http_conn *conn) { return lnm_http_step_err_done; } -bool lander_post_paste(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; +lnm_http_step_err lander_post_paste(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; if (!lander_insert_entry(ctx)) { - conn->state = event_loop_conn_state_res; - return true; + return lnm_http_step_err_res; } lsm_entry_attr_insert_uint8_t(c_ctx->entry, lander_attr_type_entry_type, lander_entry_type_paste); lander_header_to_attr(ctx, "X-Lander-Filename", lander_attr_type_file_name); - return true; + return lnm_http_step_err_done; } -bool lander_post_file(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; +lnm_http_step_err lander_post_file(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; if (!lander_insert_entry(ctx)) { - conn->state = event_loop_conn_state_res; - return true; + return lnm_http_step_err_res; } lsm_entry_attr_insert_uint8_t(c_ctx->entry, lander_attr_type_entry_type, @@ -124,5 +122,5 @@ bool lander_post_file(event_loop_conn *conn) { lander_attr_type_content_type); lander_header_to_attr(ctx, "X-Lander-Filename", lander_attr_type_file_name); - return true; + return lnm_http_step_err_done; } diff --git a/src/lander/lander_steps.c b/src/lander/lander_steps.c index 7804df5..6021dbd 100644 --- a/src/lander/lander_steps.c +++ b/src/lander/lander_steps.c @@ -1,22 +1,26 @@ #include #include "lander.h" +#include "lnm/http/loop.h" +#include "lnm/loop.h" -bool lander_stream_body_to_entry(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; +lnm_http_step_err lander_stream_body_to_entry(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; uint64_t to_append = - MIN(conn->rbuf_size - conn->rbuf_read, + MIN(conn->r.size - conn->r.read, ctx->req.body.expected_len - lsm_entry_data_len(c_ctx->entry)); lsm_str *data; - lsm_str_init_copy_n(&data, (char *)&conn->rbuf[conn->rbuf_read], to_append); + lsm_str_init_copy_n(&data, (char *)&conn->r.buf[conn->r.read], to_append); lsm_entry_data_append(c_ctx->entry, data); - conn->rbuf_read += to_append; + conn->r.read += to_append; lsm_str_free(data); - return lsm_entry_data_len(c_ctx->entry) == ctx->req.body.expected_len; + return lsm_entry_data_len(c_ctx->entry) == ctx->req.body.expected_len + ? lnm_http_step_err_done + : lnm_http_step_err_io_needed; } diff --git a/src/main.c b/src/main.c index 21e8a82..3af1d2f 100644 --- a/src/main.c +++ b/src/main.c @@ -24,11 +24,29 @@ lnm_http_loop *loop_init(lander_gctx *gctx) { lnm_http_loop_route_add(hl, route); lnm_http_step_init(&step, lander_post_redirect); - lnm_http_route_init_regex(&route, lnm_http_method_post, "^/s(l?)/([^/]*)$", 2, step); + lnm_http_route_init_regex(&route, lnm_http_method_post, "^/s(l?)/([^/]*)$", 2, + step); lnm_http_step_append(&step, step, lnm_http_loop_step_body_to_buf); lnm_http_step_append(&step, step, lander_post_redirect_body_to_attr); lnm_http_loop_route_add(hl, route); + lnm_http_step_init(&step, lander_post_paste); + lnm_http_route_init_regex(&route, lnm_http_method_post, "^/p(l?)/([^/]*)$", 2, + step); + lnm_http_step_append(&step, step, lander_stream_body_to_entry); + lnm_http_loop_route_add(hl, route); + + lnm_http_step_init(&step, lander_post_file); + lnm_http_route_init_regex(&route, lnm_http_method_post, "^/f(l?)/([^/]*)$", 2, + step); + lnm_http_step_append(&step, step, lander_stream_body_to_entry); + lnm_http_loop_route_add(hl, route); + + lnm_http_step_init(&step, lander_remove_entry); + lnm_http_route_init_regex(&route, lnm_http_method_delete, "^/([^/]+)$", 1, + step); + lnm_http_loop_route_add(hl, route); + return hl; } From 876e5a7de4b02a498b3d73cf092ba849bf7faced Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Tue, 5 Dec 2023 20:21:28 +0100 Subject: [PATCH 22/45] fix(lnm): some small bugs --- lnm/src/http/lnm_http_loop_process.c | 5 +++-- lnm/src/loop/lnm_loop.c | 13 +++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index 8122058..791393e 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -275,9 +275,10 @@ void lnm_http_loop_process_finish(lnm_http_conn *conn) { return; } - lnm_http_loop_ctx_reset(conn->ctx); + lnm_http_loop_ctx *ctx = conn->ctx; + lnm_http_loop_ctx_reset(ctx); - conn->state = lnm_loop_state_req; + ctx->state = lnm_http_loop_state_parse_req; } void (*process_fns[])(lnm_http_conn *conn) = { diff --git a/lnm/src/loop/lnm_loop.c b/lnm/src/loop/lnm_loop.c index f44c829..a412b23 100644 --- a/lnm/src/loop/lnm_loop.c +++ b/lnm/src/loop/lnm_loop.c @@ -3,6 +3,7 @@ #include #include #include +#include #include "lnm/common.h" #include "lnm/loop_internal.h" @@ -43,10 +44,9 @@ lnm_err lnm_loop_accept(lnm_loop *l) { // Append connection to list of connections if ((size_t)conn_fd >= l->conns.len) { - lnm_loop_conn **new = - l->conns.len == 0 - ? calloc(sizeof(lnm_loop_conn *), conn_fd + 1) - : realloc(l->conns.arr, sizeof(lnm_loop_conn *) * (conn_fd + 1)); + // We always calloc as a realloc might introduce unitialized values in the + // array + lnm_loop_conn **new = calloc(sizeof(lnm_loop_conn *), conn_fd + 1); if (new == NULL) { close(conn_fd); @@ -54,6 +54,11 @@ lnm_err lnm_loop_accept(lnm_loop *l) { return lnm_err_failed_alloc; } + if (l->conns.len > 0) { + memcpy(new, l->conns.arr, l->conns.len * sizeof(lnm_loop_conn *)); + free(l->conns.arr); + } + l->conns.arr = new; l->conns.len = conn_fd + 1; } From 8dc8ef8e2da034a369066ad03b2044e22165c26e Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 6 Dec 2023 17:43:56 +0100 Subject: [PATCH 23/45] chore(lander): updated changelog --- CHANGELOG.md | 12 +++++++++--- lnm/src/http/lnm_http_req.c | 9 +++++---- lnm/src/loop/lnm_loop.c | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d358bd0..ea9588b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://git.rustybever.be/Chewing_Bever/lander/src/branch/dev) -### Changed +### Added -* HTTP Loop - * Responses can now have an arbitrary number of headers +* LNM - Lander Network Module + * Rewrite of the event loop & HTTP loop + * Fully independent library + * Numerous improvements + * Streaming of headers + * Allow custom & an arbitrary number of response headers + * Better API for adding routes + * State machine HTTP loop ## [0.2.0](https://git.rustybever.be/Chewing_Bever/lander/src/tag/0.2.0) diff --git a/lnm/src/http/lnm_http_req.c b/lnm/src/http/lnm_http_req.c index a601e32..853a33e 100644 --- a/lnm/src/http/lnm_http_req.c +++ b/lnm/src/http/lnm_http_req.c @@ -49,12 +49,13 @@ lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, req->query.len = path_len - (question_mark + 1 - path); path_len = question_mark - path; + + // All parsed strings should be null-terminated. This character is either a + // newline (if at the end of the path), or a question mark (if a query is + // present). + path[path_len] = '\0'; } - // All parsed strings should be null-terminated. This character is either a - // newline (if at the end of the path), or a question mark (if a query is - // present). - path[path_len] = '\0'; req->path.len = path_len; req->path.s = path; req->len = req_len; diff --git a/lnm/src/loop/lnm_loop.c b/lnm/src/loop/lnm_loop.c index a412b23..350eb9b 100644 --- a/lnm/src/loop/lnm_loop.c +++ b/lnm/src/loop/lnm_loop.c @@ -2,8 +2,8 @@ #include #include #include -#include #include +#include #include "lnm/common.h" #include "lnm/loop_internal.h" From 1a7686003ccaef11ca582c06be8e7d621db19122 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 6 Dec 2023 18:16:52 +0100 Subject: [PATCH 24/45] feat(lander): re-add authentication using LNM --- lnm/include/lnm/http/loop.h | 5 +++++ lnm/src/http/lnm_http_loop.c | 5 +++++ lnm/src/http/lnm_http_loop_steps.c | 20 ++++++++++++++++++++ src/main.c | 17 +++++++++++------ 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/lnm/include/lnm/http/loop.h b/lnm/include/lnm/http/loop.h index 867a93e..8a199e7 100644 --- a/lnm/include/lnm/http/loop.h +++ b/lnm/include/lnm/http/loop.h @@ -83,6 +83,8 @@ lnm_err lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route); lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port); +void lnm_http_loop_set_api_key(lnm_http_loop *hl, const char *api_key); + /** * Represents what state an HTTP loop request is currently in. */ @@ -115,6 +117,7 @@ typedef struct lnm_http_loop_gctx { lnm_http_ctx_init_fn ctx_init; lnm_http_ctx_reset_fn ctx_reset; lnm_http_ctx_free_fn ctx_free; + const char *api_key; void *c; } lnm_http_loop_gctx; @@ -130,4 +133,6 @@ typedef struct lnm_http_loop_ctx { lnm_http_step_err lnm_http_loop_step_body_to_buf(lnm_http_conn *conn); +lnm_http_step_err lnm_http_loop_step_auth(lnm_http_conn *conn); + #endif diff --git a/lnm/src/http/lnm_http_loop.c b/lnm/src/http/lnm_http_loop.c index 6cd7d53..0b9b812 100644 --- a/lnm/src/http/lnm_http_loop.c +++ b/lnm/src/http/lnm_http_loop.c @@ -126,3 +126,8 @@ lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port) { LNM_RES(lnm_loop_setup(hl, port)); return lnm_loop_run(hl); } + +void lnm_http_loop_set_api_key(lnm_http_loop *hl, const char *api_key) { + lnm_http_loop_gctx *gctx = hl->gctx; + gctx->api_key = api_key; +} diff --git a/lnm/src/http/lnm_http_loop_steps.c b/lnm/src/http/lnm_http_loop_steps.c index fde3d70..2552e09 100644 --- a/lnm/src/http/lnm_http_loop_steps.c +++ b/lnm/src/http/lnm_http_loop_steps.c @@ -1,5 +1,6 @@ #include +#include "lnm/http/consts.h" #include "lnm/http/loop.h" #include "lnm/loop.h" @@ -22,3 +23,22 @@ lnm_http_step_err lnm_http_loop_step_body_to_buf(lnm_http_conn *conn) { ? lnm_http_step_err_done : lnm_http_step_err_io_needed; } + +lnm_http_step_err lnm_http_loop_step_auth(lnm_http_conn *conn) { + lnm_http_loop_ctx *ctx = conn->ctx; + + // If there's no API key, requests are always authorized + bool authorized = ctx->g->api_key == NULL; + + const char *value; + size_t value_len; + + if (!authorized && lnm_http_req_header_get_s(&value, &value_len, &ctx->req, + "X-Api-Key") == lnm_err_ok) { + authorized = (value_len == strlen(ctx->g->api_key)) && + (memcmp(value, ctx->g->api_key, value_len) == 0); + } + + ctx->res.status = authorized ? ctx->res.status : lnm_http_status_unauthorized; + return authorized ? lnm_http_step_err_done : lnm_http_step_err_res; +} diff --git a/src/main.c b/src/main.c index 3af1d2f..04bff84 100644 --- a/src/main.c +++ b/src/main.c @@ -7,13 +7,14 @@ #include "lander.h" #include "log.h" -lnm_http_loop *loop_init(lander_gctx *gctx) { +lnm_http_loop *loop_init(lander_gctx *gctx, const char *api_key) { lnm_http_loop *hl; lnm_http_step *step = NULL; lnm_http_route *route; lnm_http_loop_init(&hl, gctx, lander_ctx_init, (lnm_http_ctx_reset_fn)lander_ctx_reset, (lnm_http_ctx_free_fn)lander_ctx_free); + lnm_http_loop_set_api_key(hl, api_key); lnm_http_step_init(&step, lander_get_index); lnm_http_route_init_literal(&route, lnm_http_method_get, "/", step); @@ -23,28 +24,32 @@ lnm_http_loop *loop_init(lander_gctx *gctx) { lnm_http_route_init_regex(&route, lnm_http_method_get, "^/([^/]+)$", 1, step); lnm_http_loop_route_add(hl, route); - lnm_http_step_init(&step, lander_post_redirect); + lnm_http_step_init(&step, lnm_http_loop_step_auth); lnm_http_route_init_regex(&route, lnm_http_method_post, "^/s(l?)/([^/]*)$", 2, step); + lnm_http_step_append(&step, step, lander_post_redirect); lnm_http_step_append(&step, step, lnm_http_loop_step_body_to_buf); lnm_http_step_append(&step, step, lander_post_redirect_body_to_attr); lnm_http_loop_route_add(hl, route); - lnm_http_step_init(&step, lander_post_paste); + lnm_http_step_init(&step, lnm_http_loop_step_auth); lnm_http_route_init_regex(&route, lnm_http_method_post, "^/p(l?)/([^/]*)$", 2, step); + lnm_http_step_append(&step, step, lander_post_paste); lnm_http_step_append(&step, step, lander_stream_body_to_entry); lnm_http_loop_route_add(hl, route); - lnm_http_step_init(&step, lander_post_file); + lnm_http_step_init(&step, lnm_http_loop_step_auth); lnm_http_route_init_regex(&route, lnm_http_method_post, "^/f(l?)/([^/]*)$", 2, step); + lnm_http_step_append(&step, step, lander_post_file); lnm_http_step_append(&step, step, lander_stream_body_to_entry); lnm_http_loop_route_add(hl, route); - lnm_http_step_init(&step, lander_remove_entry); + lnm_http_step_init(&step, lnm_http_loop_step_auth); lnm_http_route_init_regex(&route, lnm_http_method_delete, "^/([^/]+)$", 1, step); + lnm_http_step_append(&step, step, lander_remove_entry); lnm_http_loop_route_add(hl, route); return hl; @@ -89,7 +94,7 @@ int main() { } info("Store loaded containing %lu entries", lsm_store_size(c_gctx->store)); - lnm_http_loop *hl = loop_init(c_gctx); + lnm_http_loop *hl = loop_init(c_gctx, api_key); lnm_http_loop_run(hl, port); /* http_loop *hl = http_loop_init( */ From c59dd29648928bfa22e8774130f2316ad8968be7 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Wed, 6 Dec 2023 23:04:06 +0100 Subject: [PATCH 25/45] chore(lander): removed old networking code --- Makefile | 4 + config.mk | 2 +- lnm/src/http/lnm_http_loop_process.c | 4 +- src/event_loop/event_loop.c | 192 --------------------------- src/event_loop/event_loop_conn.c | 13 -- src/event_loop/event_loop_io.c | 98 -------------- src/http/http_consts.c | 133 ------------------- src/http/res.c | 52 -------- src/http/types.c | 33 ----- src/http_loop/http_loop.c | 101 -------------- src/http_loop/http_loop_ctx.c | 62 --------- src/http_loop/http_loop_req.c | 149 --------------------- src/http_loop/http_loop_res.c | 143 -------------------- src/http_loop/http_loop_steps.c | 179 ------------------------- src/lander/lander.c | 49 ------- 15 files changed, 7 insertions(+), 1207 deletions(-) delete mode 100644 src/event_loop/event_loop.c delete mode 100644 src/event_loop/event_loop_conn.c delete mode 100644 src/event_loop/event_loop_io.c delete mode 100644 src/http/http_consts.c delete mode 100644 src/http/res.c delete mode 100644 src/http/types.c delete mode 100644 src/http_loop/http_loop.c delete mode 100644 src/http_loop/http_loop_ctx.c delete mode 100644 src/http_loop/http_loop_req.c delete mode 100644 src/http_loop/http_loop_res.c delete mode 100644 src/http_loop/http_loop_steps.c 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) { From cc9dc6aec599cdeb4e9a36afb273315149115e98 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Thu, 7 Dec 2023 11:02:20 +0100 Subject: [PATCH 26/45] chore: properly compile picohttpparser in lnm; remove old http header files --- .dockerignore | 11 +- Makefile | 9 +- include/event_loop.h | 137 ---- include/http/req.h | 42 -- include/http/res.h | 72 -- include/http/types.h | 175 ----- include/http_loop.h | 241 ------- include/lander.h | 5 - lnm/Makefile | 23 +- lnm/config.mk | 4 +- lnm/{thirdparty => }/include/picohttpparser.h | 0 lnm/{thirdparty => }/src/picohttpparser.c | 0 src/lander/lander.c | 2 - src/lander/lander_get.c | 27 +- src/lander/lander_post.c | 7 +- src/lander/lander_steps.c | 7 +- thirdparty/include/picohttpparser.h | 87 --- thirdparty/src/picohttpparser.c | 665 ------------------ 18 files changed, 30 insertions(+), 1484 deletions(-) delete mode 100644 include/event_loop.h delete mode 100644 include/http/req.h delete mode 100644 include/http/res.h delete mode 100644 include/http/types.h delete mode 100644 include/http_loop.h rename lnm/{thirdparty => }/include/picohttpparser.h (100%) rename lnm/{thirdparty => }/src/picohttpparser.c (100%) delete mode 100644 thirdparty/include/picohttpparser.h delete mode 100644 thirdparty/src/picohttpparser.c diff --git a/.dockerignore b/.dockerignore index 9bda86a..26ee61d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,14 +2,15 @@ !src/ !include/ +!Makefile +!config.mk !lsm/src/ !lsm/include/ !lsm/Makefile !lsm/config.mk -!thirdparty/include -!thirdparty/src - -!Makefile -!config.mk +!lnm/src/ +!lnm/include/ +!lnm/Makefile +!lnm/config.mk diff --git a/Makefile b/Makefile index 6e13422..cedc8b8 100644 --- a/Makefile +++ b/Makefile @@ -10,14 +10,13 @@ BIN := $(BUILD_DIR)/$(BIN_FILENAME) SRCS != find '$(SRC_DIR)' -iname '*.c' SRCS_TEST != find '$(TEST_DIR)' -iname '*.c' -SRCS_THIRDPARTY != find '$(THIRDPARTY_DIR)/src' -iname '*.c' SRCS_H != find include -iname '*.h' SRCS_H_INTERNAL != find $(SRC_DIR) -iname '*.h' -OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) $(SRCS_THIRDPARTY:%=$(BUILD_DIR)/%.o) +OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) OBJS_TEST := $(SRCS_TEST:%=$(BUILD_DIR)/%.o) -DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) $(SRCS_THIRDPARTY:%=$(BUILD_DIR)/%.d) $(SRCS_TEST:%=$(BUILD_DIR)/%.d) +DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) $(SRCS_TEST:%=$(BUILD_DIR)/%.d) BINS_TEST := $(OBJS_TEST:%.c.o=%) TARGETS_TEST := $(BINS_TEST:%=test-%) @@ -50,10 +49,6 @@ $(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c mkdir -p $(dir $@) $(CC) $(_CFLAGS) -c $< -o $@ -$(BUILD_DIR)/$(THIRDPARTY_DIR)/%.c.o: $(THIRDPARTY_DIR)/%.c - mkdir -p $(dir $@) - $(CC) $(_CFLAGS) -c $< -o $@ - .PHONY: bin-docker bin-docker: docker build -t lander . diff --git a/include/event_loop.h b/include/event_loop.h deleted file mode 100644 index 24dd05a..0000000 --- a/include/event_loop.h +++ /dev/null @@ -1,137 +0,0 @@ -#ifndef LANDER_EVENT_LOOP -#define LANDER_EVENT_LOOP - -#include -#include -#include - -// Size of the read and write buffers for each connection, in bytes -#define EVENT_LOOP_BUFFER_SIZE 2048 - -/** - * State of a connection - */ -typedef enum { - event_loop_conn_state_req = 0, - event_loop_conn_state_res = 1, - event_loop_conn_state_end = 2, -} event_loop_conn_state; - -/** - * Represents an active connection managed by the event loop - */ -typedef struct event_loop_conn { - int fd; - event_loop_conn_state state; - // Read buffer - size_t rbuf_size; - size_t rbuf_read; - uint8_t rbuf[EVENT_LOOP_BUFFER_SIZE]; - // Write buffer - size_t wbuf_size; - size_t wbuf_sent; - uint8_t wbuf[EVENT_LOOP_BUFFER_SIZE]; - - // If true, the server will close the connection after the final write buffer - // has been written - bool close_after_write; - // Context for a request - void *ctx; -} event_loop_conn; - -/* - * Main struct object representing the event loop - */ -typedef struct event_loop { - event_loop_conn **connections; - size_t connection_count; - // Global context passed to every connection - void *gctx; - /** - * Function to initialize a connection context. - * - * @param gctx global context of the event loop - * @return pointer to the allocated object. - */ - void *(*ctx_init)(void *gctx); - /** - * Function to free a connection context object. - * - * @param ctx context to free - */ - void (*ctx_free)(void *ctx); - /** - * Function to process incoming data while in the req state. - * - * @param conn connection to process - * @return whether the function can be called again immediately in the same - * event loop cycle. This allows quicly processing multiple requests without - * waiting for I/O. - */ - bool (*handle_data)(event_loop_conn *conn); - /** - * Function to process outgoing data while in the res state. - * - * @param conn connection to proces - */ - void (*write_data)(event_loop_conn *conn); -} event_loop; - -/* - * Initialize a new connection struct - * - * @param el the event loop - * @return pointer to the newly allocated connection struct - */ -event_loop_conn *event_loop_conn_init(event_loop *el); - -/* - * Free a connection struct - * - * @param el the event loop - * @param conn connection struct to free - */ -void event_loop_conn_free(event_loop *el, event_loop_conn *conn); - -/* - * Handle I/O for a connection, be it reading input or writing output. - * - * @param el the event loop - * @param conn the connection to process - */ -void event_loop_conn_io(event_loop *el, event_loop_conn *conn); - -/* - * Initialize a new event loop struct - * - * @return pointer to the newly allocated event loop struct - */ -event_loop *event_loop_init(); - -/* - * Place a new connection into the event loop's internal array. - * - * @param el the event loop - * @param conn connection to insert - * @return 0 on success, -1 if the internal realloc failed. - */ -int event_loop_put(event_loop *el, event_loop_conn *conn); - -/** - * Accept a new connection for the given file descriptor. - * - * @param el the event loop - * @param fd file descriptor for the connection - * @return 0 if successful, negative value otherwise - */ -int event_loop_accept(event_loop *el, int fd); - -/* - * Run the event loop. This function never returns. - * - * @param el the event loop - * @param port on what port to listen - */ -void event_loop_run(event_loop *el, int port); - -#endif diff --git a/include/http/req.h b/include/http/req.h deleted file mode 100644 index ab8922b..0000000 --- a/include/http/req.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef LANDER_HTTP_REQ -#define LANDER_HTTP_REQ - -#include -#include -#include -#include - -#include "http/types.h" -#include "picohttpparser.h" - -#define HTTP_MAX_ALLOWED_HEADERS 32 -#define HTTP_MAX_REGEX_GROUPS 4 - -/** - * Struct representing the specific type of request - */ -typedef struct http_request { - size_t len; - int minor_version; - http_method method; - const char *path; - size_t path_len; - const char *query; - size_t query_len; - http_body body; - regmatch_t regex_groups[HTTP_MAX_REGEX_GROUPS]; - struct phr_header headers[HTTP_MAX_ALLOWED_HEADERS]; - size_t num_headers; -} http_request; - -/** - * Result of the HTTP parse function - */ -typedef enum http_parse_error { - http_parse_error_ok = 0, - http_parse_error_incomplete = 1, - http_parse_error_invalid = 2, - http_parse_error_unknown_method = 3 -} http_parse_error; - -#endif diff --git a/include/http/res.h b/include/http/res.h deleted file mode 100644 index 729958f..0000000 --- a/include/http/res.h +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef LANDER_HTTP_RES -#define LANDER_HTTP_RES - -#include -#include - -#include "http/types.h" - -/** - * Struct describing a header for the response. - */ -typedef struct http_response_header { - http_header type; - const char *value; - bool owned; -} http_response_header; - -/** - * Struct representing an HTTP response. - */ -typedef struct http_response { - http_status status; - const char *head; - size_t head_len; - size_t head_written; - http_body body; - struct { - http_response_header *arr; - size_t len; - size_t cap; - } headers; -} http_response; - -/** - * Set the request body to the given buffer. - * - * @param res response to modify - * @param body pointer to the buf containing the body - * @param body_len length of the body - * @owned whether the body should be freed after processing the request - */ -void http_res_set_body_buf(http_response *res, const char *body, - size_t body_len, bool owned); - -/** - * Set the request body to the given filename. - * - * @param res response to modify - * @param filename path to the file to return - */ -void http_res_set_body_file(http_response *res, const char *filename); - -/** - * Add a header to the response. - * - * @param res response to modify - * @param type type of the header - * @param value value of the header - * @param owned whether the value should be freed after processing the request - */ -void http_res_add_header(http_response *res, http_header type, - const char *value, bool owned); - -/** - * Add a Content-Type header corresponding to the mime type. - * - * @param res response to modiy - * @param mime_type mime type of the response - */ -void http_res_set_mime_type(http_response *res, http_mime_type mime_type); - -#endif diff --git a/include/http/types.h b/include/http/types.h deleted file mode 100644 index fbbe160..0000000 --- a/include/http/types.h +++ /dev/null @@ -1,175 +0,0 @@ -#ifndef LANDER_HTTP_TYPES -#define LANDER_HTTP_TYPES - -#include -#include -#include - -// Array mapping the http_request_method enum to strings -extern const char *http_method_names[]; -extern const size_t http_method_names_len; - -typedef enum http_method { - http_get = 0, - http_post = 1, - http_put = 2, - http_patch = 3, - http_delete = 4 -} http_method; - -// Array mapping the http_response_type enum to strings -extern const char *http_status_names[][32]; - -typedef enum http_status { - // 1xx - http_continue = 100, - http_switching_protocols = 101, - http_processing = 102, - http_early_hints = 103, - // 2xx - http_ok = 200, - http_created = 201, - http_accepted = 202, - http_non_authoritative_information = 203, - http_no_content = 204, - http_reset_content = 205, - http_partial_content = 206, - http_multi_status = 207, - http_already_reported = 208, - // 3xx - http_multiple_choices = 300, - http_moved_permanently = 301, - http_found = 302, - http_see_other = 303, - http_not_modified = 304, - http_temporary_redirect = 307, - http_permanent_redirect = 308, - // 4xx - http_bad_request = 400, - http_unauthorized = 401, - http_payment_required = 402, - http_forbidden = 403, - http_not_found = 404, - http_method_not_allowed = 405, - http_not_acceptable = 406, - http_proxy_authentication_required = 407, - http_request_timeout = 408, - http_conflict = 409, - http_gone = 410, - http_length_required = 411, - http_precondition_failed = 412, - http_content_too_large = 413, - http_uri_too_long = 414, - http_unsupported_media_type = 415, - http_range_not_satisfiable = 416, - http_expection_failed = 417, - http_im_a_teapot = 418, - http_misdirected_request = 421, - http_unprocessable_content = 422, - http_locked = 423, - http_failed_dependency = 424, - http_too_early = 425, - http_upgrade_required = 426, - http_precondition_required = 428, - http_too_many_requests = 429, - http_request_header_fields_too_large = 431, - // 5xx - http_internal_server_error = 500, - http_method_not_implemented = 501, - http_bad_gateway = 502, - http_service_unavailable = 503, - http_gateway_timeout = 504, - http_http_version_not_supported = 505, - http_variant_also_negotiates = 506, - http_insufficient_storage = 507, - http_loop_detected = 508, - http_not_extended = 510, - http_network_authentication_required = 511 -} http_status; - -// Array mapping the http_mime_type enum to strings -extern const char *http_mime_type_names[][2]; - -typedef enum http_mime_type { - http_mime_aac = 0, - http_mime_bz, - http_mime_bz2, - http_mime_css, - http_mime_csv, - http_mime_gz, - http_mime_gif, - http_mime_htm, - http_mime_html, - http_mime_jar, - http_mime_jpeg, - http_mime_jpg, - http_mime_js, - http_mime_json, - http_mime_mp3, - http_mime_mp4, - http_mime_png, - http_mime_pdf, - http_mime_rar, - http_mime_sh, - http_mime_svg, - http_mime_tar, - http_mime_txt, - http_mime_wav, - http_mime_7z -} http_mime_type; - -// Array mapping the http_header enum to strings -extern const char *http_header_names[]; - -typedef enum http_header { - http_header_connection = 0, - http_header_location, - http_header_content_type, - http_header_content_disposition, - http_header_server, - http_header_content_length -} http_header; - -typedef enum http_body_type { - http_body_buf = 0, - http_body_file = 1 -} http_body_type; - -typedef struct http_body { - http_body_type type; - char *buf; - bool buf_owned; - FILE *file; - char *fname; - bool fname_owned; - // In the context of a request, expected_len is the content of the request's - // Content-Length header, and len is how many bytes have already been - // received. - // In the context of a response, expected_len is the actual length of the - // body, and len is how many have been written. - size_t expected_len; - size_t len; -} http_body; - -/** - * Initialize a new body struct. - * - * @return pointer to the newly allocated object. - */ -http_body *http_body_init(); - -/** - * Reset a body, allowing it to be reused as if newly allocated. - * - * @param body body to reset - */ -void http_body_reset(http_body *body); - -/** - * Free a body. Internally, this calls http_body_reset. - * - * @param body body to free - */ -void http_body_free(http_body *body); - -#endif diff --git a/include/http_loop.h b/include/http_loop.h deleted file mode 100644 index 131bd6e..0000000 --- a/include/http_loop.h +++ /dev/null @@ -1,241 +0,0 @@ -#ifndef LANDER_HTTP_LOOP -#define LANDER_HTTP_LOOP - -#include - -#include "event_loop.h" -#include "http/req.h" -#include "http/res.h" -#include "http/types.h" - -// Max amount of steps a route can use -#define HTTP_LOOP_MAX_STEPS 17 - -#define MIN(x, y) (((x) < (y)) ? (x) : (y)) -#define MAX(x, y) (((x) > (y)) ? (x) : (y)) - -/** - * Type of a route - */ -typedef enum http_route_type { - http_route_literal = 0, - http_route_regex = 1, -} http_route_type; - -/** - * Function describing a step in a route's processing. - * - * @param conn connection to process - * @return whether processing can proceed to the next step without performing - * I/O first. For a request step, `false` means more data needs to be read - * before the step can finish its processing. For response steps, `false` means - * there's new data in the write buffer that needs to be written. - */ -typedef bool (*http_step)(event_loop_conn *conn); - -extern const http_step http_default_res_steps[HTTP_LOOP_MAX_STEPS]; - -/** - * Struct describing a route a request can take. - */ -typedef struct http_route { - http_route_type type; - http_method method; - char *path; - // Compiled regex for a regex route. This value gets set at runtime when - // starting the http loop - regex_t *regex; - const http_step steps[HTTP_LOOP_MAX_STEPS]; - const http_step steps_res[HTTP_LOOP_MAX_STEPS]; -} http_route; - -/** - * Global context passed to every connection using the same pointer - */ -typedef struct http_loop_gctx { - http_route *routes; - size_t route_count; - void *(*custom_ctx_init)(); - void (*custom_ctx_reset)(void *); - void (*custom_ctx_free)(void *); - const char *api_key; - // Custom global context - void *c; -} http_loop_gctx; - -/** - * Initialize a new global context - * - * @return pointer to the newly allocated object. - */ -http_loop_gctx *http_loop_gctx_init(); - -/** - * Invidivual context initialized for every connection - */ -typedef struct http_loop_ctx { - http_request req; - http_response res; - http_route *route; - size_t current_step; - http_loop_gctx *g; - void *c; -} http_loop_ctx; - -/** - * Initialize a context struct - * - * @param g global context - * @return pointer to the newly allocated context - */ -http_loop_ctx *http_loop_ctx_init(http_loop_gctx *g); - -/** - * Resets an already allocated context so that it can be reused for a new - * request. - * - * @param ctx context to reset - */ -void http_loop_ctx_reset(http_loop_ctx *ctx); - -/** - * Free a context struct. Internally this first calls http_loop_ctx_reset. - * - * @param ctx context to free - */ -void http_loop_ctx_free(http_loop_ctx *ctx); - -/** - * Represents an HTTP loop - */ -typedef struct event_loop http_loop; - -/** - * Process incoming data as an HTTP request. This is the "handle_data" function - * for the event loop. - * - * @param conn connection to process - * @return whether another request can be processed immediately. - */ -bool http_loop_handle_request(event_loop_conn *conn); - -/** - * Try to parse the incoming data as an HTTP request. - * - * @param conn connection to process - * @return result of the parse - */ -http_parse_error http_loop_parse_request(event_loop_conn *conn); - -/** - * Try to match the parsed request with one of the defined routes, aka route the - * request. - * - * @param conn connection to process - */ -void http_loop_route_request(event_loop_conn *conn); - -/** - * Advance the processing of the routed request's processing by cycling through - * the request's various steps. - * - * @param conn connection to process - */ -void http_loop_process_request(event_loop_conn *conn); - -/** - * Handles the response processing. This is the `write_data` function for the - * event loop. - * - * @param conn connection to process - */ -void http_loop_handle_response(event_loop_conn *conn); - -/** - * Request step that consumes the request body and stores it in a buffer. - * - * @param conn connection to process - * @return true if the body has been fully received, false otherwise - */ -bool http_loop_step_body_to_buf(event_loop_conn *conn); - -/** - * Request step that consumes the request body and stores it in a file. - * - * @param conn connection to process - * @return true if the body has been fully received, false otherwise - */ -bool http_loop_step_body_to_file(event_loop_conn *conn); - -/** - * Try to parse the Content-Length header. - * - * @param conn connection to process - */ -bool http_loop_step_parse_content_length(event_loop_conn *conn); - -/** - * Authenticate the request using the X-Api-Key header. - * - * @param conn connection to check - * @return always true - */ -bool http_loop_step_auth(event_loop_conn *conn); - -/** - * A step that simply sets the connection's state to res. - * - * @param conn connection to process - * @return always true - */ -bool http_loop_step_switch_res(event_loop_conn *conn); - -/** - * Write the HTTP header back to the connection. If `res->head` is not set, a - * header will be generated for you. - * - * @param conn connection to process - */ -bool http_loop_step_write_header(event_loop_conn *conn); - -/** - * Write the HTTP body back to the connection. - * - * @param conn connection to process - */ -bool http_loop_step_write_body(event_loop_conn *conn); - -/** - * Initialize a new http loop. - * - * @param routes array of routes that should be served - * @param route_count how many elements are in `routes` - * @param custom_gctx the application's custom global context; can be NULL - * @param custom_ctx_init function to initialize a new custom context - * @param custom_ctx_reset function to reset a custom context - * @param custom_ctx_free function to free a custom context; will always be run - * after a reset - * @return pointer to the newly allocated object - */ -http_loop *http_loop_init(http_route *routes, size_t route_count, - void *custom_gctx, void *(*custom_ctx_init)(), - void(custom_ctx_reset)(void *), - void(custom_ctx_free)(void *)); - -/** - * Set the API key the authentication steps should use. - * - * @param hl HTTP loop to set key in - * @param api_key API key to use - */ -void http_loop_set_api_key(http_loop *hl, const char *api_key); - -/** - * Run the HTTP loop. This function never returns. - * - * @param el the event loop - * @param port on what port to listen - */ -void http_loop_run(http_loop *hl, int port); - -#endif diff --git a/include/lander.h b/include/lander.h index c61b7b6..ae2f714 100644 --- a/include/lander.h +++ b/include/lander.h @@ -5,9 +5,6 @@ #include "lnm/http/loop.h" #include "lsm/store.h" -#include "http_loop.h" - -extern http_route lander_routes[6]; extern const char lander_key_charset[]; typedef struct lander_gctx { @@ -50,8 +47,6 @@ lnm_http_step_err lander_post_paste(lnm_http_conn *conn); lnm_http_step_err lander_stream_body_to_entry(lnm_http_conn *conn); -bool lander_stream_body_to_client(event_loop_conn *conn); - lnm_http_step_err lander_post_redirect_body_to_attr(lnm_http_conn *conn); lnm_http_step_err lander_remove_entry(lnm_http_conn *conn); diff --git a/lnm/Makefile b/lnm/Makefile index 7371c9f..38b2b4a 100644 --- a/lnm/Makefile +++ b/lnm/Makefile @@ -9,11 +9,10 @@ SRCS != find '$(SRC_DIR)' -iname '*.c' SRCS_H != find include -iname '*.h' SRCS_H_INTERNAL != find $(SRC_DIR) -iname '*.h' SRCS_TEST != find '$(TEST_DIR)' -iname '*.c' -SRCS_THIRDPARTY != find '$(THIRDPARTY_DIR)/src' -iname '*.c' -OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) $(SRCS_THIRDPARTY:%=$(BUILD_DIR)/%.o) +OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) OBJS_TEST := $(SRCS_TEST:%=$(BUILD_DIR)/%.o) -DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) $(SRCS_TEST:%=$(BUILD_DIR)/%.d) $(SRCS_THIRDPARTY:%=$(BUILD_DIR)/%.d) +DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) $(SRCS_TEST:%=$(BUILD_DIR)/%.d) BINS_TEST := $(OBJS_TEST:%.c.o=%) TARGETS_TEST := $(BINS_TEST:%=test-%) @@ -39,10 +38,6 @@ $(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c mkdir -p $(dir $@) $(CC) -c $(_CFLAGS) $< -o $@ -$(BUILD_DIR)/$(THIRDPARTY_DIR)/%.c.o: $(THIRDPARTY_DIR)/%.c - mkdir -p $(dir $@) - $(CC) $(_CFLAGS) -c $< -o $@ - # =====TESTING===== .PHONY: test test: $(TARGETS_TEST) @@ -91,11 +86,17 @@ $(BUILD_DIR)/$(EXAMPLE_DIR)/%.c.o: $(EXAMPLE_DIR)/%.c # =====MAINTENANCE===== .PHONY: lint lint: - clang-format -n --Werror $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) + clang-format -n --Werror \ + $(filter-out $(THIRDPARTY),$(SRCS)) \ + $(filter-out $(THIRDPARTY),$(SRCS_H)) \ + $(filter-out $(THIRDPARTY),$(SRCS_H_INTERNAL)) .PHONY: fmt fmt: - clang-format -i $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) + clang-format -i \ + $(filter-out $(THIRDPARTY),$(SRCS)) \ + $(filter-out $(THIRDPARTY),$(SRCS_H)) \ + $(filter-out $(THIRDPARTY),$(SRCS_H_INTERNAL)) .PHONY: check check: @@ -109,11 +110,11 @@ check: --check-level=exhaustive \ --quiet \ -j$(shell nproc) \ - $(SRCS) + $(filter-out $(THIRDPARTY),$(SRCS)) .PHONY: clean clean: - rm -rf $(BUILD_DIR) + rm -rf '$(BUILD_DIR)' .PHONY: bear diff --git a/lnm/config.mk b/lnm/config.mk index fde7e0e..7e55aff 100644 --- a/lnm/config.mk +++ b/lnm/config.mk @@ -3,10 +3,10 @@ LIB_FILENAME = liblnm.a BUILD_DIR = build SRC_DIR = src TEST_DIR = test -THIRDPARTY_DIR = thirdparty +THIRDPARTY = src/picohttpparser.c include/picohttpparser.h PUB_INC_DIR = include -INC_DIRS = $(PUB_INC_DIR) src/_include $(THIRDPARTY_DIR)/include +INC_DIRS = $(PUB_INC_DIR) src/_include # -MMD: generate a .d file for every source file. This file can be imported by # make and makes make aware that a header file has been changed, ensuring an diff --git a/lnm/thirdparty/include/picohttpparser.h b/lnm/include/picohttpparser.h similarity index 100% rename from lnm/thirdparty/include/picohttpparser.h rename to lnm/include/picohttpparser.h diff --git a/lnm/thirdparty/src/picohttpparser.c b/lnm/src/picohttpparser.c similarity index 100% rename from lnm/thirdparty/src/picohttpparser.c rename to lnm/src/picohttpparser.c diff --git a/src/lander/lander.c b/src/lander/lander.c index d6bf3f5..ff44b5c 100644 --- a/src/lander/lander.c +++ b/src/lander/lander.c @@ -4,8 +4,6 @@ #include "lnm/common.h" #include "lsm/store.h" -#include "http/types.h" -#include "http_loop.h" #include "lander.h" const char lander_key_charset[] = diff --git a/src/lander/lander_get.c b/src/lander/lander_get.c index 1bf4da0..4b42221 100644 --- a/src/lander/lander_get.c +++ b/src/lander/lander_get.c @@ -3,13 +3,10 @@ #include "lnm/http/consts.h" #include "lnm/http/loop.h" #include "lnm/loop.h" +#include "lsm/store.h" -#include "event_loop.h" -#include "http/res.h" -#include "http/types.h" #include "lander.h" #include "log.h" -#include "lsm/store.h" static const char index_page[] = "\n" @@ -156,25 +153,3 @@ lnm_http_step_err lander_get_entry(lnm_http_conn *conn) { return res; } - -bool lander_stream_body_to_client(event_loop_conn *conn) { - http_loop_ctx *ctx = conn->ctx; - lander_ctx *c_ctx = ctx->c; - - if ((c_ctx->entry == NULL) || - (ctx->res.body.expected_len == ctx->res.body.len)) { - return true; - } - - uint64_t to_write = MIN(EVENT_LOOP_BUFFER_SIZE - conn->wbuf_size, - ctx->res.body.expected_len - ctx->res.body.len); - - uint64_t read = 0; - lsm_entry_data_read(&read, (char *)&conn->wbuf[conn->wbuf_size], c_ctx->entry, - to_write); - - ctx->res.body.len += read; - conn->wbuf_size += read; - - return false; -} diff --git a/src/lander/lander_post.c b/src/lander/lander_post.c index 882cb04..4bda832 100644 --- a/src/lander/lander_post.c +++ b/src/lander/lander_post.c @@ -1,10 +1,9 @@ -#include "http/res.h" -#include "http/types.h" -#include "lander.h" #include "lnm/loop.h" -#include "log.h" #include "lsm/store.h" +#include "lander.h" +#include "log.h" + static void randomize_key(char *key, int len) { size_t charset_len = strlen(lander_key_charset); diff --git a/src/lander/lander_steps.c b/src/lander/lander_steps.c index 6021dbd..00c86ba 100644 --- a/src/lander/lander_steps.c +++ b/src/lander/lander_steps.c @@ -1,16 +1,17 @@ #include -#include "lander.h" #include "lnm/http/loop.h" #include "lnm/loop.h" +#include "lander.h" + lnm_http_step_err lander_stream_body_to_entry(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; uint64_t to_append = - MIN(conn->r.size - conn->r.read, - ctx->req.body.expected_len - lsm_entry_data_len(c_ctx->entry)); + LNM_MIN(conn->r.size - conn->r.read, + ctx->req.body.expected_len - lsm_entry_data_len(c_ctx->entry)); lsm_str *data; lsm_str_init_copy_n(&data, (char *)&conn->r.buf[conn->r.read], to_append); diff --git a/thirdparty/include/picohttpparser.h b/thirdparty/include/picohttpparser.h deleted file mode 100644 index 07537cf..0000000 --- a/thirdparty/include/picohttpparser.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, - * Shigeo Mitsunari - * - * The software is licensed under either the MIT License (below) or the Perl - * license. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#ifndef picohttpparser_h -#define picohttpparser_h - -#include - -#ifdef _MSC_VER -#define ssize_t intptr_t -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/* contains name and value of a header (name == NULL if is a continuing line - * of a multiline header */ -struct phr_header { - const char *name; - size_t name_len; - const char *value; - size_t value_len; -}; - -/* returns number of bytes consumed if successful, -2 if request is partial, - * -1 if failed */ -int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len, - int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len); - -/* ditto */ -int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len, - struct phr_header *headers, size_t *num_headers, size_t last_len); - -/* ditto */ -int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len); - -/* should be zero-filled before start */ -struct phr_chunked_decoder { - size_t bytes_left_in_chunk; /* number of bytes left in current chunk */ - char consume_trailer; /* if trailing headers should be consumed */ - char _hex_count; - char _state; -}; - -/* the function rewrites the buffer given as (buf, bufsz) removing the chunked- - * encoding headers. When the function returns without an error, bufsz is - * updated to the length of the decoded data available. Applications should - * repeatedly call the function while it returns -2 (incomplete) every time - * supplying newly arrived data. If the end of the chunked-encoded data is - * found, the function returns a non-negative number indicating the number of - * octets left undecoded, that starts from the offset returned by `*bufsz`. - * Returns -1 on error. - */ -ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz); - -/* returns if the chunked decoder is in middle of chunked data */ -int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/thirdparty/src/picohttpparser.c b/thirdparty/src/picohttpparser.c deleted file mode 100644 index 5e5783a..0000000 --- a/thirdparty/src/picohttpparser.c +++ /dev/null @@ -1,665 +0,0 @@ -/* - * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, - * Shigeo Mitsunari - * - * The software is licensed under either the MIT License (below) or the Perl - * license. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#include -#include -#include -#ifdef __SSE4_2__ -#ifdef _MSC_VER -#include -#else -#include -#endif -#endif -#include "picohttpparser.h" - -#if __GNUC__ >= 3 -#define likely(x) __builtin_expect(!!(x), 1) -#define unlikely(x) __builtin_expect(!!(x), 0) -#else -#define likely(x) (x) -#define unlikely(x) (x) -#endif - -#ifdef _MSC_VER -#define ALIGNED(n) _declspec(align(n)) -#else -#define ALIGNED(n) __attribute__((aligned(n))) -#endif - -#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u) - -#define CHECK_EOF() \ - if (buf == buf_end) { \ - *ret = -2; \ - return NULL; \ - } - -#define EXPECT_CHAR_NO_CHECK(ch) \ - if (*buf++ != ch) { \ - *ret = -1; \ - return NULL; \ - } - -#define EXPECT_CHAR(ch) \ - CHECK_EOF(); \ - EXPECT_CHAR_NO_CHECK(ch); - -#define ADVANCE_TOKEN(tok, toklen) \ - do { \ - const char *tok_start = buf; \ - static const char ALIGNED(16) ranges2[16] = "\000\040\177\177"; \ - int found2; \ - buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \ - if (!found2) { \ - CHECK_EOF(); \ - } \ - while (1) { \ - if (*buf == ' ') { \ - break; \ - } else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \ - if ((unsigned char)*buf < '\040' || *buf == '\177') { \ - *ret = -1; \ - return NULL; \ - } \ - } \ - ++buf; \ - CHECK_EOF(); \ - } \ - tok = tok_start; \ - toklen = buf - tok_start; \ - } while (0) - -static const char *token_char_map = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0" - "\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1" - "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; - -static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, size_t ranges_size, int *found) -{ - *found = 0; -#if __SSE4_2__ - if (likely(buf_end - buf >= 16)) { - __m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges); - - size_t left = (buf_end - buf) & ~15; - do { - __m128i b16 = _mm_loadu_si128((const __m128i *)buf); - int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS); - if (unlikely(r != 16)) { - buf += r; - *found = 1; - break; - } - buf += 16; - left -= 16; - } while (likely(left != 0)); - } -#else - /* suppress unused parameter warning */ - (void)buf_end; - (void)ranges; - (void)ranges_size; -#endif - return buf; -} - -static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret) -{ - const char *token_start = buf; - -#ifdef __SSE4_2__ - static const char ALIGNED(16) ranges1[16] = "\0\010" /* allow HT */ - "\012\037" /* allow SP and up to but not including DEL */ - "\177\177"; /* allow chars w. MSB set */ - int found; - buf = findchar_fast(buf, buf_end, ranges1, 6, &found); - if (found) - goto FOUND_CTL; -#else - /* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */ - while (likely(buf_end - buf >= 8)) { -#define DOIT() \ - do { \ - if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \ - goto NonPrintable; \ - ++buf; \ - } while (0) - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); -#undef DOIT - continue; - NonPrintable: - if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { - goto FOUND_CTL; - } - ++buf; - } -#endif - for (;; ++buf) { - CHECK_EOF(); - if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { - if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { - goto FOUND_CTL; - } - } - } -FOUND_CTL: - if (likely(*buf == '\015')) { - ++buf; - EXPECT_CHAR('\012'); - *token_len = buf - 2 - token_start; - } else if (*buf == '\012') { - *token_len = buf - token_start; - ++buf; - } else { - *ret = -1; - return NULL; - } - *token = token_start; - - return buf; -} - -static const char *is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret) -{ - int ret_cnt = 0; - buf = last_len < 3 ? buf : buf + last_len - 3; - - while (1) { - CHECK_EOF(); - if (*buf == '\015') { - ++buf; - CHECK_EOF(); - EXPECT_CHAR('\012'); - ++ret_cnt; - } else if (*buf == '\012') { - ++buf; - ++ret_cnt; - } else { - ++buf; - ret_cnt = 0; - } - if (ret_cnt == 2) { - return buf; - } - } - - *ret = -2; - return NULL; -} - -#define PARSE_INT(valp_, mul_) \ - if (*buf < '0' || '9' < *buf) { \ - buf++; \ - *ret = -1; \ - return NULL; \ - } \ - *(valp_) = (mul_) * (*buf++ - '0'); - -#define PARSE_INT_3(valp_) \ - do { \ - int res_ = 0; \ - PARSE_INT(&res_, 100) \ - *valp_ = res_; \ - PARSE_INT(&res_, 10) \ - *valp_ += res_; \ - PARSE_INT(&res_, 1) \ - *valp_ += res_; \ - } while (0) - -/* returned pointer is always within [buf, buf_end), or null */ -static const char *parse_token(const char *buf, const char *buf_end, const char **token, size_t *token_len, char next_char, - int *ret) -{ - /* We use pcmpestri to detect non-token characters. This instruction can take no more than eight character ranges (8*2*8=128 - * bits that is the size of a SSE register). Due to this restriction, characters `|` and `~` are handled in the slow loop. */ - static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up to SP */ - "\"\"" /* 0x22 */ - "()" /* 0x28,0x29 */ - ",," /* 0x2c */ - "//" /* 0x2f */ - ":@" /* 0x3a-0x40 */ - "[]" /* 0x5b-0x5d */ - "{\xff"; /* 0x7b-0xff */ - const char *buf_start = buf; - int found; - buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found); - if (!found) { - CHECK_EOF(); - } - while (1) { - if (*buf == next_char) { - break; - } else if (!token_char_map[(unsigned char)*buf]) { - *ret = -1; - return NULL; - } - ++buf; - CHECK_EOF(); - } - *token = buf_start; - *token_len = buf - buf_start; - return buf; -} - -/* returned pointer is always within [buf, buf_end), or null */ -static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret) -{ - /* we want at least [HTTP/1.] to try to parse */ - if (buf_end - buf < 9) { - *ret = -2; - return NULL; - } - EXPECT_CHAR_NO_CHECK('H'); - EXPECT_CHAR_NO_CHECK('T'); - EXPECT_CHAR_NO_CHECK('T'); - EXPECT_CHAR_NO_CHECK('P'); - EXPECT_CHAR_NO_CHECK('/'); - EXPECT_CHAR_NO_CHECK('1'); - EXPECT_CHAR_NO_CHECK('.'); - PARSE_INT(minor_version, 1); - return buf; -} - -static const char *parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, size_t *num_headers, - size_t max_headers, int *ret) -{ - for (;; ++*num_headers) { - CHECK_EOF(); - if (*buf == '\015') { - ++buf; - EXPECT_CHAR('\012'); - break; - } else if (*buf == '\012') { - ++buf; - break; - } - if (*num_headers == max_headers) { - *ret = -1; - return NULL; - } - if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) { - /* parsing name, but do not discard SP before colon, see - * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */ - if ((buf = parse_token(buf, buf_end, &headers[*num_headers].name, &headers[*num_headers].name_len, ':', ret)) == NULL) { - return NULL; - } - if (headers[*num_headers].name_len == 0) { - *ret = -1; - return NULL; - } - ++buf; - for (;; ++buf) { - CHECK_EOF(); - if (!(*buf == ' ' || *buf == '\t')) { - break; - } - } - } else { - headers[*num_headers].name = NULL; - headers[*num_headers].name_len = 0; - } - const char *value; - size_t value_len; - if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len, ret)) == NULL) { - return NULL; - } - /* remove trailing SPs and HTABs */ - const char *value_end = value + value_len; - for (; value_end != value; --value_end) { - const char c = *(value_end - 1); - if (!(c == ' ' || c == '\t')) { - break; - } - } - headers[*num_headers].value = value; - headers[*num_headers].value_len = value_end - value; - } - return buf; -} - -static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path, - size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, - size_t max_headers, int *ret) -{ - /* skip first empty line (some clients add CRLF after POST content) */ - CHECK_EOF(); - if (*buf == '\015') { - ++buf; - EXPECT_CHAR('\012'); - } else if (*buf == '\012') { - ++buf; - } - - /* parse request line */ - if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == NULL) { - return NULL; - } - do { - ++buf; - CHECK_EOF(); - } while (*buf == ' '); - ADVANCE_TOKEN(*path, *path_len); - do { - ++buf; - CHECK_EOF(); - } while (*buf == ' '); - if (*method_len == 0 || *path_len == 0) { - *ret = -1; - return NULL; - } - if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { - return NULL; - } - if (*buf == '\015') { - ++buf; - EXPECT_CHAR('\012'); - } else if (*buf == '\012') { - ++buf; - } else { - *ret = -1; - return NULL; - } - - return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); -} - -int phr_parse_request(const char *buf_start, size_t len, const char **method, size_t *method_len, const char **path, - size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len) -{ - const char *buf = buf_start, *buf_end = buf_start + len; - size_t max_headers = *num_headers; - int r; - - *method = NULL; - *method_len = 0; - *path = NULL; - *path_len = 0; - *minor_version = -1; - *num_headers = 0; - - /* if last_len != 0, check if the request is complete (a fast countermeasure - againt slowloris */ - if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { - return r; - } - - if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers, - &r)) == NULL) { - return r; - } - - return (int)(buf - buf_start); -} - -static const char *parse_response(const char *buf, const char *buf_end, int *minor_version, int *status, const char **msg, - size_t *msg_len, struct phr_header *headers, size_t *num_headers, size_t max_headers, int *ret) -{ - /* parse "HTTP/1.x" */ - if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { - return NULL; - } - /* skip space */ - if (*buf != ' ') { - *ret = -1; - return NULL; - } - do { - ++buf; - CHECK_EOF(); - } while (*buf == ' '); - /* parse status code, we want at least [:digit:][:digit:][:digit:] to try to parse */ - if (buf_end - buf < 4) { - *ret = -2; - return NULL; - } - PARSE_INT_3(status); - - /* get message including preceding space */ - if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) { - return NULL; - } - if (*msg_len == 0) { - /* ok */ - } else if (**msg == ' ') { - /* Remove preceding space. Successful return from `get_token_to_eol` guarantees that we would hit something other than SP - * before running past the end of the given buffer. */ - do { - ++*msg; - --*msg_len; - } while (**msg == ' '); - } else { - /* garbage found after status code */ - *ret = -1; - return NULL; - } - - return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); -} - -int phr_parse_response(const char *buf_start, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len, - struct phr_header *headers, size_t *num_headers, size_t last_len) -{ - const char *buf = buf_start, *buf_end = buf + len; - size_t max_headers = *num_headers; - int r; - - *minor_version = -1; - *status = 0; - *msg = NULL; - *msg_len = 0; - *num_headers = 0; - - /* if last_len != 0, check if the response is complete (a fast countermeasure - against slowloris */ - if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { - return r; - } - - if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) == NULL) { - return r; - } - - return (int)(buf - buf_start); -} - -int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len) -{ - const char *buf = buf_start, *buf_end = buf + len; - size_t max_headers = *num_headers; - int r; - - *num_headers = 0; - - /* if last_len != 0, check if the response is complete (a fast countermeasure - against slowloris */ - if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { - return r; - } - - if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) { - return r; - } - - return (int)(buf - buf_start); -} - -enum { - CHUNKED_IN_CHUNK_SIZE, - CHUNKED_IN_CHUNK_EXT, - CHUNKED_IN_CHUNK_DATA, - CHUNKED_IN_CHUNK_CRLF, - CHUNKED_IN_TRAILERS_LINE_HEAD, - CHUNKED_IN_TRAILERS_LINE_MIDDLE -}; - -static int decode_hex(int ch) -{ - if ('0' <= ch && ch <= '9') { - return ch - '0'; - } else if ('A' <= ch && ch <= 'F') { - return ch - 'A' + 0xa; - } else if ('a' <= ch && ch <= 'f') { - return ch - 'a' + 0xa; - } else { - return -1; - } -} - -ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *_bufsz) -{ - size_t dst = 0, src = 0, bufsz = *_bufsz; - ssize_t ret = -2; /* incomplete */ - - while (1) { - switch (decoder->_state) { - case CHUNKED_IN_CHUNK_SIZE: - for (;; ++src) { - int v; - if (src == bufsz) - goto Exit; - if ((v = decode_hex(buf[src])) == -1) { - if (decoder->_hex_count == 0) { - ret = -1; - goto Exit; - } - break; - } - if (decoder->_hex_count == sizeof(size_t) * 2) { - ret = -1; - goto Exit; - } - decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v; - ++decoder->_hex_count; - } - decoder->_hex_count = 0; - decoder->_state = CHUNKED_IN_CHUNK_EXT; - /* fallthru */ - case CHUNKED_IN_CHUNK_EXT: - /* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */ - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] == '\012') - break; - } - ++src; - if (decoder->bytes_left_in_chunk == 0) { - if (decoder->consume_trailer) { - decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; - break; - } else { - goto Complete; - } - } - decoder->_state = CHUNKED_IN_CHUNK_DATA; - /* fallthru */ - case CHUNKED_IN_CHUNK_DATA: { - size_t avail = bufsz - src; - if (avail < decoder->bytes_left_in_chunk) { - if (dst != src) - memmove(buf + dst, buf + src, avail); - src += avail; - dst += avail; - decoder->bytes_left_in_chunk -= avail; - goto Exit; - } - if (dst != src) - memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk); - src += decoder->bytes_left_in_chunk; - dst += decoder->bytes_left_in_chunk; - decoder->bytes_left_in_chunk = 0; - decoder->_state = CHUNKED_IN_CHUNK_CRLF; - } - /* fallthru */ - case CHUNKED_IN_CHUNK_CRLF: - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] != '\015') - break; - } - if (buf[src] != '\012') { - ret = -1; - goto Exit; - } - ++src; - decoder->_state = CHUNKED_IN_CHUNK_SIZE; - break; - case CHUNKED_IN_TRAILERS_LINE_HEAD: - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] != '\015') - break; - } - if (buf[src++] == '\012') - goto Complete; - decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE; - /* fallthru */ - case CHUNKED_IN_TRAILERS_LINE_MIDDLE: - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] == '\012') - break; - } - ++src; - decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; - break; - default: - assert(!"decoder is corrupt"); - } - } - -Complete: - ret = bufsz - src; -Exit: - if (dst != src) - memmove(buf + dst, buf + src, bufsz - src); - *_bufsz = dst; - return ret; -} - -int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder) -{ - return decoder->_state == CHUNKED_IN_CHUNK_DATA; -} - -#undef CHECK_EOF -#undef EXPECT_CHAR -#undef ADVANCE_TOKEN From 58a8645c6c4f1ad5260661950c3a220a254ec3eb Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Thu, 7 Dec 2023 12:54:34 +0100 Subject: [PATCH 27/45] feat(lnm): support HEAD requests --- CHANGELOG.md | 1 + lnm/include/lnm/http/consts.h | 3 ++- lnm/src/http/lnm_http_consts.c | 3 ++- lnm/src/http/lnm_http_loop_process.c | 18 ++++++++++++++---- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ea9588b..4736fb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Allow custom & an arbitrary number of response headers * Better API for adding routes * State machine HTTP loop + * Auomatically support HEAD requests for all GET requests ## [0.2.0](https://git.rustybever.be/Chewing_Bever/lander/src/tag/0.2.0) diff --git a/lnm/include/lnm/http/consts.h b/lnm/include/lnm/http/consts.h index 89622a3..f543bbb 100644 --- a/lnm/include/lnm/http/consts.h +++ b/lnm/include/lnm/http/consts.h @@ -11,7 +11,8 @@ typedef enum lnm_http_method { lnm_http_method_post, lnm_http_method_put, lnm_http_method_patch, - lnm_http_method_delete + lnm_http_method_delete, + lnm_http_method_head, } lnm_http_method; extern const char *lnm_http_status_names[][32]; diff --git a/lnm/src/http/lnm_http_consts.c b/lnm/src/http/lnm_http_consts.c index 834816a..cfbee40 100644 --- a/lnm/src/http/lnm_http_consts.c +++ b/lnm/src/http/lnm_http_consts.c @@ -1,6 +1,7 @@ #include "lnm/http/consts.h" -const char *lnm_http_method_names[] = {"GET", "POST", "PUT", "PATCH", "DELETE"}; +const char *lnm_http_method_names[] = {"GET", "POST", "PUT", + "PATCH", "DELETE", "HEAD"}; const size_t lnm_http_method_names_len = sizeof(lnm_http_method_names) / sizeof(lnm_http_method_names[0]); diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index 8c50ec1..7c32868 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -68,8 +68,14 @@ void lnm_http_loop_process_route(lnm_http_conn *conn) { break; } - // Remember the previous match levels - int new_match_level = 2 * matched_path + (route->method == ctx->req.method); + // GET routes also automatically route HEAD requests + bool matched_method = route->method == ctx->req.method || + (route->method == lnm_http_method_get && + ctx->req.method == lnm_http_method_head); + int new_match_level = 2 * matched_path + matched_method; + + // Remember the previous match levels so we can return the correct status + // message match_level = match_level < new_match_level ? new_match_level : match_level; } @@ -227,8 +233,12 @@ void lnm_http_loop_process_write_headers(lnm_http_conn *conn) { conn->w.buf[conn->w.size] = '\n'; conn->w.size++; - ctx->state = ctx->res.body.len > 0 ? lnm_http_loop_state_write_body - : lnm_http_loop_state_finish; + // HEAD requests function exactly the same as GET requests, except that they + // skip the body writing part + ctx->state = + ctx->req.method != lnm_http_method_head && ctx->res.body.len > 0 + ? lnm_http_loop_state_write_body + : lnm_http_loop_state_finish; } } From a0954e8d07c3a29714e8fa0a3bb8066212291845 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Fri, 8 Dec 2023 22:03:47 +0100 Subject: [PATCH 28/45] fix(lnm): seemingly fix performance regression --- lnm/include/lnm/loop.h | 2 +- lnm/src/http/lnm_http_loop_process.c | 6 ++---- lnm/src/loop/lnm_loop.c | 11 +++++++---- lnm/src/loop/lnm_loop_io.c | 6 +++--- 4 files changed, 13 insertions(+), 12 deletions(-) diff --git a/lnm/include/lnm/loop.h b/lnm/include/lnm/loop.h index 8493170..6e67f68 100644 --- a/lnm/include/lnm/loop.h +++ b/lnm/include/lnm/loop.h @@ -6,7 +6,7 @@ #include "lnm/common.h" -#define LNM_LOOP_BUF_SIZE 4096 +#define LNM_LOOP_BUF_SIZE 2048 #define LNM_LOOP_INITIAL_CONNS 16 typedef enum { diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index 7c32868..1fd5530 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -341,11 +341,9 @@ void lnm_http_loop_process(lnm_http_conn *conn) { http_loop_state = ctx->state; process_fns[http_loop_state](conn); - } while ((conn->state == loop_state) && - (conn->state == state_map[http_loop_state]) && + } while ((conn->state == state_map[ctx->state]) && (http_loop_state != ctx->state)); // Check required to prevent overwriting manually set event loop state - conn->state = - conn->state == loop_state ? state_map[http_loop_state] : conn->state; + conn->state = conn->state == loop_state ? state_map[ctx->state] : conn->state; } diff --git a/lnm/src/loop/lnm_loop.c b/lnm/src/loop/lnm_loop.c index 350eb9b..a5839d4 100644 --- a/lnm/src/loop/lnm_loop.c +++ b/lnm/src/loop/lnm_loop.c @@ -76,7 +76,7 @@ lnm_err lnm_loop_accept(lnm_loop *l) { } lnm_err lnm_loop_setup(lnm_loop *l, uint16_t port) { - int listen_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); + int listen_fd = socket(AF_INET, SOCK_STREAM, 0); if (listen_fd < 0) { return lnm_err_failed_network; @@ -105,6 +105,9 @@ lnm_err lnm_loop_setup(lnm_loop *l, uint16_t port) { return lnm_err_failed_network; } + int flags = fcntl(listen_fd, F_GETFL); + fcntl(listen_fd, F_SETFL, flags | O_NONBLOCK); + l->listen_fd = listen_fd; return lnm_err_ok; @@ -116,7 +119,7 @@ lnm_err lnm_loop_run(lnm_loop *l) { } struct pollfd *poll_args = - malloc((LNM_LOOP_INITIAL_CONNS + 1) * sizeof(struct pollfd)); + calloc(LNM_LOOP_INITIAL_CONNS + 1, sizeof(struct pollfd)); size_t poll_args_cap = LNM_LOOP_INITIAL_CONNS + 1; if (poll_args == NULL) { @@ -128,7 +131,7 @@ lnm_err lnm_loop_run(lnm_loop *l) { poll_args[0].events = POLLIN; while (1) { - size_t poll_args_len = 1; + nfds_t poll_args_len = 1; // Add all open connections to the poll command for (size_t i = 0; i < l->conns.len && poll_args_len < l->conns.open + 1; @@ -176,7 +179,7 @@ lnm_err lnm_loop_run(lnm_loop *l) { } if (poll_args_cap < l->conns.open + 1) { - struct pollfd *buf = malloc((l->conns.open + 1) * sizeof(struct pollfd)); + struct pollfd *buf = calloc(l->conns.open + 1, sizeof(struct pollfd)); if (buf == NULL) { return lnm_err_failed_alloc; diff --git a/lnm/src/loop/lnm_loop_io.c b/lnm/src/loop/lnm_loop_io.c index 5d89191..363d6af 100644 --- a/lnm/src/loop/lnm_loop_io.c +++ b/lnm/src/loop/lnm_loop_io.c @@ -38,6 +38,8 @@ void lnm_loop_conn_io_req(lnm_loop *l, lnm_loop_conn *conn) { void lnm_loop_conn_io_res(lnm_loop *l, lnm_loop_conn *conn) { do { + l->data_write(conn); + ssize_t res; do { @@ -49,7 +51,7 @@ void lnm_loop_conn_io_res(lnm_loop *l, lnm_loop_conn *conn) { return; } - if (res <= 0) { + if (res < 0) { conn->state = lnm_loop_state_end; return; @@ -59,8 +61,6 @@ void lnm_loop_conn_io_res(lnm_loop *l, lnm_loop_conn *conn) { // writer function more space to work with memmove(conn->w.buf, &conn->w.buf[res], conn->w.size - res); conn->w.size -= res; - - l->data_write(conn); } while (conn->state == lnm_loop_state_res); } From cda61f5433cf92f72247c4803d4419b232704866 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 9 Dec 2023 10:37:10 +0100 Subject: [PATCH 29/45] feat(landerctl): add option to use custom config file --- CHANGELOG.md | 2 ++ landerctl/landerrc | 3 ++ landerctl/src/main.c | 76 +++++++++++++++++++++++--------------------- 3 files changed, 45 insertions(+), 36 deletions(-) create mode 100644 landerctl/landerrc diff --git a/CHANGELOG.md b/CHANGELOG.md index 4736fb6..e199b61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Better API for adding routes * State machine HTTP loop * Auomatically support HEAD requests for all GET requests +* Landerctl + * `-c` flag to use custom config file (useful for testing) ## [0.2.0](https://git.rustybever.be/Chewing_Bever/lander/src/tag/0.2.0) diff --git a/landerctl/landerrc b/landerctl/landerrc new file mode 100644 index 0000000..faa8b21 --- /dev/null +++ b/landerctl/landerrc @@ -0,0 +1,3 @@ +api_key = test +server_url = http://localhost:18080 +ca_certs_bundle = /etc/ssl/certs/ca-certificates.crt diff --git a/landerctl/src/main.c b/landerctl/src/main.c index 2e0b375..fd53944 100644 --- a/landerctl/src/main.c +++ b/landerctl/src/main.c @@ -10,25 +10,56 @@ #include "landerctl.h" const char *cfg_file_name = ".landerrc"; -const char *usage = "%s [-SPFsv] arg [key]\n"; +const char *usage = "%s [-SPFsv] [-c CONFIG_FILE] arg [key]\n"; int main(int argc, char **argv) { landerctl_ctx ctx = {0}; - char *err_msg = NULL; - - landerctl_cfg_err parse_res; const char *home_dir = getenv("HOME"); + const char *cfg_path; if (home_dir == NULL) { - parse_res = landerctl_cfg_parse(&ctx.cfg, cfg_file_name); + cfg_path = cfg_file_name; } else { - char cfg_path[strlen(home_dir) + strlen(cfg_file_name) + 2]; - sprintf(cfg_path, "%s/%s", home_dir, cfg_file_name); - - parse_res = landerctl_cfg_parse(&ctx.cfg, cfg_path); + // This is a blatant memleak if a custom config file is set, but it really + // doesn't matter for a short-lived CLI tool + char *buf = malloc(strlen(home_dir) + strlen(cfg_file_name) + 2); + sprintf(buf, "%s/%s", home_dir, cfg_file_name); + cfg_path = buf; } + opterr = 0; + int c; + + while ((c = getopt(argc, argv, "SPFsvc:")) != -1) { + switch (c) { + case 'S': + ctx.mode = landerctl_mode_short; + break; + case 'P': + ctx.mode = landerctl_mode_paste; + break; + case 'F': + ctx.mode = landerctl_mode_file; + break; + case 's': + ctx.secure = true; + break; + case 'v': + ctx.verbose = true; + break; + case 'c': + cfg_path = optarg; + break; + case '?': + printf(usage, argv[0]); + exit(2); + } + } + + char *err_msg = NULL; + landerctl_cfg_err parse_res = landerctl_cfg_parse(&ctx.cfg, cfg_path); + switch (parse_res) { case landerctl_cfg_err_ok: break; @@ -48,33 +79,6 @@ int main(int argc, char **argv) { exit(1); } - opterr = 0; - - int c; - - while ((c = getopt(argc, argv, "SPFsv")) != -1) { - switch (c) { - case 'S': - ctx.mode = landerctl_mode_short; - break; - case 'P': - ctx.mode = landerctl_mode_paste; - break; - case 'F': - ctx.mode = landerctl_mode_file; - break; - case 's': - ctx.secure = true; - break; - case 'v': - ctx.verbose = true; - break; - case '?': - printf(usage, argv[0]); - exit(2); - } - } - if (ctx.mode == landerctl_mode_none) { printf("No mode specified.\n\n"); printf(usage, argv[0]); From 4ae1355cecd597f5cbfec9fdd6abfad180f3b990 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 9 Dec 2023 12:34:02 +0100 Subject: [PATCH 30/45] feat(lander): re-add Server header --- lnm/include/lnm/http/loop.h | 3 +++ lnm/src/http/lnm_http_loop.c | 5 +++++ lnm/src/http/lnm_http_loop_process.c | 5 +++++ src/main.c | 3 +++ 4 files changed, 16 insertions(+) diff --git a/lnm/include/lnm/http/loop.h b/lnm/include/lnm/http/loop.h index 8a199e7..071e22a 100644 --- a/lnm/include/lnm/http/loop.h +++ b/lnm/include/lnm/http/loop.h @@ -85,6 +85,8 @@ lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port); void lnm_http_loop_set_api_key(lnm_http_loop *hl, const char *api_key); +void lnm_http_loop_set_server(lnm_http_loop *hl, const char *value); + /** * Represents what state an HTTP loop request is currently in. */ @@ -118,6 +120,7 @@ typedef struct lnm_http_loop_gctx { lnm_http_ctx_reset_fn ctx_reset; lnm_http_ctx_free_fn ctx_free; const char *api_key; + const char *server; void *c; } lnm_http_loop_gctx; diff --git a/lnm/src/http/lnm_http_loop.c b/lnm/src/http/lnm_http_loop.c index 0b9b812..c226b29 100644 --- a/lnm/src/http/lnm_http_loop.c +++ b/lnm/src/http/lnm_http_loop.c @@ -131,3 +131,8 @@ void lnm_http_loop_set_api_key(lnm_http_loop *hl, const char *api_key) { lnm_http_loop_gctx *gctx = hl->gctx; gctx->api_key = api_key; } + +void lnm_http_loop_set_server(lnm_http_loop *hl, const char *server) { + lnm_http_loop_gctx *gctx = hl->gctx; + gctx->server = server; +} diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index 1fd5530..e98c00b 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -158,6 +158,11 @@ void lnm_http_loop_state_process_add_headers(lnm_http_conn *conn) { lnm_http_res_add_header_len(res, lnm_http_header_content_length, buf, digits, true); + if (ctx->g->server != NULL) { + lnm_http_res_add_header(res, lnm_http_header_server, (char *)ctx->g->server, + false); + } + ctx->state = lnm_http_loop_state_write_status_line; } diff --git a/src/main.c b/src/main.c index 04bff84..4970d95 100644 --- a/src/main.c +++ b/src/main.c @@ -7,6 +7,8 @@ #include "lander.h" #include "log.h" +const char *lander_server = "lander/" LANDER_VERSION; + lnm_http_loop *loop_init(lander_gctx *gctx, const char *api_key) { lnm_http_loop *hl; lnm_http_step *step = NULL; @@ -15,6 +17,7 @@ lnm_http_loop *loop_init(lander_gctx *gctx, const char *api_key) { (lnm_http_ctx_reset_fn)lander_ctx_reset, (lnm_http_ctx_free_fn)lander_ctx_free); lnm_http_loop_set_api_key(hl, api_key); + lnm_http_loop_set_server(hl, lander_server); lnm_http_step_init(&step, lander_get_index); lnm_http_route_init_literal(&route, lnm_http_method_get, "/", step); From e44b7d0e5fb338f348bd60f792f2598913801eed Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 9 Dec 2023 12:48:32 +0100 Subject: [PATCH 31/45] fix(lander): remove small mem leak --- src/lander/lander_get.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/lander/lander_get.c b/src/lander/lander_get.c index 4b42221..bb3ca08 100644 --- a/src/lander/lander_get.c +++ b/src/lander/lander_get.c @@ -121,8 +121,10 @@ lnm_http_step_err lander_get_entry(lnm_http_conn *conn) { lsm_str *key; lsm_str_init_copy_n(&key, (char *)key_s, key_len); + lsm_error lsm_res = lsm_store_open_read(&c_ctx->entry, c_gctx->store, key); + lsm_str_free(key); - switch (lsm_store_open_read(&c_ctx->entry, c_gctx->store, key)) { + switch (lsm_res) { case lsm_error_ok: break; case lsm_error_not_found: From 3aa0ace863bb643b5aaff73e57f23a020f057ca6 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Mon, 11 Dec 2023 15:00:34 +0100 Subject: [PATCH 32/45] feat(lnm): add logging framework --- lnm/include/lnm/log.h | 47 ++++++++++++++++ lnm/src/_include/lnm/log_internal.h | 26 +++++++++ lnm/src/lnm_log.c | 86 +++++++++++++++++++++++++++++ src/main.c | 18 ++++-- 4 files changed, 171 insertions(+), 6 deletions(-) create mode 100644 lnm/include/lnm/log.h create mode 100644 lnm/src/_include/lnm/log_internal.h create mode 100644 lnm/src/lnm_log.c diff --git a/lnm/include/lnm/log.h b/lnm/include/lnm/log.h new file mode 100644 index 0000000..42f27de --- /dev/null +++ b/lnm/include/lnm/log.h @@ -0,0 +1,47 @@ +#ifndef LNM_LOG +#define LOG + +#include + +#include "lnm/common.h" + +typedef struct lnm_logger lnm_logger; + +typedef enum lnm_log_level { + lnm_log_level_debug = 0, + lnm_log_level_info, + lnm_log_level_notice, + lnm_log_level_warning, + lnm_log_level_error, + lnm_log_level_critical +} lnm_log_level; + +extern const char *lnm_log_level_names[]; + +/** + * Initialize the global logger. + */ +lnm_err lnm_log_init_global(); + +/** + * Register stdout as one of the streams for the global logger. + */ +lnm_err lnm_log_register_stdout(lnm_log_level level); + +void lnm_log(lnm_log_level level, const char *section, const char *fmt, ...) + __attribute__((format(printf, 3, 4))); + +#define lnm_ldebug(section, fmt, ...) \ + lnm_log(lnm_log_level_debug, section, fmt, __VA_ARGS__) +#define lnm_linfo(section, fmt, ...) \ + lnm_log(lnm_log_level_info, section, fmt, __VA_ARGS__) +#define lnm_lnotice(section, fmt, ...) \ + lnm_log(lnm_log_level_notice, section, fmt, __VA_ARGS__) +#define lnm_lwarning(section, fmt, ...) \ + lnm_log(lnm_log_level_warning, section, fmt, __VA_ARGS__) +#define lnm_lerror(section, fmt, ...) \ + lnm_log(lnm_log_level_error, section, fmt, __VA_ARGS__) +#define lnm_lcritical(section, fmt, ...) \ + lnm_log(lnm_log_level_critical, section, fmt, __VA_ARGS__) + +#endif diff --git a/lnm/src/_include/lnm/log_internal.h b/lnm/src/_include/lnm/log_internal.h new file mode 100644 index 0000000..ebcd243 --- /dev/null +++ b/lnm/src/_include/lnm/log_internal.h @@ -0,0 +1,26 @@ +#ifndef LNM_LOG_INTERNAL +#define LNM_LOG_INTERNAL + +#include "lnm/log.h" + +typedef enum lnm_logger_stream_type { + lnm_logger_stream_type_file = 0 +} lnm_logger_stream_type; + +typedef struct lnm_logger_stream { + void *ptr; + lnm_logger_stream_type type; + lnm_log_level level; +} lnm_logger_stream; + +struct lnm_logger { + struct { + lnm_logger_stream **arr; + size_t len; + } streams; +}; + +lnm_err lnm_logger_stream_register(lnm_logger *logger, + lnm_logger_stream *stream); + +#endif diff --git a/lnm/src/lnm_log.c b/lnm/src/lnm_log.c new file mode 100644 index 0000000..15fb00f --- /dev/null +++ b/lnm/src/lnm_log.c @@ -0,0 +1,86 @@ +#include +#include + +#include "lnm/common.h" +#include "lnm/log_internal.h" + +const char *lnm_log_level_names[] = {"DEBUG ", "INFO ", "NOTICE ", + "WARNING ", "ERROR ", "CRITICAL"}; + +lnm_logger *global_logger = NULL; + +lnm_err lnm_log_init_global() { + global_logger = calloc(1, sizeof(lnm_logger)); + + return global_logger == NULL ? lnm_err_failed_alloc : lnm_err_ok; +} + +lnm_err lnm_logger_stream_register(lnm_logger *logger, + lnm_logger_stream *stream) { + lnm_logger_stream **new = + logger->streams.len == 0 + ? malloc(sizeof(lnm_logger_stream *)) + : realloc(logger->streams.arr, + (logger->streams.len + 1) * sizeof(lnm_logger_stream *)); + + if (new == NULL) { + return lnm_err_failed_alloc; + } + + new[logger->streams.len] = stream; + logger->streams.arr = new; + logger->streams.len++; + + return lnm_err_ok; +} + +lnm_err lnm_log_register_stdout(lnm_log_level level) { + lnm_logger_stream *stream = malloc(sizeof(lnm_logger_stream)); + + if (stream == NULL) { + return lnm_err_failed_alloc; + } + + stream->type = lnm_logger_stream_type_file; + stream->ptr = stdout; + stream->level = level; + + LNM_RES2(lnm_logger_stream_register(global_logger, stream), free(stream)); + + return lnm_err_ok; +} + +void lnm_vlog(lnm_log_level level, const char *section, const char *fmt, + va_list ap) { + char date_str[32]; + + time_t now = time(NULL); + strftime(date_str, sizeof(date_str) - 1, "%Y-%m-%d %H:%M:%S", + localtime(&now)); + + for (size_t i = 0; i < global_logger->streams.len; i++) { + lnm_logger_stream *stream = global_logger->streams.arr[i]; + + if (level < stream->level) { + continue; + } + + switch (stream->type) { + case lnm_logger_stream_type_file: + fprintf(stream->ptr, "[%s][%s][%s] ", date_str, + lnm_log_level_names[level], section); + vfprintf(stream->ptr, fmt, ap); + fprintf(stream->ptr, "\n"); + break; + } + + va_end(ap); + } +} + +void lnm_log(lnm_log_level level, const char *section, const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + lnm_vlog(level, section, fmt, ap); + va_end(ap); +} diff --git a/src/main.c b/src/main.c index 4970d95..d25aec3 100644 --- a/src/main.c +++ b/src/main.c @@ -3,6 +3,7 @@ #include #include "lnm/http/loop.h" +#include "lnm/log.h" #include "lander.h" #include "log.h" @@ -61,7 +62,8 @@ lnm_http_loop *loop_init(lander_gctx *gctx, const char *api_key) { #define ENV(var, env_var) \ const char *var = getenv(env_var); \ if (var == NULL) { \ - critical(1, "Missing environment variable %s", env_var); \ + lnm_lcritical("main", "Missing environment variable %s", env_var); \ + exit(1); \ } #define ENV_OPT(var, env_var, default) \ @@ -71,9 +73,11 @@ lnm_http_loop *loop_init(lander_gctx *gctx, const char *api_key) { } int main() { - setvbuf(stdout, NULL, _IONBF, 0); srand(time(NULL)); + lnm_log_init_global(); + lnm_log_register_stdout(lnm_log_level_debug); + ENV(api_key, "LANDER_API_KEY"); ENV_OPT(port_str, "LANDER_PORT", "18080"); ENV_OPT(data_dir_s, "LANDER_DATA_DIR", "."); @@ -81,7 +85,8 @@ int main() { int port = atoi(port_str); if (port <= 0 || port >= 1 << 16) { - critical(1, "Invalid TCP port %s", port_str); + lnm_lcritical("main", "Invalid TCP port %s", port_str); + exit(1); } lander_gctx *c_gctx = lander_gctx_init(); @@ -90,13 +95,14 @@ int main() { lsm_str *data_dir; lsm_str_init_copy(&data_dir, (char *)data_dir_s); - info("Initializing store from path '%s'", data_dir_s); + lnm_linfo("main", "Initializing store from path '%s'", data_dir_s); if (lsm_store_load(&c_gctx->store, data_dir) != lsm_error_ok) { - critical(2, "Failed to load existing store."); + lnm_lcritical("main", "%s", "Failed to load existing store."); } - info("Store loaded containing %lu entries", lsm_store_size(c_gctx->store)); + lnm_linfo("main", "Store loaded containing %lu entries", + lsm_store_size(c_gctx->store)); lnm_http_loop *hl = loop_init(c_gctx, api_key); lnm_http_loop_run(hl, port); From dde83584a77298a34ab1118c251878dda7febefd Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Mon, 11 Dec 2023 15:34:49 +0100 Subject: [PATCH 33/45] feat(lnm): add some internal logging --- lnm/src/http/lnm_http_loop_process.c | 23 +++++++++++++++++++---- lnm/src/loop/lnm_loop.c | 11 +++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index e98c00b..4cfa6f7 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -6,9 +6,12 @@ #include "lnm/http/loop.h" #include "lnm/http/loop_internal.h" #include "lnm/http/req.h" +#include "lnm/log.h" #include "lnm/loop.h" #include "lnm/loop_internal.h" +static const char *section = "http"; + /* static const lnm_http_loop_state lnm_http_loop_state_first_req = * lnm_http_loop_state_parse_req; */ static const lnm_http_loop_state lnm_http_loop_state_first_res = @@ -29,10 +32,15 @@ void lnm_http_loop_process_parse_req(lnm_http_conn *conn) { // If the request is already the size of the read buffer, we close the // request. Otherwise, we wait for anything read if (conn->r.size - conn->r.read == LNM_LOOP_BUF_SIZE) { + lnm_linfo(section, "Received request larger than buffer (%i bytes)", + LNM_LOOP_BUF_SIZE); + conn->state = lnm_loop_state_end; } break; case lnm_http_parse_err_invalid: + lnm_linfo(section, "%s", "Received invalid request"); + conn->state = lnm_loop_state_end; break; case lnm_http_parse_err_unknown_method: @@ -40,6 +48,10 @@ void lnm_http_loop_process_parse_req(lnm_http_conn *conn) { ctx->state = lnm_http_loop_state_first_res; break; } + + lnm_linfo(section, "%s %.*s HTTP/1.%i", + lnm_http_method_names[ctx->req.method], (int)ctx->req.path.len, + ctx->req.path.s, ctx->req.minor_version); } void lnm_http_loop_process_route(lnm_http_conn *conn) { @@ -163,6 +175,13 @@ void lnm_http_loop_state_process_add_headers(lnm_http_conn *conn) { false); } + if (res->status == 0) { + res->status = lnm_http_status_ok; + } + + lnm_linfo(section, "%i %s", res->status, + lnm_http_status_names[res->status / 100 - 1][res->status % 100]); + ctx->state = lnm_http_loop_state_write_status_line; } @@ -172,10 +191,6 @@ void lnm_http_loop_process_write_status_line(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_res *res = &ctx->res; - if (res->status == 0) { - res->status = lnm_http_status_ok; - } - const char *response_type_name = lnm_http_status_names[res->status / 100 - 1][res->status % 100]; diff --git a/lnm/src/loop/lnm_loop.c b/lnm/src/loop/lnm_loop.c index a5839d4..4126eb0 100644 --- a/lnm/src/loop/lnm_loop.c +++ b/lnm/src/loop/lnm_loop.c @@ -6,8 +6,11 @@ #include #include "lnm/common.h" +#include "lnm/log.h" #include "lnm/loop_internal.h" +static const char *section = "loop"; + lnm_err lnm_loop_init(lnm_loop **out, void *gctx, lnm_err (*ctx_init)(void **out, void *gctx), void (*ctx_free)(void *ctx), @@ -34,6 +37,8 @@ lnm_err lnm_loop_accept(lnm_loop *l) { int conn_fd = accept(l->listen_fd, NULL, NULL); if (conn_fd < 0) { + lnm_lcritical(section, "accept failed: %i", conn_fd); + return lnm_err_failed_network; } @@ -72,6 +77,8 @@ lnm_err lnm_loop_accept(lnm_loop *l) { l->conns.open++; + lnm_ldebug(section, "connection opened with fd %i", conn_fd); + return lnm_err_ok; } @@ -130,6 +137,8 @@ lnm_err lnm_loop_run(lnm_loop *l) { poll_args[0].fd = l->listen_fd; poll_args[0].events = POLLIN; + lnm_linfo(section, "started on fd %i", l->listen_fd); + while (1) { nfds_t poll_args_len = 1; @@ -171,6 +180,8 @@ lnm_err lnm_loop_run(lnm_loop *l) { close(conn->fd); l->conns.open--; + lnm_ldebug(section, "connection closed with fd %i", conn->fd); + lnm_loop_conn_free(l, conn); } From 8e0477c34b01169b3cc25e55e0b149d9ed92b13f Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Mon, 11 Dec 2023 15:39:31 +0100 Subject: [PATCH 34/45] feat(lander): fully switch to lnm logger --- src/lander/lander_get.c | 5 +++-- src/lander/lander_post.c | 3 ++- src/log.c | 31 ------------------------------- 3 files changed, 5 insertions(+), 34 deletions(-) delete mode 100644 src/log.c diff --git a/src/lander/lander_get.c b/src/lander/lander_get.c index bb3ca08..24e93ba 100644 --- a/src/lander/lander_get.c +++ b/src/lander/lander_get.c @@ -1,12 +1,13 @@ #include +#include #include "lnm/http/consts.h" #include "lnm/http/loop.h" #include "lnm/loop.h" +#include "lnm/log.h" #include "lsm/store.h" #include "lander.h" -#include "log.h" static const char index_page[] = "\n" @@ -39,7 +40,7 @@ lnm_http_step_err lander_get_redirect(lnm_http_conn *conn) { // This shouldn't be able to happen if (lsm_entry_attr_get(&url_attr_val, c_ctx->entry, lander_attr_type_url) != lsm_error_ok) { - error("Entry of type redirect detected without URL attribute"); + lnm_lerror("lander", "%s", "Entry of type redirect detected without URL attribute"); ctx->res.status = lnm_http_status_internal_server_error; lsm_entry_close(c_ctx->entry); diff --git a/src/lander/lander_post.c b/src/lander/lander_post.c index 4bda832..1244cb7 100644 --- a/src/lander/lander_post.c +++ b/src/lander/lander_post.c @@ -1,8 +1,9 @@ +#include + #include "lnm/loop.h" #include "lsm/store.h" #include "lander.h" -#include "log.h" static void randomize_key(char *key, int len) { size_t charset_len = strlen(lander_key_charset); diff --git a/src/log.c b/src/log.c deleted file mode 100644 index 1ca0bc7..0000000 --- a/src/log.c +++ /dev/null @@ -1,31 +0,0 @@ -#include - -#include "log.h" - -const char *log_level_names[] = {"DEBUG", "INFO ", "WARN ", "ERROR", - "CRITICAL"}; - -log_level _log_level = log_level_debug; - -void _lander_log(log_level level, FILE *f, const char *fmt, ...) { - if (level < _log_level) { - return; - } - - // Log to stdout by default - f = (f == NULL) ? stdout : f; - - char date_str[32]; - - time_t now = time(NULL); - strftime(date_str, sizeof(date_str) - 1, "%Y-%m-%d %H:%M:%S", - localtime(&now)); - fprintf(f, "[%s][%s] ", date_str, log_level_names[level]); - - va_list ap; - va_start(ap, fmt); - vfprintf(f, fmt, ap); - va_end(ap); - - fprintf(f, "\n"); -} From 89cc41f28a7af657979df511ffa6136f411844e5 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Tue, 12 Dec 2023 12:15:21 +0100 Subject: [PATCH 35/45] chore(lander): move a log line --- CHANGELOG.md | 2 +- lnm/src/http/lnm_http_loop_process.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e199b61..92635f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,7 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Allow custom & an arbitrary number of response headers * Better API for adding routes * State machine HTTP loop - * Auomatically support HEAD requests for all GET requests + * Automatically support HEAD requests for all GET requests * Landerctl * `-c` flag to use custom config file (useful for testing) diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index 4cfa6f7..62cea3f 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -27,6 +27,10 @@ void lnm_http_loop_process_parse_req(lnm_http_conn *conn) { case lnm_http_parse_err_ok: conn->r.read += ctx->req.len; ctx->state = lnm_http_loop_state_route; + + lnm_linfo(section, "%s %.*s HTTP/1.%i", + lnm_http_method_names[ctx->req.method], (int)ctx->req.path.len, + ctx->req.path.s, ctx->req.minor_version); break; case lnm_http_parse_err_incomplete: // If the request is already the size of the read buffer, we close the @@ -48,10 +52,6 @@ void lnm_http_loop_process_parse_req(lnm_http_conn *conn) { ctx->state = lnm_http_loop_state_first_res; break; } - - lnm_linfo(section, "%s %.*s HTTP/1.%i", - lnm_http_method_names[ctx->req.method], (int)ctx->req.path.len, - ctx->req.path.s, ctx->req.minor_version); } void lnm_http_loop_process_route(lnm_http_conn *conn) { From d53a9499466fffa70e202ea8d705c7ba9d86cc65 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Tue, 12 Dec 2023 22:22:55 +0100 Subject: [PATCH 36/45] feat(lnm): switch to epoll --- CHANGELOG.md | 2 + lnm/include/lnm/http/loop.h | 2 +- lnm/include/lnm/loop.h | 11 +- lnm/src/http/lnm_http_loop.c | 4 +- lnm/src/http/lnm_http_loop_process.c | 4 +- lnm/src/loop/lnm_loop.c | 182 +++++++++++++++------------ src/lander/lander_get.c | 5 +- src/main.c | 2 +- 8 files changed, 117 insertions(+), 95 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92635f8..6a64beb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Better API for adding routes * State machine HTTP loop * Automatically support HEAD requests for all GET requests + * Event loop uses `epoll` instead of `poll` + * Configurable multithreading using `epoll` * Landerctl * `-c` flag to use custom config file (useful for testing) diff --git a/lnm/include/lnm/http/loop.h b/lnm/include/lnm/http/loop.h index 071e22a..8e36caa 100644 --- a/lnm/include/lnm/http/loop.h +++ b/lnm/include/lnm/http/loop.h @@ -81,7 +81,7 @@ lnm_err lnm_http_route_init_regex(lnm_http_route **out, lnm_http_method method, */ lnm_err lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route); -lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port); +lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port, int thread_count); void lnm_http_loop_set_api_key(lnm_http_loop *hl, const char *api_key); diff --git a/lnm/include/lnm/loop.h b/lnm/include/lnm/loop.h index 6e67f68..7ca372c 100644 --- a/lnm/include/lnm/loop.h +++ b/lnm/include/lnm/loop.h @@ -1,13 +1,13 @@ #ifndef LNM_LOOP #define LNM_LOOP +#include #include #include #include "lnm/common.h" #define LNM_LOOP_BUF_SIZE 2048 -#define LNM_LOOP_INITIAL_CONNS 16 typedef enum { lnm_loop_state_req = 0, @@ -32,11 +32,8 @@ typedef struct lnm_loop_conn { typedef struct lnm_loop { int listen_fd; - struct { - lnm_loop_conn **arr; - size_t len; - size_t open; - } conns; + int epoll_fd; + atomic_int open; void *gctx; lnm_err (*ctx_init)(void **out, void *gctx); void (*ctx_free)(void *ctx); @@ -52,6 +49,6 @@ lnm_err lnm_loop_init(lnm_loop **out, void *gctx, lnm_err lnm_loop_setup(lnm_loop *l, uint16_t port); -lnm_err lnm_loop_run(lnm_loop *l); +lnm_err lnm_loop_run(lnm_loop *l, int thread_count); #endif diff --git a/lnm/src/http/lnm_http_loop.c b/lnm/src/http/lnm_http_loop.c index c226b29..784126e 100644 --- a/lnm/src/http/lnm_http_loop.c +++ b/lnm/src/http/lnm_http_loop.c @@ -122,9 +122,9 @@ lnm_err lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route) { return lnm_err_ok; } -lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port) { +lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port, int thread_count) { LNM_RES(lnm_loop_setup(hl, port)); - return lnm_loop_run(hl); + return lnm_loop_run(hl, thread_count); } void lnm_http_loop_set_api_key(lnm_http_loop *hl, const char *api_key) { diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index 62cea3f..63017f1 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -29,8 +29,8 @@ void lnm_http_loop_process_parse_req(lnm_http_conn *conn) { ctx->state = lnm_http_loop_state_route; lnm_linfo(section, "%s %.*s HTTP/1.%i", - lnm_http_method_names[ctx->req.method], (int)ctx->req.path.len, - ctx->req.path.s, ctx->req.minor_version); + lnm_http_method_names[ctx->req.method], (int)ctx->req.path.len, + ctx->req.path.s, ctx->req.minor_version); break; case lnm_http_parse_err_incomplete: // If the request is already the size of the read buffer, we close the diff --git a/lnm/src/loop/lnm_loop.c b/lnm/src/loop/lnm_loop.c index 4126eb0..6c9a854 100644 --- a/lnm/src/loop/lnm_loop.c +++ b/lnm/src/loop/lnm_loop.c @@ -1,12 +1,14 @@ #include #include -#include +#include #include #include +#include #include #include "lnm/common.h" #include "lnm/log.h" +#include "lnm/loop.h" #include "lnm/loop_internal.h" static const char *section = "loop"; @@ -47,35 +49,18 @@ lnm_err lnm_loop_accept(lnm_loop *l) { flags |= O_NONBLOCK; fcntl(conn_fd, F_SETFL, flags); - // Append connection to list of connections - if ((size_t)conn_fd >= l->conns.len) { - // We always calloc as a realloc might introduce unitialized values in the - // array - lnm_loop_conn **new = calloc(sizeof(lnm_loop_conn *), conn_fd + 1); - - if (new == NULL) { - close(conn_fd); - - return lnm_err_failed_alloc; - } - - if (l->conns.len > 0) { - memcpy(new, l->conns.arr, l->conns.len * sizeof(lnm_loop_conn *)); - free(l->conns.arr); - } - - l->conns.arr = new; - l->conns.len = conn_fd + 1; - } - lnm_loop_conn *conn; LNM_RES2(lnm_loop_conn_init(&conn, l), close(conn_fd)); - l->conns.arr[conn_fd] = conn; conn->fd = conn_fd; conn->state = lnm_loop_state_req; - l->conns.open++; + struct epoll_event event = {.data.ptr = conn, + .events = EPOLLIN | EPOLLET | EPOLLONESHOT}; + + epoll_ctl(l->epoll_fd, EPOLL_CTL_ADD, conn_fd, &event); + + l->open++; lnm_ldebug(section, "connection opened with fd %i", conn_fd); @@ -115,96 +100,133 @@ lnm_err lnm_loop_setup(lnm_loop *l, uint16_t port) { int flags = fcntl(listen_fd, F_GETFL); fcntl(listen_fd, F_SETFL, flags | O_NONBLOCK); + int epoll_fd = epoll_create1(0); + + if (epoll_fd < 0) { + return lnm_err_failed_network; + } + + struct epoll_event event = { + // The listening socket is marked using a NULL data field + .data.ptr = NULL, + .events = EPOLLIN | EPOLLET | EPOLLONESHOT}; + + res = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event); + + if (res < 0) { + return lnm_err_failed_network; + } + l->listen_fd = listen_fd; + l->epoll_fd = epoll_fd; return lnm_err_ok; } -lnm_err lnm_loop_run(lnm_loop *l) { - if (l->listen_fd == 0) { - return lnm_err_not_setup; - } +typedef struct lnm_loop_thread_args { + lnm_loop *l; + int id; + int thread_count; +} lnm_loop_thread_args; - struct pollfd *poll_args = - calloc(LNM_LOOP_INITIAL_CONNS + 1, sizeof(struct pollfd)); - size_t poll_args_cap = LNM_LOOP_INITIAL_CONNS + 1; +lnm_err lnm_loop_run_thread(lnm_loop_thread_args *args) { + lnm_loop *l = args->l; + int thread_id = args->id; + int thread_count = args->thread_count; - if (poll_args == NULL) { + struct epoll_event *events = calloc(1, sizeof(struct epoll_event)); + int events_cap = 1; + + if (events == NULL) { return lnm_err_failed_alloc; } - // First argument is listening socket - poll_args[0].fd = l->listen_fd; - poll_args[0].events = POLLIN; + lnm_linfo(section, "thread %i started", thread_id); - lnm_linfo(section, "started on fd %i", l->listen_fd); + struct epoll_event listen_event = { + .data.ptr = NULL, .events = EPOLLIN | EPOLLET | EPOLLONESHOT}; while (1) { - nfds_t poll_args_len = 1; - - // Add all open connections to the poll command - for (size_t i = 0; i < l->conns.len && poll_args_len < l->conns.open + 1; - i++) { - const lnm_loop_conn *conn = l->conns.arr[i]; - - if (conn == NULL) { - continue; - } - - poll_args[poll_args_len].fd = conn->fd; - poll_args[poll_args_len].events = - ((conn->state == lnm_loop_state_req) ? POLLIN : POLLOUT) | POLLERR; - - poll_args_len++; - } - - int polled = poll(poll_args, poll_args_len, -1); + int polled = epoll_wait(l->epoll_fd, events, events_cap, -1); + lnm_ldebug(section, "polled (thread %i): %i", thread_id, polled); if (polled < 0) { return lnm_err_failed_poll; } - if (poll_args[0].revents) { - lnm_loop_accept(l); - polled--; - } - - for (size_t i = 1; i < poll_args_len && polled > 0; i++) { - if (poll_args[i].revents) { - lnm_loop_conn *conn = l->conns.arr[poll_args[i].fd]; + for (int i = 0; i < polled; i++) { + if (events[i].data.ptr == NULL) { + lnm_loop_accept(l); + epoll_ctl(l->epoll_fd, EPOLL_CTL_MOD, l->listen_fd, &listen_event); + } else { + lnm_loop_conn *conn = events[i].data.ptr; lnm_loop_conn_io(l, conn); if (conn->state == lnm_loop_state_end) { - l->conns.arr[conn->fd] = NULL; - close(conn->fd); - - l->conns.open--; - lnm_ldebug(section, "connection closed with fd %i", conn->fd); + int conn_fd = conn->fd; lnm_loop_conn_free(l, conn); - } + close(conn_fd); + l->open--; - polled--; + epoll_ctl(l->epoll_fd, EPOLL_CTL_DEL, conn_fd, NULL); + + lnm_ldebug(section, "connection closed with fd %i", conn_fd); + } else { + struct epoll_event event = { + .data.ptr = conn, + .events = + (conn->state == lnm_loop_state_req ? EPOLLIN : EPOLLOUT) | + EPOLLET | EPOLLONESHOT}; + epoll_ctl(l->epoll_fd, EPOLL_CTL_MOD, conn->fd, &event); + } } } - if (poll_args_cap < l->conns.open + 1) { - struct pollfd *buf = calloc(l->conns.open + 1, sizeof(struct pollfd)); + int open = l->open; + int cap_per_thread = + open + 1 > thread_count ? (open + 1) / thread_count : 1; - if (buf == NULL) { + if (cap_per_thread > events_cap) { + struct epoll_event *new_events = + malloc(cap_per_thread * sizeof(struct epoll_event)); + + if (new_events == NULL) { return lnm_err_failed_alloc; } - buf[0].fd = l->listen_fd; - buf[0].events = POLLIN; - - free(poll_args); - poll_args = buf; - - poll_args_cap = l->conns.open + 1; + free(events); + events = new_events; + events_cap = cap_per_thread; } } return lnm_err_ok; } + +lnm_err lnm_loop_run(lnm_loop *l, int thread_count) { + if (l->epoll_fd == 0) { + return lnm_err_not_setup; + } + + lnm_loop_thread_args args[thread_count]; + + for (int i = 1; i < thread_count; i++) { + args[i].l = l; + args[i].id = i; + args[i].thread_count = thread_count; + + pthread_t thread; + pthread_create(&thread, NULL, (void *(*)(void *))lnm_loop_run_thread, + &args[i]); + } + + args[0].l = l; + args[0].id = 0; + args[0].thread_count = thread_count; + + lnm_loop_run_thread(&args[0]); + + return lnm_err_ok; +} diff --git a/src/lander/lander_get.c b/src/lander/lander_get.c index 24e93ba..0e90086 100644 --- a/src/lander/lander_get.c +++ b/src/lander/lander_get.c @@ -3,8 +3,8 @@ #include "lnm/http/consts.h" #include "lnm/http/loop.h" -#include "lnm/loop.h" #include "lnm/log.h" +#include "lnm/loop.h" #include "lsm/store.h" #include "lander.h" @@ -40,7 +40,8 @@ lnm_http_step_err lander_get_redirect(lnm_http_conn *conn) { // This shouldn't be able to happen if (lsm_entry_attr_get(&url_attr_val, c_ctx->entry, lander_attr_type_url) != lsm_error_ok) { - lnm_lerror("lander", "%s", "Entry of type redirect detected without URL attribute"); + lnm_lerror("lander", "%s", + "Entry of type redirect detected without URL attribute"); ctx->res.status = lnm_http_status_internal_server_error; lsm_entry_close(c_ctx->entry); diff --git a/src/main.c b/src/main.c index d25aec3..6cacf84 100644 --- a/src/main.c +++ b/src/main.c @@ -104,7 +104,7 @@ int main() { lnm_linfo("main", "Store loaded containing %lu entries", lsm_store_size(c_gctx->store)); lnm_http_loop *hl = loop_init(c_gctx, api_key); - lnm_http_loop_run(hl, port); + lnm_http_loop_run(hl, port, 1); /* http_loop *hl = http_loop_init( */ /* lander_routes, sizeof(lander_routes) / sizeof(lander_routes[0]), From 8bdf52da0d7a1d4ea5598adba8fdb1a55bdd62a7 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Fri, 22 Dec 2023 20:06:06 +0100 Subject: [PATCH 37/45] feat(lnm): move request data to separate buffer if read buffer changes --- lnm/include/lnm/http/req.h | 29 +++++++++++++++++--- lnm/src/http/lnm_http_loop_process.c | 34 ++++++++++++++++++----- lnm/src/http/lnm_http_req.c | 40 ++++++++++++++++++++-------- src/lander/lander_delete.c | 3 ++- src/lander/lander_get.c | 3 ++- src/lander/lander_post.c | 3 ++- 6 files changed, 88 insertions(+), 24 deletions(-) diff --git a/lnm/include/lnm/http/req.h b/lnm/include/lnm/http/req.h index e40d388..06f2743 100644 --- a/lnm/include/lnm/http/req.h +++ b/lnm/include/lnm/http/req.h @@ -13,24 +13,39 @@ #define LNM_HTTP_MAX_REQ_HEADERS 32 #define LNM_HTTP_MAX_REGEX_GROUPS 4 +typedef struct lnm_http_req_header { + struct { + size_t o; + size_t len; + } name; + struct { + size_t o; + size_t len; + } value; +} lnm_http_req_header; + /** * Represents the parsed HTTP request */ typedef struct lnm_http_req { - size_t len; + struct { + char *s; + size_t len; + bool owned; + } buf; int minor_version; lnm_http_method method; struct { - const char *s; + size_t o; size_t len; regmatch_t groups[LNM_HTTP_MAX_REGEX_GROUPS]; } path; struct { - const char *s; + size_t o; size_t len; } query; struct { - struct phr_header arr[LNM_HTTP_MAX_REQ_HEADERS]; + lnm_http_req_header arr[LNM_HTTP_MAX_REQ_HEADERS]; size_t len; } headers; struct { @@ -68,6 +83,9 @@ void lnm_http_req_reset(lnm_http_req *req); /** * Retrieve a specific header from the request. * + * Pointers retrieved from this function should never be used between step + * functions; simply request the header again if you need to. + * * @param out where to write pointer to header value * @param out_len where to store length of out value * @param req request to look for header in @@ -79,6 +97,9 @@ lnm_err lnm_http_req_header_get(const char **out, size_t *out_len, /** * Retrieve a specific header from the request by specifying its name. * + * Pointers retrieved from this function should never be used between step + * functions; simply request the header again if you need to. + * * @param out where to write pointer to header value * @param out_len where to store length of out value * @param req request to look for header in diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index 63017f1..e16e18c 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -25,12 +25,12 @@ void lnm_http_loop_process_parse_req(lnm_http_conn *conn) { switch (res) { case lnm_http_parse_err_ok: - conn->r.read += ctx->req.len; + conn->r.read += ctx->req.buf.len; ctx->state = lnm_http_loop_state_route; lnm_linfo(section, "%s %.*s HTTP/1.%i", lnm_http_method_names[ctx->req.method], (int)ctx->req.path.len, - ctx->req.path.s, ctx->req.minor_version); + ctx->req.buf.s + ctx->req.path.o, ctx->req.minor_version); break; case lnm_http_parse_err_incomplete: // If the request is already the size of the read buffer, we close the @@ -70,12 +70,12 @@ void lnm_http_loop_process_route(lnm_http_conn *conn) { switch (route->type) { case lnm_http_route_type_literal: - matched_path = - strncmp(route->route.s, ctx->req.path.s, ctx->req.path.len) == 0; + matched_path = strncmp(route->route.s, ctx->req.buf.s + ctx->req.path.o, + ctx->req.path.len) == 0; break; case lnm_http_route_type_regex: matched_path = - regexec(route->route.regex, ctx->req.path.s, + regexec(route->route.regex, ctx->req.buf.s + ctx->req.path.o, LNM_HTTP_MAX_REGEX_GROUPS, ctx->req.path.groups, 0) == 0; break; } @@ -345,7 +345,7 @@ lnm_loop_state state_map[] = { }; void lnm_http_loop_process(lnm_http_conn *conn) { - const lnm_http_loop_ctx *ctx = conn->ctx; + lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_loop_state http_loop_state; lnm_loop_state loop_state = conn->state; @@ -366,4 +366,26 @@ void lnm_http_loop_process(lnm_http_conn *conn) { // Check required to prevent overwriting manually set event loop state conn->state = conn->state == loop_state ? state_map[ctx->state] : conn->state; + + // We move the request to a dedicated buffer if the read buffer needs to be + // reused + if ((conn->state == lnm_loop_state_req) && (conn->state == loop_state) && + (!ctx->req.buf.owned) && (ctx->req.buf.len > 0)) { + char *buf = malloc(ctx->req.buf.len); + + if (buf == NULL) { + lnm_lerror(section, + "Failed to allocate request buffer; closing connection %i", + conn->fd); + + conn->state = lnm_loop_state_end; + } else { + memcpy(buf, ctx->req.buf.s, ctx->req.buf.len); + ctx->req.buf.s = buf; + ctx->req.buf.owned = true; + + lnm_ldebug(section, "Allocated request buffer for connection %i", + conn->fd); + } + } } diff --git a/lnm/src/http/lnm_http_req.c b/lnm/src/http/lnm_http_req.c index 853a33e..50be3af 100644 --- a/lnm/src/http/lnm_http_req.c +++ b/lnm/src/http/lnm_http_req.c @@ -11,15 +11,15 @@ lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, const char *method; char *path; size_t method_len, path_len; - - req->headers.len = LNM_HTTP_MAX_REQ_HEADERS; + size_t num_headers = LNM_HTTP_MAX_REQ_HEADERS; + struct phr_header headers[LNM_HTTP_MAX_REQ_HEADERS]; int req_len = phr_parse_request( buf, len, &method, &method_len, (const char **)&path, &path_len, - &req->minor_version, req->headers.arr, &req->headers.len, req->len); + &req->minor_version, headers, &num_headers, req->buf.len); if (req_len == -1) { - req->len = len; + req->buf.len = len; return lnm_http_parse_err_invalid; } else if (req_len == -2) { @@ -45,7 +45,7 @@ lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, // Only store query if the path doesn't simply end with a question mark if ((question_mark != NULL) && (path_len - (question_mark + 1 - path) > 0)) { - req->query.s = question_mark + 1; + req->query.o = question_mark + 1 - buf; req->query.len = path_len - (question_mark + 1 - path); path_len = question_mark - path; @@ -56,9 +56,22 @@ lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, path[path_len] = '\0'; } + // Also migrate headers to offset-based + for (size_t i = 0; i < num_headers; i++) { + req->headers.arr[i].name.o = headers[i].name - buf; + req->headers.arr[i].name.len = headers[i].name_len; + req->headers.arr[i].value.o = headers[i].value - buf; + req->headers.arr[i].value.len = headers[i].value_len; + } + + req->headers.len = num_headers; + req->path.len = path_len; - req->path.s = path; - req->len = req_len; + req->path.o = path - buf; + + req->buf.len = req_len; + req->buf.s = buf; + req->buf.owned = false; return lnm_http_parse_err_ok; } @@ -68,6 +81,10 @@ void lnm_http_req_reset(lnm_http_req *req) { free(req->body.buf); } + if (req->buf.owned) { + free(req->buf.s); + } + memset(req, 0, sizeof(lnm_http_req)); } @@ -82,11 +99,12 @@ lnm_err lnm_http_req_header_get_s(const char **out, size_t *out_len, size_t name_len = strlen(name); for (size_t i = 0; i < req->headers.len; i++) { - const struct phr_header *header = &req->headers.arr[i]; + const lnm_http_req_header *header = &req->headers.arr[i]; - if (lnm_strnieq(header->name, header->name_len, name, name_len)) { - *out = header->value; - *out_len = header->value_len; + if (lnm_strnieq(req->buf.s + header->name.o, header->name.len, name, + name_len)) { + *out = req->buf.s + header->value.o; + *out_len = header->value.len; return lnm_err_ok; } diff --git a/src/lander/lander_delete.c b/src/lander/lander_delete.c index 349fac6..f773ab5 100644 --- a/src/lander/lander_delete.c +++ b/src/lander/lander_delete.c @@ -8,7 +8,8 @@ lnm_http_step_err lander_remove_entry(lnm_http_conn *conn) { lnm_http_loop_gctx *gctx = ctx->g; lander_gctx *c_gctx = gctx->c; - const char *key_s = &ctx->req.path.s[ctx->req.path.groups[1].rm_so]; + const char *key_s = + &ctx->req.buf.s[ctx->req.path.o + ctx->req.path.groups[1].rm_so]; int key_len = ctx->req.path.groups[1].rm_eo - ctx->req.path.groups[1].rm_so; lsm_str *key; diff --git a/src/lander/lander_get.c b/src/lander/lander_get.c index 0e90086..f9d085f 100644 --- a/src/lander/lander_get.c +++ b/src/lander/lander_get.c @@ -118,7 +118,8 @@ lnm_http_step_err lander_get_entry(lnm_http_conn *conn) { lnm_http_loop_gctx *gctx = ctx->g; lander_gctx *c_gctx = gctx->c; - const char *key_s = &ctx->req.path.s[ctx->req.path.groups[1].rm_so]; + const char *key_s = + &ctx->req.buf.s[ctx->req.path.o + ctx->req.path.groups[1].rm_so]; int key_len = ctx->req.path.groups[1].rm_eo - ctx->req.path.groups[1].rm_so; lsm_str *key; diff --git a/src/lander/lander_post.c b/src/lander/lander_post.c index 1244cb7..8b14410 100644 --- a/src/lander/lander_post.c +++ b/src/lander/lander_post.c @@ -38,7 +38,8 @@ bool lander_insert_entry(lnm_http_loop_ctx *ctx) { randomize_key(key_s, key_len); lsm_str_init(&key, key_s); } else { - const char *key_s = &ctx->req.path.s[ctx->req.path.groups[2].rm_so]; + const char *key_s = + &ctx->req.buf.s[ctx->req.path.o + ctx->req.path.groups[2].rm_so]; key_len = ctx->req.path.groups[2].rm_eo - ctx->req.path.groups[2].rm_so; lsm_str_init_copy_n(&key, key_s, key_len); From e438bd045ca2ee64e3d9ab98f416027b5417c3f6 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 30 Dec 2023 16:59:47 +0100 Subject: [PATCH 38/45] feat(ci): build for arm64 --- .woodpecker/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml index eaaac5a..0ee849e 100644 --- a/.woodpecker/build.yml +++ b/.woodpecker/build.yml @@ -1,6 +1,7 @@ matrix: PLATFORM: - 'linux/amd64' + - 'linux/arm64' platform: ${PLATFORM} @@ -34,7 +35,8 @@ pipeline: commands: - apk add --no-cache minio-client - mcli alias set rb 'https://s3.rustybever.be' "$MINIO_ACCESS_KEY" "$MINIO_SECRET_KEY" - - mcli cp build/lander landerctl/build/landerctl "rb/lander/commits/$CI_COMMIT_SHA/" + - mcli cp build/lander "rb/lander/commits/$CI_COMMIT_SHA/lander-$(echo '${PLATFORM}' | sed 's:/:-:g')" + - mcli cp landerctl/build/landerctl "rb/lander/commits/$CI_COMMIT_SHA/landerctl-$(echo '${PLATFORM}' | sed 's:/:-:g')" secrets: - minio_access_key - minio_secret_key From dc0a3a7349b4d6deb3273db1f2a37cfdbc8636eb Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 2 Mar 2024 12:02:20 +0100 Subject: [PATCH 39/45] feat(lander): remove content-disposition header --- CHANGELOG.md | 4 ++++ src/lander/lander_get.c | 16 ---------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a64beb..1790cc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Landerctl * `-c` flag to use custom config file (useful for testing) +## Changed + +* Removed Content-Disposition header for files + ## [0.2.0](https://git.rustybever.be/Chewing_Bever/lander/src/tag/0.2.0) ### Added diff --git a/src/lander/lander_get.c b/src/lander/lander_get.c index f9d085f..aaf35de 100644 --- a/src/lander/lander_get.c +++ b/src/lander/lander_get.c @@ -93,22 +93,6 @@ lnm_http_step_err lander_get_file(lnm_http_conn *conn) { lander_attr_to_header(ctx, lander_attr_type_content_type, lnm_http_header_content_type); - lsm_str *value; - char *buf; - - if (lsm_entry_attr_get(&value, c_ctx->entry, lander_attr_type_file_name) == - lsm_error_ok) { - buf = malloc(24 + lsm_str_len(value)); - int len = lsm_str_len(value); - sprintf(buf, "attachment; filename=\"%.*s\"", len, lsm_str_ptr(value)); - } else { - buf = malloc(11); - strcpy(buf, "attachment"); - } - - lnm_http_res_add_header(&ctx->res, lnm_http_header_content_disposition, buf, - true); - return lnm_http_step_err_done; } From 7b195c75b089d04cb2dc166c25943ad89230dfb4 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 2 Mar 2024 22:40:57 +0100 Subject: [PATCH 40/45] chore: remove lnm directory --- lnm/Makefile | 128 ----- lnm/config.mk | 16 - lnm/include/lnm/common.h | 89 --- lnm/include/lnm/http/consts.h | 98 ---- lnm/include/lnm/http/loop.h | 141 ----- lnm/include/lnm/http/req.h | 111 ---- lnm/include/lnm/http/res.h | 119 ---- lnm/include/lnm/log.h | 47 -- lnm/include/lnm/loop.h | 54 -- lnm/include/picohttpparser.h | 87 --- lnm/src/_include/lnm/http/loop_internal.h | 69 --- lnm/src/_include/lnm/log_internal.h | 26 - lnm/src/_include/lnm/loop_internal.h | 14 - lnm/src/http/lnm_http_consts.c | 101 ---- lnm/src/http/lnm_http_loop.c | 138 ----- lnm/src/http/lnm_http_loop_ctx.c | 55 -- lnm/src/http/lnm_http_loop_process.c | 391 ------------- lnm/src/http/lnm_http_loop_steps.c | 44 -- lnm/src/http/lnm_http_req.c | 114 ---- lnm/src/http/lnm_http_res.c | 99 ---- lnm/src/lnm_log.c | 86 --- lnm/src/lnm_utils.c | 57 -- lnm/src/loop/lnm_loop.c | 232 -------- lnm/src/loop/lnm_loop_conn.c | 22 - lnm/src/loop/lnm_loop_io.c | 77 --- lnm/src/picohttpparser.c | 665 ---------------------- 26 files changed, 3080 deletions(-) delete mode 100644 lnm/Makefile delete mode 100644 lnm/config.mk delete mode 100644 lnm/include/lnm/common.h delete mode 100644 lnm/include/lnm/http/consts.h delete mode 100644 lnm/include/lnm/http/loop.h delete mode 100644 lnm/include/lnm/http/req.h delete mode 100644 lnm/include/lnm/http/res.h delete mode 100644 lnm/include/lnm/log.h delete mode 100644 lnm/include/lnm/loop.h delete mode 100644 lnm/include/picohttpparser.h delete mode 100644 lnm/src/_include/lnm/http/loop_internal.h delete mode 100644 lnm/src/_include/lnm/log_internal.h delete mode 100644 lnm/src/_include/lnm/loop_internal.h delete mode 100644 lnm/src/http/lnm_http_consts.c delete mode 100644 lnm/src/http/lnm_http_loop.c delete mode 100644 lnm/src/http/lnm_http_loop_ctx.c delete mode 100644 lnm/src/http/lnm_http_loop_process.c delete mode 100644 lnm/src/http/lnm_http_loop_steps.c delete mode 100644 lnm/src/http/lnm_http_req.c delete mode 100644 lnm/src/http/lnm_http_res.c delete mode 100644 lnm/src/lnm_log.c delete mode 100644 lnm/src/lnm_utils.c delete mode 100644 lnm/src/loop/lnm_loop.c delete mode 100644 lnm/src/loop/lnm_loop_conn.c delete mode 100644 lnm/src/loop/lnm_loop_io.c delete mode 100644 lnm/src/picohttpparser.c diff --git a/lnm/Makefile b/lnm/Makefile deleted file mode 100644 index 38b2b4a..0000000 --- a/lnm/Makefile +++ /dev/null @@ -1,128 +0,0 @@ -# https://spin.atomicobject.com/2016/08/26/makefile-c-projects/ was a great -# base for this Makefile - --include config.mk - -LIB := $(BUILD_DIR)/$(LIB_FILENAME) - -SRCS != find '$(SRC_DIR)' -iname '*.c' -SRCS_H != find include -iname '*.h' -SRCS_H_INTERNAL != find $(SRC_DIR) -iname '*.h' -SRCS_TEST != find '$(TEST_DIR)' -iname '*.c' - -OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) -OBJS_TEST := $(SRCS_TEST:%=$(BUILD_DIR)/%.o) -DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) $(SRCS_TEST:%=$(BUILD_DIR)/%.d) - -BINS_TEST := $(OBJS_TEST:%.c.o=%) -TARGETS_TEST := $(BINS_TEST:%=test-%) -TARGETS_MEM_TEST := $(BINS_TEST:%=test-mem-%) - -_CFLAGS := $(addprefix -I,$(INC_DIRS)) $(CFLAGS) -Wall -Wextra - -.PHONY: all -all: lib - - -# =====COMPILATION===== -# Utility used by the CI to lint -.PHONY: objs -objs: $(OBJS) - -.PHONY: lib -lib: $(LIB) -$(LIB): $(OBJS) - ar -rcs $@ $(OBJS) - -$(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c - mkdir -p $(dir $@) - $(CC) -c $(_CFLAGS) $< -o $@ - -# =====TESTING===== -.PHONY: test -test: $(TARGETS_TEST) - -.PHONY: test-mem -test-mem: $(TARGETS_MEM_TEST) - -.PHONY: $(TARGETS_TEST) -$(TARGETS_TEST): test-%: % - ./$^ - -.PHONY: $(TARGETS_MEM_TEST) -$(TARGETS_MEM_TEST): test-mem-%: % - valgrind --tool=memcheck --error-exitcode=1 --track-origins=yes --leak-check=full ./$^ - -.PHONY: build-test -build-test: $(BINS_TEST) - -$(BINS_TEST): %: %.c.o $(LIB) - $(CC) \ - $^ -o $@ - -# Along with the include directory, each test includes $(TEST_DIR) (which -# contains the acutest.h header file), and the src directory of the module it's -# testing. This allows tests to access internal methods, which aren't publicly -# exposed. -$(BUILD_DIR)/$(TEST_DIR)/%.c.o: $(TEST_DIR)/%.c - mkdir -p $(dir $@) - $(CC) $(_CFLAGS) -I$(TEST_DIR) \ - -I$(dir $(@:$(BUILD_DIR)/$(TEST_DIR)/%=$(SRC_DIR)/%)) \ - -c $< -o $@ - -# =====EXAMPLES===== -.PHONY: build-example -build-example: $(BINS_EXAMPLE) - -$(BINS_EXAMPLE): %: %.c.o $(LIB) - $(CC) \ - $^ -o $@ - -# Example binaries link the resulting library -$(BUILD_DIR)/$(EXAMPLE_DIR)/%.c.o: $(EXAMPLE_DIR)/%.c - mkdir -p $(dir $@) - $(CC) $(_CFLAGS) -I$(PUB_INC_DIR) -c $< -o $@ - -# =====MAINTENANCE===== -.PHONY: lint -lint: - clang-format -n --Werror \ - $(filter-out $(THIRDPARTY),$(SRCS)) \ - $(filter-out $(THIRDPARTY),$(SRCS_H)) \ - $(filter-out $(THIRDPARTY),$(SRCS_H_INTERNAL)) - -.PHONY: fmt -fmt: - clang-format -i \ - $(filter-out $(THIRDPARTY),$(SRCS)) \ - $(filter-out $(THIRDPARTY),$(SRCS_H)) \ - $(filter-out $(THIRDPARTY),$(SRCS_H_INTERNAL)) - -.PHONY: check -check: - mkdir -p $(BUILD_DIR)/cppcheck - cppcheck \ - $(addprefix -I,$(INC_DIRS)) \ - --cppcheck-build-dir=$(BUILD_DIR)/cppcheck \ - --error-exitcode=1 \ - --enable=warning,style \ - --inline-suppr \ - --check-level=exhaustive \ - --quiet \ - -j$(shell nproc) \ - $(filter-out $(THIRDPARTY),$(SRCS)) - -.PHONY: clean -clean: - rm -rf '$(BUILD_DIR)' - - -.PHONY: bear -bear: clean - bear -- make - bear --append -- make build-test - bear --append -- make build-example - - -# Make make aware of the .d files --include $(DEPS) diff --git a/lnm/config.mk b/lnm/config.mk deleted file mode 100644 index 7e55aff..0000000 --- a/lnm/config.mk +++ /dev/null @@ -1,16 +0,0 @@ -LIB_FILENAME = liblnm.a - -BUILD_DIR = build -SRC_DIR = src -TEST_DIR = test -THIRDPARTY = src/picohttpparser.c include/picohttpparser.h - -PUB_INC_DIR = include -INC_DIRS = $(PUB_INC_DIR) src/_include - -# -MMD: generate a .d file for every source file. This file can be imported by -# make and makes make aware that a header file has been changed, ensuring an -# object file is also recompiled if only a header is changed. -# -MP: generate a dummy target for every header file (according to the docs it -# prevents some errors when removing header files) -CFLAGS ?= -MMD -MP -g diff --git a/lnm/include/lnm/common.h b/lnm/include/lnm/common.h deleted file mode 100644 index 8cc982e..0000000 --- a/lnm/include/lnm/common.h +++ /dev/null @@ -1,89 +0,0 @@ -#ifndef LNM_COMMON -#define LNM_COMMON - -#include -#include -#include - -#define LNM_RES(x) \ - { \ - lnm_err res = x; \ - if (res != lnm_err_ok) \ - return res; \ - } - -#define LNM_RES2(x, e) \ - { \ - lnm_err res = x; \ - if (res != lnm_err_ok) { \ - e; \ - return res; \ - } \ - } - -#define LNM_MIN(x, y) ((x) < (y) ? (x) : (y)) -#define LNM_MAX(x, y) ((x) > (y) ? (x) : (y)) - -typedef enum lnm_err { - lnm_err_ok = 0, - lnm_err_failed_alloc, - lnm_err_failed_network, - lnm_err_failed_poll, - lnm_err_not_setup, - lnm_err_bad_regex, - lnm_err_not_found, -} lnm_err; - -typedef struct lnm_loop lnm_http_loop; - -typedef struct lnm_loop_conn lnm_http_conn; - -typedef struct lnm_http_step lnm_http_step; - -typedef struct lnm_http_route lnm_http_route; - -/** - * Returns whether the two strings are equal. - * - * @param s1 first string to compare - * @param s1_len length of `s1` - * @param s2 second string to compare - * @param s2_len length of `s2` - */ -bool lnm_strneq(const char *s1, size_t s1_len, const char *s2, size_t s2_len); - -/** - * Returns whether the two strings are equal, ignoring capitalisation. - * - * @param s1 first string to compare - * @param s1_len length of `s1` - * @param s2 second string to compare - * @param s2_len length of `s2` - */ -bool lnm_strnieq(const char *s1, size_t s1_len, const char *s2, size_t s2_len); - -/** - * Calculate integer exponentation. - * - * @param base - * @param power - */ -uint64_t lnm_ipow(uint64_t base, uint64_t power); - -/** - * Parse the given string into a number. - * - * @param s string to parse - * @param len length of s - * @return the parsed number, or 0 if the number is invalid - */ -uint64_t lnm_atoi(const char *s, size_t len); - -/** - * Calculate how many base 10 digits the given number consists of. - * - * @param num number to use - */ -uint64_t lnm_digits(uint64_t num); - -#endif diff --git a/lnm/include/lnm/http/consts.h b/lnm/include/lnm/http/consts.h deleted file mode 100644 index f543bbb..0000000 --- a/lnm/include/lnm/http/consts.h +++ /dev/null @@ -1,98 +0,0 @@ -#ifndef LNM_HTTP_CONSTS -#define LNM_HTTP_CONSTS - -#include - -extern const char *lnm_http_method_names[]; -extern const size_t lnm_http_method_names_len; - -typedef enum lnm_http_method { - lnm_http_method_get = 0, - lnm_http_method_post, - lnm_http_method_put, - lnm_http_method_patch, - lnm_http_method_delete, - lnm_http_method_head, -} lnm_http_method; - -extern const char *lnm_http_status_names[][32]; - -typedef enum lnm_http_status { - // 1xx - lnm_http_status_continue = 100, - lnm_http_status_switching_protocols = 101, - lnm_http_status_processing = 102, - lnm_http_status_early_hints = 103, - // 2xx - lnm_http_status_ok = 200, - lnm_http_status_created = 201, - lnm_http_status_accepted = 202, - lnm_http_status_non_authoritative_information = 203, - lnm_http_status_no_content = 204, - lnm_http_status_reset_content = 205, - lnm_http_status_partial_content = 206, - lnm_http_status_multi_status = 207, - lnm_http_status_already_reported = 208, - // 3xx - lnm_http_status_multiple_choices = 300, - lnm_http_status_moved_permanently = 301, - lnm_http_status_found = 302, - lnm_http_status_see_other = 303, - lnm_http_status_not_modified = 304, - lnm_http_status_temporary_redirect = 307, - lnm_http_status_permanent_redirect = 308, - // 4xx - lnm_http_status_bad_request = 400, - lnm_http_status_unauthorized = 401, - lnm_http_status_payment_required = 402, - lnm_http_status_forbidden = 403, - lnm_http_status_not_found = 404, - lnm_http_status_method_not_allowed = 405, - lnm_http_status_not_acceptable = 406, - lnm_http_status_proxy_authentication_required = 407, - lnm_http_status_request_timeout = 408, - lnm_http_status_conflict = 409, - lnm_http_status_gone = 410, - lnm_http_status_length_required = 411, - lnm_http_status_precondition_failed = 412, - lnm_http_status_content_too_large = 413, - lnm_http_status_uri_too_long = 414, - lnm_http_status_unsupported_media_type = 415, - lnm_http_status_range_not_satisfiable = 416, - lnm_http_status_expection_failed = 417, - lnm_http_status_im_a_teapot = 418, - lnm_http_status_misdirected_request = 421, - lnm_http_status_unprocessable_content = 422, - lnm_http_status_locked = 423, - lnm_http_status_failed_dependency = 424, - lnm_http_status_too_early = 425, - lnm_http_status_upgrade_required = 426, - lnm_http_status_precondition_required = 428, - lnm_http_status_too_many_requests = 429, - lnm_http_status_request_header_fields_too_large = 431, - // 5xx - lnm_http_status_internal_server_error = 500, - lnm_http_status_method_not_implemented = 501, - lnm_http_status_bad_gateway = 502, - lnm_http_status_service_unavailable = 503, - lnm_http_status_gateway_timeout = 504, - lnm_http_status_http_status_version_not_supported = 505, - lnm_http_status_variant_also_negotiates = 506, - lnm_http_status_insufficient_storage = 507, - lnm_http_status_loop_detected = 508, - lnm_http_status_not_extended = 510, - lnm_http_status_network_authentication_required = 511 -} lnm_http_status; - -extern const char *lnm_http_header_names[]; - -typedef enum lnm_http_header { - lnm_http_header_connection = 0, - lnm_http_header_location, - lnm_http_header_content_type, - lnm_http_header_content_disposition, - lnm_http_header_server, - lnm_http_header_content_length -} lnm_http_header; - -#endif diff --git a/lnm/include/lnm/http/loop.h b/lnm/include/lnm/http/loop.h deleted file mode 100644 index 8e36caa..0000000 --- a/lnm/include/lnm/http/loop.h +++ /dev/null @@ -1,141 +0,0 @@ -#ifndef LNM_HTTP_LOOP -#define LNM_HTTP_LOOP - -#include - -#include "lnm/common.h" -#include "lnm/http/req.h" -#include "lnm/http/res.h" - -typedef enum lnm_http_step_err { - lnm_http_step_err_done = 0, - lnm_http_step_err_io_needed, - lnm_http_step_err_close, - lnm_http_step_err_res, -} lnm_http_step_err; - -typedef lnm_http_step_err (*lnm_http_step_fn)(lnm_http_conn *conn); - -typedef lnm_err (*lnm_http_ctx_init_fn)(void **c_ctx, void *gctx); - -typedef void (*lnm_http_ctx_reset_fn)(void *c_ctx); - -typedef void (*lnm_http_ctx_free_fn)(void *c_ctx); - -/** - * Initialize a new `lnm_http_loop`. - * - * @param out where to store pointer to new `lnm_http_loop` - */ -lnm_err lnm_http_loop_init(lnm_http_loop **out, void *c_gctx, - lnm_http_ctx_init_fn ctx_init, - lnm_http_ctx_reset_fn ctx_reset, - lnm_http_ctx_free_fn ctx_free); - -/** - * Initialize a new step. - * - * @param out where to store pointer to new `lnm_http_step` - * @param fn step function - */ -lnm_err lnm_http_step_init(lnm_http_step **out, lnm_http_step_fn fn); - -/** - * Append the given step fn to the step. - * - * @param out where to store pointer to new `lnm_http_step` - * @param step step to append new step to - * @param fn step function - */ -lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step *step, - lnm_http_step_fn fn); - -/** - * Initialize a new route of type literal. - * - * @param out where to store pointer to new `lnm_http_route` - * @param path literal path to match - * @param step step to process request with - */ -lnm_err lnm_http_route_init_literal(lnm_http_route **out, - lnm_http_method method, const char *path, - lnm_http_step *step); - -/** - * Initialize a new route of type regex. - * - * @param out where to store pointer to new `lnm_http_route` - * @param pattern regex pattern - * @param regex_group_count how many regex groups are contained in the pattern - * @param step step to process request with - */ -lnm_err lnm_http_route_init_regex(lnm_http_route **out, lnm_http_method method, - const char *pattern, int regex_group_count, - lnm_http_step *step); - -/** - * Add a new route to the HTTP route. - * - * @param hl HTTP loop to modify - * @param route route to add - */ -lnm_err lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route); - -lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port, int thread_count); - -void lnm_http_loop_set_api_key(lnm_http_loop *hl, const char *api_key); - -void lnm_http_loop_set_server(lnm_http_loop *hl, const char *value); - -/** - * Represents what state an HTTP loop request is currently in. - */ -typedef enum lnm_http_loop_state { - // Parse the HTTP request - lnm_http_loop_state_parse_req = 0, - // Route the request - lnm_http_loop_state_route, - // Parse specific headers (e.g. Content-Length) - lnm_http_loop_state_parse_headers, - // Execute the various steps defined for the route - lnm_http_loop_state_steps, - // Add certain automatically added headers - lnm_http_loop_state_add_headers, - // Write the response status line - lnm_http_loop_state_write_status_line, - // Write the various response headers - lnm_http_loop_state_write_headers, - // Write the request body - lnm_http_loop_state_write_body, - // Clean up the request and reset the state for a next request - lnm_http_loop_state_finish, -} lnm_http_loop_state; - -typedef struct lnm_http_loop_gctx { - struct { - lnm_http_route **arr; - size_t len; - } routes; - lnm_http_ctx_init_fn ctx_init; - lnm_http_ctx_reset_fn ctx_reset; - lnm_http_ctx_free_fn ctx_free; - const char *api_key; - const char *server; - void *c; -} lnm_http_loop_gctx; - -typedef struct lnm_http_loop_ctx { - lnm_http_loop_state state; - lnm_http_req req; - lnm_http_res res; - lnm_http_route *route; - lnm_http_step *cur_step; - lnm_http_loop_gctx *g; - void *c; -} lnm_http_loop_ctx; - -lnm_http_step_err lnm_http_loop_step_body_to_buf(lnm_http_conn *conn); - -lnm_http_step_err lnm_http_loop_step_auth(lnm_http_conn *conn); - -#endif diff --git a/lnm/include/lnm/http/req.h b/lnm/include/lnm/http/req.h deleted file mode 100644 index 06f2743..0000000 --- a/lnm/include/lnm/http/req.h +++ /dev/null @@ -1,111 +0,0 @@ -#ifndef LNM_HTTP_REQ -#define LNM_HTTP_REQ - -#include -#include -#include - -#include "picohttpparser.h" - -#include "lnm/common.h" -#include "lnm/http/consts.h" - -#define LNM_HTTP_MAX_REQ_HEADERS 32 -#define LNM_HTTP_MAX_REGEX_GROUPS 4 - -typedef struct lnm_http_req_header { - struct { - size_t o; - size_t len; - } name; - struct { - size_t o; - size_t len; - } value; -} lnm_http_req_header; - -/** - * Represents the parsed HTTP request - */ -typedef struct lnm_http_req { - struct { - char *s; - size_t len; - bool owned; - } buf; - int minor_version; - lnm_http_method method; - struct { - size_t o; - size_t len; - regmatch_t groups[LNM_HTTP_MAX_REGEX_GROUPS]; - } path; - struct { - size_t o; - size_t len; - } query; - struct { - lnm_http_req_header arr[LNM_HTTP_MAX_REQ_HEADERS]; - size_t len; - } headers; - struct { - uint64_t expected_len; - uint64_t len; - char *buf; - bool owned; - } body; -} lnm_http_req; - -typedef enum lnm_http_parse_err { - lnm_http_parse_err_ok = 0, - lnm_http_parse_err_incomplete, - lnm_http_parse_err_invalid, - lnm_http_parse_err_unknown_method, -} lnm_http_parse_err; - -/** - * Try to parse the given buffer into an HTTP request. - * - * @param req request to store parsed data in - * @param buf buffer to parse; might be modified - * @param len length of buf - */ -lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, size_t len); - -/** - * Reset the given request object, free'ing all its relevant parts and allowing - * it to be reused as a new object. - * - * @param req object to reset - */ -void lnm_http_req_reset(lnm_http_req *req); - -/** - * Retrieve a specific header from the request. - * - * Pointers retrieved from this function should never be used between step - * functions; simply request the header again if you need to. - * - * @param out where to write pointer to header value - * @param out_len where to store length of out value - * @param req request to look for header in - * @param type type of header to look for - */ -lnm_err lnm_http_req_header_get(const char **out, size_t *out_len, - lnm_http_req *req, lnm_http_header type); - -/** - * Retrieve a specific header from the request by specifying its name. - * - * Pointers retrieved from this function should never be used between step - * functions; simply request the header again if you need to. - * - * @param out where to write pointer to header value - * @param out_len where to store length of out value - * @param req request to look for header in - * @param name name of the header; matches case-insensitive - */ -lnm_err lnm_http_req_header_get_s(const char **out, size_t *out_len, - lnm_http_req *req, const char *name); - -#endif diff --git a/lnm/include/lnm/http/res.h b/lnm/include/lnm/http/res.h deleted file mode 100644 index ee2a079..0000000 --- a/lnm/include/lnm/http/res.h +++ /dev/null @@ -1,119 +0,0 @@ -#ifndef LNM_HTTP_RES -#define LNM_HTTP_RES - -#include -#include - -#include "lnm/common.h" -#include "lnm/http/consts.h" - -typedef lnm_err (*data_fn)(uint64_t *written, char *buf, lnm_http_conn *conn, - uint64_t offset, uint64_t len); - -/** - * Linked list elements used to store the response headers - */ -typedef struct lnm_http_res_header { - struct { - char *s; - size_t len; - bool owned; - } name; - struct { - char *s; - size_t len; - bool owned; - } value; - struct lnm_http_res_header *next; -} lnm_http_res_header; - -typedef enum lnm_http_res_body_type { - lnm_http_res_body_type_file = 0, - lnm_http_res_body_type_buf, - lnm_http_res_body_type_fn, -} lnm_http_res_body_type; - -typedef struct lnm_http_res { - lnm_http_status status; - struct { - lnm_http_res_header *head; - lnm_http_res_header *current; - } headers; - struct { - struct { - char *buf; - FILE *f; - data_fn fn; - } data; - uint64_t len; - bool owned; - lnm_http_res_body_type type; - } body; - // General-purpose; meaning depends on the current state - uint64_t written; -} lnm_http_res; - -/** - * Add a new header of a known type to the response - * - * @param type type of header - * @param value null-terminated string containing the value of the header - * @param value_owned whether to take ownership of the value pointer; if false, - * free'ing the buffer is the caller's responsibility - */ -lnm_err lnm_http_res_add_header(lnm_http_res *res, lnm_http_header type, - char *value, bool value_owned); - -/** - * Add a new header of a known type to the response with a given value length. - * - * @param type type of header - * @param value string of length `value_len` containing the value of the header - * @param value_len length of value - * @param value_owned whether to take ownership of the value pointer; if false, - * free'ing the buffer is the caller's responsibility - */ -lnm_err lnm_http_res_add_header_len(lnm_http_res *res, lnm_http_header type, - char *value, size_t value_len, - bool value_owned); - -/** - * Set the request body to the given file pointer. - * - * @param res response to modify - * @param f file pointer to use as data - * @param len expected length of the file - * @param owned whether to take ownership of the file pointer - */ -void lnm_http_res_body_set_file(lnm_http_res *res, FILE *f, size_t len, - bool owned); - -/** - * Set the request body to the given buffer. - * - * @param res response to modify - * @param buf buffer to use as data - * @param len length of the buffer - * @param owned whether to take ownership of the file pointer - */ -void lnm_http_res_body_set_buf(lnm_http_res *res, char *buf, size_t len, - bool owned); - -/** - * Set the request body to be read from the given data function. - * - * @param res response to modify - * @param fn data reader function - * @param len expected length of the response - */ -void lnm_http_res_body_set_fn(lnm_http_res *res, data_fn fn, size_t len); - -/** - * Reset the given response object, properly free'ing any allocated buffers, - * allowing it to be reused for later connections. - * - * @param res res to reset - */ -void lnm_http_res_reset(lnm_http_res *res); - -#endif diff --git a/lnm/include/lnm/log.h b/lnm/include/lnm/log.h deleted file mode 100644 index 42f27de..0000000 --- a/lnm/include/lnm/log.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef LNM_LOG -#define LOG - -#include - -#include "lnm/common.h" - -typedef struct lnm_logger lnm_logger; - -typedef enum lnm_log_level { - lnm_log_level_debug = 0, - lnm_log_level_info, - lnm_log_level_notice, - lnm_log_level_warning, - lnm_log_level_error, - lnm_log_level_critical -} lnm_log_level; - -extern const char *lnm_log_level_names[]; - -/** - * Initialize the global logger. - */ -lnm_err lnm_log_init_global(); - -/** - * Register stdout as one of the streams for the global logger. - */ -lnm_err lnm_log_register_stdout(lnm_log_level level); - -void lnm_log(lnm_log_level level, const char *section, const char *fmt, ...) - __attribute__((format(printf, 3, 4))); - -#define lnm_ldebug(section, fmt, ...) \ - lnm_log(lnm_log_level_debug, section, fmt, __VA_ARGS__) -#define lnm_linfo(section, fmt, ...) \ - lnm_log(lnm_log_level_info, section, fmt, __VA_ARGS__) -#define lnm_lnotice(section, fmt, ...) \ - lnm_log(lnm_log_level_notice, section, fmt, __VA_ARGS__) -#define lnm_lwarning(section, fmt, ...) \ - lnm_log(lnm_log_level_warning, section, fmt, __VA_ARGS__) -#define lnm_lerror(section, fmt, ...) \ - lnm_log(lnm_log_level_error, section, fmt, __VA_ARGS__) -#define lnm_lcritical(section, fmt, ...) \ - lnm_log(lnm_log_level_critical, section, fmt, __VA_ARGS__) - -#endif diff --git a/lnm/include/lnm/loop.h b/lnm/include/lnm/loop.h deleted file mode 100644 index 7ca372c..0000000 --- a/lnm/include/lnm/loop.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef LNM_LOOP -#define LNM_LOOP - -#include -#include -#include - -#include "lnm/common.h" - -#define LNM_LOOP_BUF_SIZE 2048 - -typedef enum { - lnm_loop_state_req = 0, - lnm_loop_state_res, - lnm_loop_state_end, -} lnm_loop_state; - -typedef struct lnm_loop_conn { - int fd; - lnm_loop_state state; - void *ctx; - struct { - char buf[LNM_LOOP_BUF_SIZE]; - size_t size; - size_t read; - } r; - struct { - char buf[LNM_LOOP_BUF_SIZE]; - size_t size; - } w; -} lnm_loop_conn; - -typedef struct lnm_loop { - int listen_fd; - int epoll_fd; - atomic_int open; - void *gctx; - lnm_err (*ctx_init)(void **out, void *gctx); - void (*ctx_free)(void *ctx); - void (*data_read)(lnm_loop_conn *conn); - void (*data_write)(lnm_loop_conn *conn); -} lnm_loop; - -lnm_err lnm_loop_init(lnm_loop **out, void *gctx, - lnm_err (*ctx_init)(void **out, void *gctx), - void (*ctx_free)(void *ctx), - void (*data_read)(lnm_loop_conn *conn), - void (*data_write)(lnm_loop_conn *conn)); - -lnm_err lnm_loop_setup(lnm_loop *l, uint16_t port); - -lnm_err lnm_loop_run(lnm_loop *l, int thread_count); - -#endif diff --git a/lnm/include/picohttpparser.h b/lnm/include/picohttpparser.h deleted file mode 100644 index 07537cf..0000000 --- a/lnm/include/picohttpparser.h +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, - * Shigeo Mitsunari - * - * The software is licensed under either the MIT License (below) or the Perl - * license. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#ifndef picohttpparser_h -#define picohttpparser_h - -#include - -#ifdef _MSC_VER -#define ssize_t intptr_t -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/* contains name and value of a header (name == NULL if is a continuing line - * of a multiline header */ -struct phr_header { - const char *name; - size_t name_len; - const char *value; - size_t value_len; -}; - -/* returns number of bytes consumed if successful, -2 if request is partial, - * -1 if failed */ -int phr_parse_request(const char *buf, size_t len, const char **method, size_t *method_len, const char **path, size_t *path_len, - int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len); - -/* ditto */ -int phr_parse_response(const char *_buf, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len, - struct phr_header *headers, size_t *num_headers, size_t last_len); - -/* ditto */ -int phr_parse_headers(const char *buf, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len); - -/* should be zero-filled before start */ -struct phr_chunked_decoder { - size_t bytes_left_in_chunk; /* number of bytes left in current chunk */ - char consume_trailer; /* if trailing headers should be consumed */ - char _hex_count; - char _state; -}; - -/* the function rewrites the buffer given as (buf, bufsz) removing the chunked- - * encoding headers. When the function returns without an error, bufsz is - * updated to the length of the decoded data available. Applications should - * repeatedly call the function while it returns -2 (incomplete) every time - * supplying newly arrived data. If the end of the chunked-encoded data is - * found, the function returns a non-negative number indicating the number of - * octets left undecoded, that starts from the offset returned by `*bufsz`. - * Returns -1 on error. - */ -ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *bufsz); - -/* returns if the chunked decoder is in middle of chunked data */ -int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/lnm/src/_include/lnm/http/loop_internal.h b/lnm/src/_include/lnm/http/loop_internal.h deleted file mode 100644 index 4d9e865..0000000 --- a/lnm/src/_include/lnm/http/loop_internal.h +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef LNM_HTTP_LOOP_INTERNAL -#define LNM_HTTP_LOOP_INTERNAL - -#include - -#include "lnm/http/loop.h" - -typedef struct lnm_http_step { - lnm_http_step_fn fn; - struct lnm_http_step *next; -} lnm_http_step; - -typedef enum lnm_http_route_type { - lnm_http_route_type_literal = 0, - lnm_http_route_type_regex, -} lnm_http_route_type; - -typedef struct lnm_http_route { - union { - regex_t *regex; - const char *s; - } route; - lnm_http_method method; - lnm_http_route_type type; - int regex_group_count; - lnm_http_step *step; -} lnm_http_route; - -/** - * Initialize a new empty route. - * - * @param out where to store pointer to new `lnm_http_route` - */ -lnm_err lnm_http_route_init(lnm_http_route **out); - -/** - * Initialize a first step. - * - * @param out where to store pointer to new `lnm_http_step` - * @param fn step function associated with the step - */ -lnm_err lnm_http_step_init(lnm_http_step **out, lnm_http_step_fn fn); - -/** - * Initialize a new global context object. - * - * @param out where to store pointer to new `lnm_http_loop_gctx` - */ -lnm_err lnm_http_loop_gctx_init(lnm_http_loop_gctx **out, void *c_gctx, - lnm_http_ctx_init_fn ctx_init, - lnm_http_ctx_reset_fn ctx_reset, - lnm_http_ctx_free_fn ctx_free); - -/** - * Initialize a new context. - * - * @param out where to store pointer to new object - * @param gctx global context for the loop - */ -lnm_err lnm_http_loop_ctx_init(lnm_http_loop_ctx **out, - lnm_http_loop_gctx *gctx); - -void lnm_http_loop_ctx_reset(lnm_http_loop_ctx *ctx); - -void lnm_http_loop_ctx_free(lnm_http_loop_ctx *ctx); - -void lnm_http_loop_process(lnm_http_conn *conn); - -#endif diff --git a/lnm/src/_include/lnm/log_internal.h b/lnm/src/_include/lnm/log_internal.h deleted file mode 100644 index ebcd243..0000000 --- a/lnm/src/_include/lnm/log_internal.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef LNM_LOG_INTERNAL -#define LNM_LOG_INTERNAL - -#include "lnm/log.h" - -typedef enum lnm_logger_stream_type { - lnm_logger_stream_type_file = 0 -} lnm_logger_stream_type; - -typedef struct lnm_logger_stream { - void *ptr; - lnm_logger_stream_type type; - lnm_log_level level; -} lnm_logger_stream; - -struct lnm_logger { - struct { - lnm_logger_stream **arr; - size_t len; - } streams; -}; - -lnm_err lnm_logger_stream_register(lnm_logger *logger, - lnm_logger_stream *stream); - -#endif diff --git a/lnm/src/_include/lnm/loop_internal.h b/lnm/src/_include/lnm/loop_internal.h deleted file mode 100644 index a5e70a8..0000000 --- a/lnm/src/_include/lnm/loop_internal.h +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef LNM_LOOP_INTERNAL -#define LNM_LOOP_INTERNAL - -#include "lnm/loop.h" - -lnm_err lnm_loop_conn_init(lnm_loop_conn **out, lnm_loop *l); - -void lnm_loop_conn_free(lnm_loop *l, lnm_loop_conn *conn); - -lnm_err lnm_loop_accept(lnm_loop *l); - -void lnm_loop_conn_io(lnm_loop *l, lnm_loop_conn *conn); - -#endif diff --git a/lnm/src/http/lnm_http_consts.c b/lnm/src/http/lnm_http_consts.c deleted file mode 100644 index cfbee40..0000000 --- a/lnm/src/http/lnm_http_consts.c +++ /dev/null @@ -1,101 +0,0 @@ -#include "lnm/http/consts.h" - -const char *lnm_http_method_names[] = {"GET", "POST", "PUT", - "PATCH", "DELETE", "HEAD"}; -const size_t lnm_http_method_names_len = - sizeof(lnm_http_method_names) / sizeof(lnm_http_method_names[0]); - -// clang-format off - -const char *lnm_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 *lnm_http_header_names[] = { - "Connection", - "Location", - "Content-Type", - "Content-Disposition", - "Server", - "Content-Length" -}; diff --git a/lnm/src/http/lnm_http_loop.c b/lnm/src/http/lnm_http_loop.c deleted file mode 100644 index 784126e..0000000 --- a/lnm/src/http/lnm_http_loop.c +++ /dev/null @@ -1,138 +0,0 @@ -#include - -#include "lnm/common.h" -#include "lnm/http/loop.h" -#include "lnm/http/loop_internal.h" -#include "lnm/loop_internal.h" - -lnm_err lnm_http_loop_init(lnm_http_loop **out, void *c_gctx, - lnm_http_ctx_init_fn ctx_init, - lnm_http_ctx_reset_fn ctx_reset, - lnm_http_ctx_free_fn ctx_free) { - lnm_http_loop *hl = calloc(1, sizeof(lnm_http_loop)); - - if (hl == NULL) { - return lnm_err_failed_alloc; - } - - LNM_RES2(lnm_http_loop_gctx_init((lnm_http_loop_gctx **)&hl->gctx, c_gctx, - ctx_init, ctx_reset, ctx_free), - free(hl)); - - hl->data_read = lnm_http_loop_process; - hl->data_write = lnm_http_loop_process; - hl->ctx_init = (lnm_err(*)(void **, void *))lnm_http_loop_ctx_init; - hl->ctx_free = (void (*)(void *))lnm_http_loop_ctx_free; - *out = hl; - - return lnm_err_ok; -} - -lnm_err lnm_http_step_init(lnm_http_step **out, lnm_http_step_fn fn) { - lnm_http_step *step = calloc(1, sizeof(lnm_http_step)); - - if (step == NULL) { - return lnm_err_failed_alloc; - } - - step->fn = fn; - *out = step; - - return lnm_err_ok; -} - -lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step *step, - lnm_http_step_fn fn) { - LNM_RES(lnm_http_step_init(out, fn)); - - if (step != NULL) { - step->next = *out; - } - - return lnm_err_ok; -} - -lnm_err lnm_http_route_init(lnm_http_route **out) { - lnm_http_route *route = calloc(1, sizeof(lnm_http_route)); - - if (route == NULL) { - return lnm_err_failed_alloc; - } - - *out = route; - - return lnm_err_ok; -} - -lnm_err lnm_http_route_init_literal(lnm_http_route **out, - lnm_http_method method, const char *path, - lnm_http_step *step) { - LNM_RES(lnm_http_route_init(out)); - - (*out)->type = lnm_http_route_type_literal; - (*out)->method = method; - (*out)->route.s = path; - (*out)->step = step; - - return lnm_err_ok; -} - -lnm_err lnm_http_route_init_regex(lnm_http_route **out, lnm_http_method method, - const char *pattern, int regex_group_count, - lnm_http_step *step) { - regex_t *regex = calloc(1, sizeof(regex_t)); - - if (regex == NULL) { - return lnm_err_failed_alloc; - } - - if (regcomp(regex, pattern, REG_EXTENDED) != 0) { - free(regex); - return lnm_err_bad_regex; - } - - LNM_RES2(lnm_http_route_init(out), free(regex)); - - (*out)->method = method; - (*out)->type = lnm_http_route_type_regex; - (*out)->route.regex = regex; - (*out)->regex_group_count = regex_group_count; - (*out)->step = step; - - return lnm_err_ok; -} - -lnm_err lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route) { - lnm_http_loop_gctx *gctx = hl->gctx; - - lnm_http_route **new_routes = - gctx->routes.len > 0 - ? realloc(gctx->routes.arr, - (gctx->routes.len + 1) * sizeof(lnm_http_route *)) - : malloc(sizeof(lnm_http_route *)); - - if (new_routes == NULL) { - return lnm_err_failed_alloc; - } - - new_routes[gctx->routes.len] = route; - gctx->routes.arr = new_routes; - gctx->routes.len++; - - return lnm_err_ok; -} - -lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port, int thread_count) { - LNM_RES(lnm_loop_setup(hl, port)); - return lnm_loop_run(hl, thread_count); -} - -void lnm_http_loop_set_api_key(lnm_http_loop *hl, const char *api_key) { - lnm_http_loop_gctx *gctx = hl->gctx; - gctx->api_key = api_key; -} - -void lnm_http_loop_set_server(lnm_http_loop *hl, const char *server) { - lnm_http_loop_gctx *gctx = hl->gctx; - gctx->server = server; -} diff --git a/lnm/src/http/lnm_http_loop_ctx.c b/lnm/src/http/lnm_http_loop_ctx.c deleted file mode 100644 index 87741e2..0000000 --- a/lnm/src/http/lnm_http_loop_ctx.c +++ /dev/null @@ -1,55 +0,0 @@ -#include "lnm/http/loop_internal.h" - -lnm_err lnm_http_loop_gctx_init(lnm_http_loop_gctx **out, void *c_gctx, - lnm_http_ctx_init_fn ctx_init, - lnm_http_ctx_reset_fn ctx_reset, - lnm_http_ctx_free_fn ctx_free) { - lnm_http_loop_gctx *gctx = calloc(1, sizeof(lnm_http_loop_gctx)); - - if (gctx == NULL) { - return lnm_err_failed_alloc; - } - - gctx->c = c_gctx; - gctx->ctx_init = ctx_init; - gctx->ctx_reset = ctx_reset; - gctx->ctx_free = ctx_free; - - *out = gctx; - - return lnm_err_ok; -} - -lnm_err lnm_http_loop_ctx_init(lnm_http_loop_ctx **out, - lnm_http_loop_gctx *gctx) { - lnm_http_loop_ctx *ctx = calloc(1, sizeof(lnm_http_loop_ctx)); - - if (ctx == NULL) { - return lnm_err_failed_alloc; - } - - LNM_RES2(gctx->ctx_init(&ctx->c, gctx), free(ctx)); - - ctx->g = gctx; - *out = ctx; - - return lnm_err_ok; -} - -void lnm_http_loop_ctx_reset(lnm_http_loop_ctx *ctx) { - ctx->g->ctx_reset(ctx->c); - - lnm_http_req_reset(&ctx->req); - lnm_http_res_reset(&ctx->res); - - ctx->route = NULL; - ctx->cur_step = NULL; -} - -void lnm_http_loop_ctx_free(lnm_http_loop_ctx *ctx) { - lnm_http_loop_ctx_reset(ctx); - - ctx->g->ctx_free(ctx->c); - - free(ctx); -} diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c deleted file mode 100644 index e16e18c..0000000 --- a/lnm/src/http/lnm_http_loop_process.c +++ /dev/null @@ -1,391 +0,0 @@ -#include -#include -#include - -#include "lnm/http/consts.h" -#include "lnm/http/loop.h" -#include "lnm/http/loop_internal.h" -#include "lnm/http/req.h" -#include "lnm/log.h" -#include "lnm/loop.h" -#include "lnm/loop_internal.h" - -static const char *section = "http"; - -/* static const lnm_http_loop_state lnm_http_loop_state_first_req = - * lnm_http_loop_state_parse_req; */ -static const lnm_http_loop_state lnm_http_loop_state_first_res = - lnm_http_loop_state_add_headers; - -void lnm_http_loop_process_parse_req(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; - - lnm_http_parse_err res = lnm_http_req_parse( - &ctx->req, &conn->r.buf[conn->r.read], conn->r.size - conn->r.read); - - switch (res) { - case lnm_http_parse_err_ok: - conn->r.read += ctx->req.buf.len; - ctx->state = lnm_http_loop_state_route; - - lnm_linfo(section, "%s %.*s HTTP/1.%i", - lnm_http_method_names[ctx->req.method], (int)ctx->req.path.len, - ctx->req.buf.s + ctx->req.path.o, ctx->req.minor_version); - break; - case lnm_http_parse_err_incomplete: - // If the request is already the size of the read buffer, we close the - // request. Otherwise, we wait for anything read - if (conn->r.size - conn->r.read == LNM_LOOP_BUF_SIZE) { - lnm_linfo(section, "Received request larger than buffer (%i bytes)", - LNM_LOOP_BUF_SIZE); - - conn->state = lnm_loop_state_end; - } - break; - case lnm_http_parse_err_invalid: - lnm_linfo(section, "%s", "Received invalid request"); - - conn->state = lnm_loop_state_end; - break; - case lnm_http_parse_err_unknown_method: - ctx->res.status = lnm_http_status_method_not_implemented; - ctx->state = lnm_http_loop_state_first_res; - break; - } -} - -void lnm_http_loop_process_route(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; - lnm_http_loop_gctx *gctx = ctx->g; - - // 0: no match - // 1: matched route, but not method - // 2: fully matched route - int match_level = 0; - lnm_http_route *route; - - for (size_t i = 0; i < gctx->routes.len && match_level < 3; i++) { - route = gctx->routes.arr[i]; - bool matched_path = false; - - switch (route->type) { - case lnm_http_route_type_literal: - matched_path = strncmp(route->route.s, ctx->req.buf.s + ctx->req.path.o, - ctx->req.path.len) == 0; - break; - case lnm_http_route_type_regex: - matched_path = - regexec(route->route.regex, ctx->req.buf.s + ctx->req.path.o, - LNM_HTTP_MAX_REGEX_GROUPS, ctx->req.path.groups, 0) == 0; - break; - } - - // GET routes also automatically route HEAD requests - bool matched_method = route->method == ctx->req.method || - (route->method == lnm_http_method_get && - ctx->req.method == lnm_http_method_head); - int new_match_level = 2 * matched_path + matched_method; - - // Remember the previous match levels so we can return the correct status - // message - match_level = match_level < new_match_level ? new_match_level : match_level; - } - - switch (match_level) { - case 0: - case 1: - ctx->res.status = lnm_http_status_not_found; - ctx->state = lnm_http_loop_state_first_res; - break; - case 2: - ctx->res.status = lnm_http_status_method_not_allowed; - ctx->state = lnm_http_loop_state_first_res; - break; - case 3: - ctx->route = route; - ctx->cur_step = route->step; - ctx->state = lnm_http_loop_state_parse_headers; - break; - } -} - -void lnm_http_loop_process_parse_headers(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; - lnm_http_req *req = &ctx->req; - - const char *value; - size_t value_len; - if (lnm_http_req_header_get(&value, &value_len, req, - lnm_http_header_content_length) == lnm_err_ok) { - req->body.expected_len = lnm_atoi(value, value_len); - } - - ctx->state = lnm_http_loop_state_steps; -} - -void lnm_http_loop_process_steps(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; - lnm_http_step *step = NULL; - - // Loop until we either: - // - reach the end of the chain of steps, indicated by NULL - // - have a step that's waiting for I/O - while ((ctx->cur_step != NULL) && (step != ctx->cur_step)) { - step = ctx->cur_step; - - switch (step->fn(conn)) { - case lnm_http_step_err_done: - ctx->cur_step = ctx->cur_step->next; - break; - case lnm_http_step_err_io_needed: - break; - case lnm_http_step_err_close: - conn->state = lnm_loop_state_end; - break; - case lnm_http_step_err_res: - ctx->state = lnm_http_loop_state_first_res; - break; - } - } - - if (ctx->cur_step == NULL) { - ctx->state = lnm_http_loop_state_add_headers; - } -} - -void lnm_http_loop_state_process_add_headers(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; - lnm_http_res *res = &ctx->res; - - uint64_t digits = lnm_digits(res->body.len); - char *buf = malloc(digits + 1); - - if (buf == NULL) { - conn->state = lnm_loop_state_end; - - return; - } - - sprintf(buf, "%lu", res->body.len); - lnm_http_res_add_header_len(res, lnm_http_header_content_length, buf, digits, - true); - - if (ctx->g->server != NULL) { - lnm_http_res_add_header(res, lnm_http_header_server, (char *)ctx->g->server, - false); - } - - if (res->status == 0) { - res->status = lnm_http_status_ok; - } - - lnm_linfo(section, "%i %s", res->status, - lnm_http_status_names[res->status / 100 - 1][res->status % 100]); - - ctx->state = lnm_http_loop_state_write_status_line; -} - -// This function is intentionally written inefficiently for now, as it will most -// likely only have to run once for each response -void lnm_http_loop_process_write_status_line(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; - lnm_http_res *res = &ctx->res; - - const char *response_type_name = - lnm_http_status_names[res->status / 100 - 1][res->status % 100]; - - // First we calculate the size of the start of the header - size_t buf_size = - snprintf(NULL, 0, "HTTP/1.1 %i %s\n", res->status, response_type_name); - char buf[buf_size + 1]; - sprintf(buf, "HTTP/1.1 %i %s\n", res->status, response_type_name); - - size_t to_write = - LNM_MIN(buf_size - res->written, LNM_LOOP_BUF_SIZE - conn->w.size); - memcpy(&conn->w.buf[conn->w.size], &buf[res->written], to_write); - - conn->w.size += to_write; - res->written += to_write; - - if (res->written == buf_size) { - res->written = 0; - ctx->state = lnm_http_loop_state_write_headers; - } -} - -void lnm_http_loop_process_write_headers(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; - lnm_http_res *res = &ctx->res; - - lnm_http_res_header *header; - - // Loop as long as we can still write new data and have headers to write - while ((conn->w.size < LNM_LOOP_BUF_SIZE) && - ((header = res->headers.current) != NULL)) { - size_t buf_len = header->name.len + 2 + header->value.len + 1; - - // Here, we also constantly calculate the entire buffer as we assume each - // header will be written in one go - char buf[buf_len]; - memcpy(buf, header->name.s, header->name.len); - memcpy(&buf[header->name.len + 2], header->value.s, header->value.len); - buf[header->name.len] = ':'; - buf[header->name.len + 1] = ' '; - buf[buf_len - 1] = '\n'; - - size_t to_write = - LNM_MIN(buf_len - res->written, LNM_LOOP_BUF_SIZE - conn->w.size); - memcpy(&conn->w.buf[conn->w.size], &buf[res->written], to_write); - - conn->w.size += to_write; - res->written += to_write; - - if (res->written == buf_len) { - res->written = 0; - res->headers.current = res->headers.current->next; - } - } - - // The headers should end with an additional newline. If there's no space left - // in the write buffer, we don't switch states so we can re-try this write - // later - if ((res->headers.current == NULL) && (conn->w.size < LNM_LOOP_BUF_SIZE)) { - conn->w.buf[conn->w.size] = '\n'; - conn->w.size++; - - // HEAD requests function exactly the same as GET requests, except that they - // skip the body writing part - ctx->state = - ctx->req.method != lnm_http_method_head && ctx->res.body.len > 0 - ? lnm_http_loop_state_write_body - : lnm_http_loop_state_finish; - } -} - -void lnm_http_loop_process_write_body(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; - lnm_http_res *res = &ctx->res; - - size_t to_write = - LNM_MIN(res->body.len - res->written, LNM_LOOP_BUF_SIZE - conn->w.size); - size_t written = 0; - - switch (res->body.type) { - case lnm_http_res_body_type_buf: - memcpy(&conn->w.buf[conn->w.size], &res->body.data.buf[res->written], - to_write); - written = to_write; - break; - case lnm_http_res_body_type_file: - written = fread(&conn->w.buf[conn->w.size], 1, to_write, res->body.data.f); - - if ((written == 0) && (ferror(res->body.data.f) != 0)) { - ctx->state = lnm_http_loop_state_finish; - } - break; - case lnm_http_res_body_type_fn: - if (res->body.data.fn(&written, &conn->w.buf[conn->w.size], conn, - res->written, to_write) != lnm_err_ok) { - ctx->state = lnm_http_loop_state_finish; - } - break; - } - - conn->w.size += written; - res->written += written; - - if (res->written == res->body.len) { - ctx->state = lnm_http_loop_state_finish; - } -} - -void lnm_http_loop_process_finish(lnm_http_conn *conn) { - // First we ensure the write buffer is fully flushed - if (conn->w.size > 0) { - return; - } - - lnm_http_loop_ctx *ctx = conn->ctx; - lnm_http_loop_ctx_reset(ctx); - - ctx->state = lnm_http_loop_state_parse_req; -} - -void (*process_fns[])(lnm_http_conn *conn) = { - lnm_http_loop_process_parse_req, - lnm_http_loop_process_route, - lnm_http_loop_process_parse_headers, - lnm_http_loop_process_steps, - lnm_http_loop_state_process_add_headers, - lnm_http_loop_process_write_status_line, - lnm_http_loop_process_write_headers, - lnm_http_loop_process_write_body, - lnm_http_loop_process_finish, -}; - -lnm_loop_state state_map[] = { - // parse_req - lnm_loop_state_req, - // route - lnm_loop_state_req, - // parse_headers - lnm_loop_state_req, - // steps - lnm_loop_state_req, - // add_headers - lnm_loop_state_req, - // write_status_line - lnm_loop_state_res, - // write_headers - lnm_loop_state_res, - // write_body - lnm_loop_state_res, - // finish - lnm_loop_state_res, -}; - -void lnm_http_loop_process(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; - - lnm_http_loop_state http_loop_state; - lnm_loop_state loop_state = conn->state; - - // We stop processing if: - // - the event loop state has been explicitely changed inside the executed - // step, as we need to switch to the other I/O loop - // - the event loop state needs to be changed because the next step should be - // run in another event loop state - // - the process fn returned without changing the HTTP loop state, indicating - // it's waiting for I/O - do { - http_loop_state = ctx->state; - - process_fns[http_loop_state](conn); - } while ((conn->state == state_map[ctx->state]) && - (http_loop_state != ctx->state)); - - // Check required to prevent overwriting manually set event loop state - conn->state = conn->state == loop_state ? state_map[ctx->state] : conn->state; - - // We move the request to a dedicated buffer if the read buffer needs to be - // reused - if ((conn->state == lnm_loop_state_req) && (conn->state == loop_state) && - (!ctx->req.buf.owned) && (ctx->req.buf.len > 0)) { - char *buf = malloc(ctx->req.buf.len); - - if (buf == NULL) { - lnm_lerror(section, - "Failed to allocate request buffer; closing connection %i", - conn->fd); - - conn->state = lnm_loop_state_end; - } else { - memcpy(buf, ctx->req.buf.s, ctx->req.buf.len); - ctx->req.buf.s = buf; - ctx->req.buf.owned = true; - - lnm_ldebug(section, "Allocated request buffer for connection %i", - conn->fd); - } - } -} diff --git a/lnm/src/http/lnm_http_loop_steps.c b/lnm/src/http/lnm_http_loop_steps.c deleted file mode 100644 index 2552e09..0000000 --- a/lnm/src/http/lnm_http_loop_steps.c +++ /dev/null @@ -1,44 +0,0 @@ -#include - -#include "lnm/http/consts.h" -#include "lnm/http/loop.h" -#include "lnm/loop.h" - -lnm_http_step_err lnm_http_loop_step_body_to_buf(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; - - if (ctx->req.body.buf == NULL) { - ctx->req.body.buf = malloc(ctx->req.body.expected_len * sizeof(char)); - ctx->req.body.len = 0; - } - - size_t to_read = LNM_MIN(conn->r.size - conn->r.read, - ctx->req.body.expected_len - ctx->req.body.len); - memcpy(&ctx->req.body.buf[ctx->req.body.len], &conn->r.buf[conn->r.read], - to_read); - ctx->req.body.len += to_read; - conn->r.read += to_read; - - return ctx->req.body.len == ctx->req.body.expected_len - ? lnm_http_step_err_done - : lnm_http_step_err_io_needed; -} - -lnm_http_step_err lnm_http_loop_step_auth(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; - - // If there's no API key, requests are always authorized - bool authorized = ctx->g->api_key == NULL; - - const char *value; - size_t value_len; - - if (!authorized && lnm_http_req_header_get_s(&value, &value_len, &ctx->req, - "X-Api-Key") == lnm_err_ok) { - authorized = (value_len == strlen(ctx->g->api_key)) && - (memcmp(value, ctx->g->api_key, value_len) == 0); - } - - ctx->res.status = authorized ? ctx->res.status : lnm_http_status_unauthorized; - return authorized ? lnm_http_step_err_done : lnm_http_step_err_res; -} diff --git a/lnm/src/http/lnm_http_req.c b/lnm/src/http/lnm_http_req.c deleted file mode 100644 index 50be3af..0000000 --- a/lnm/src/http/lnm_http_req.c +++ /dev/null @@ -1,114 +0,0 @@ -#include -#include - -#include "lnm/common.h" -#include "lnm/http/consts.h" -#include "lnm/http/loop.h" -#include "lnm/http/req.h" - -lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, - size_t len) { - const char *method; - char *path; - size_t method_len, path_len; - size_t num_headers = LNM_HTTP_MAX_REQ_HEADERS; - struct phr_header headers[LNM_HTTP_MAX_REQ_HEADERS]; - - int req_len = phr_parse_request( - buf, len, &method, &method_len, (const char **)&path, &path_len, - &req->minor_version, headers, &num_headers, req->buf.len); - - if (req_len == -1) { - req->buf.len = len; - - return lnm_http_parse_err_invalid; - } else if (req_len == -2) { - return lnm_http_parse_err_incomplete; - } - - bool known_method = false; - - for (size_t i = 0; i < lnm_http_method_names_len && !known_method; i++) { - if (strncmp(method, lnm_http_method_names[i], method_len) == 0) { - req->method = i; - known_method = true; - } - } - - if (!known_method) { - return lnm_http_parse_err_unknown_method; - } - - // Path will always end with a newline, which we can safely set to nul - path[path_len] = '\0'; - char *question_mark = strchr(path, '?'); - - // Only store query if the path doesn't simply end with a question mark - if ((question_mark != NULL) && (path_len - (question_mark + 1 - path) > 0)) { - req->query.o = question_mark + 1 - buf; - req->query.len = path_len - (question_mark + 1 - path); - - path_len = question_mark - path; - - // All parsed strings should be null-terminated. This character is either a - // newline (if at the end of the path), or a question mark (if a query is - // present). - path[path_len] = '\0'; - } - - // Also migrate headers to offset-based - for (size_t i = 0; i < num_headers; i++) { - req->headers.arr[i].name.o = headers[i].name - buf; - req->headers.arr[i].name.len = headers[i].name_len; - req->headers.arr[i].value.o = headers[i].value - buf; - req->headers.arr[i].value.len = headers[i].value_len; - } - - req->headers.len = num_headers; - - req->path.len = path_len; - req->path.o = path - buf; - - req->buf.len = req_len; - req->buf.s = buf; - req->buf.owned = false; - - return lnm_http_parse_err_ok; -} - -void lnm_http_req_reset(lnm_http_req *req) { - if (req->body.owned) { - free(req->body.buf); - } - - if (req->buf.owned) { - free(req->buf.s); - } - - memset(req, 0, sizeof(lnm_http_req)); -} - -lnm_err lnm_http_req_header_get(const char **out, size_t *out_len, - lnm_http_req *req, lnm_http_header type) { - return lnm_http_req_header_get_s(out, out_len, req, - lnm_http_header_names[type]); -} - -lnm_err lnm_http_req_header_get_s(const char **out, size_t *out_len, - lnm_http_req *req, const char *name) { - size_t name_len = strlen(name); - - for (size_t i = 0; i < req->headers.len; i++) { - const lnm_http_req_header *header = &req->headers.arr[i]; - - if (lnm_strnieq(req->buf.s + header->name.o, header->name.len, name, - name_len)) { - *out = req->buf.s + header->value.o; - *out_len = header->value.len; - - return lnm_err_ok; - } - } - - return lnm_err_not_found; -} diff --git a/lnm/src/http/lnm_http_res.c b/lnm/src/http/lnm_http_res.c deleted file mode 100644 index 3f2b0c0..0000000 --- a/lnm/src/http/lnm_http_res.c +++ /dev/null @@ -1,99 +0,0 @@ -#include - -#include "lnm/http/res.h" - -lnm_err lnm_http_res_add_header(lnm_http_res *res, lnm_http_header type, - char *value, bool value_owned) { - return lnm_http_res_add_header_len(res, type, value, strlen(value), - value_owned); -} - -lnm_err lnm_http_res_add_header_len(lnm_http_res *res, lnm_http_header type, - char *value, size_t value_len, - bool value_owned) { - lnm_http_res_header *header = calloc(1, sizeof(lnm_http_res_header)); - - if (header == NULL) { - return lnm_err_failed_alloc; - } - - lnm_http_res_header **next_ptr = &res->headers.head; - - while ((*next_ptr) != NULL) { - next_ptr = &(*next_ptr)->next; - } - - *next_ptr = header; - - // Initialize the current pointer to the head of the linked list - if (res->headers.current == NULL) { - res->headers.current = header; - } - - header->name.s = (char *)lnm_http_header_names[type]; - header->name.len = strlen(lnm_http_header_names[type]); - header->name.owned = false; - - header->value.s = value; - header->value.len = value_len; - header->value.owned = value_owned; - - return lnm_err_ok; -} - -void lnm_http_res_body_set_file(lnm_http_res *res, FILE *f, size_t len, - bool owned) { - res->body.data.f = f; - res->body.len = len; - res->body.owned = owned; - res->body.type = lnm_http_res_body_type_file; -} - -void lnm_http_res_body_set_buf(lnm_http_res *res, char *buf, size_t len, - bool owned) { - res->body.data.buf = buf; - res->body.len = len; - res->body.owned = owned; - res->body.type = lnm_http_res_body_type_buf; -} - -void lnm_http_res_body_set_fn(lnm_http_res *res, data_fn fn, size_t len) { - res->body.data.fn = fn; - res->body.len = len; - res->body.type = lnm_http_res_body_type_fn; -} - -void lnm_http_res_reset(lnm_http_res *res) { - lnm_http_res_header *header = res->headers.head; - - while (header != NULL) { - lnm_http_res_header *next = header->next; - - if (header->name.owned) { - free(header->name.s); - } - - if (header->value.owned) { - free(header->value.s); - } - - free(header); - - header = next; - } - - if (res->body.owned) { - switch (res->body.type) { - case lnm_http_res_body_type_file: - fclose(res->body.data.f); - break; - case lnm_http_res_body_type_buf: - free(res->body.data.buf); - break; - case lnm_http_res_body_type_fn: - break; - } - } - - memset(res, 0, sizeof(lnm_http_res)); -} diff --git a/lnm/src/lnm_log.c b/lnm/src/lnm_log.c deleted file mode 100644 index 15fb00f..0000000 --- a/lnm/src/lnm_log.c +++ /dev/null @@ -1,86 +0,0 @@ -#include -#include - -#include "lnm/common.h" -#include "lnm/log_internal.h" - -const char *lnm_log_level_names[] = {"DEBUG ", "INFO ", "NOTICE ", - "WARNING ", "ERROR ", "CRITICAL"}; - -lnm_logger *global_logger = NULL; - -lnm_err lnm_log_init_global() { - global_logger = calloc(1, sizeof(lnm_logger)); - - return global_logger == NULL ? lnm_err_failed_alloc : lnm_err_ok; -} - -lnm_err lnm_logger_stream_register(lnm_logger *logger, - lnm_logger_stream *stream) { - lnm_logger_stream **new = - logger->streams.len == 0 - ? malloc(sizeof(lnm_logger_stream *)) - : realloc(logger->streams.arr, - (logger->streams.len + 1) * sizeof(lnm_logger_stream *)); - - if (new == NULL) { - return lnm_err_failed_alloc; - } - - new[logger->streams.len] = stream; - logger->streams.arr = new; - logger->streams.len++; - - return lnm_err_ok; -} - -lnm_err lnm_log_register_stdout(lnm_log_level level) { - lnm_logger_stream *stream = malloc(sizeof(lnm_logger_stream)); - - if (stream == NULL) { - return lnm_err_failed_alloc; - } - - stream->type = lnm_logger_stream_type_file; - stream->ptr = stdout; - stream->level = level; - - LNM_RES2(lnm_logger_stream_register(global_logger, stream), free(stream)); - - return lnm_err_ok; -} - -void lnm_vlog(lnm_log_level level, const char *section, const char *fmt, - va_list ap) { - char date_str[32]; - - time_t now = time(NULL); - strftime(date_str, sizeof(date_str) - 1, "%Y-%m-%d %H:%M:%S", - localtime(&now)); - - for (size_t i = 0; i < global_logger->streams.len; i++) { - lnm_logger_stream *stream = global_logger->streams.arr[i]; - - if (level < stream->level) { - continue; - } - - switch (stream->type) { - case lnm_logger_stream_type_file: - fprintf(stream->ptr, "[%s][%s][%s] ", date_str, - lnm_log_level_names[level], section); - vfprintf(stream->ptr, fmt, ap); - fprintf(stream->ptr, "\n"); - break; - } - - va_end(ap); - } -} - -void lnm_log(lnm_log_level level, const char *section, const char *fmt, ...) { - va_list ap; - va_start(ap, fmt); - lnm_vlog(level, section, fmt, ap); - va_end(ap); -} diff --git a/lnm/src/lnm_utils.c b/lnm/src/lnm_utils.c deleted file mode 100644 index 85094bd..0000000 --- a/lnm/src/lnm_utils.c +++ /dev/null @@ -1,57 +0,0 @@ -#include -#include - -#include "lnm/common.h" - -bool lnm_strneq(const char *s1, size_t s1_len, const char *s2, size_t s2_len) { - return (s1_len == s2_len) && (memcmp(s1, s2, s1_len) == 0); -} - -bool lnm_strnieq(const char *s1, size_t s1_len, const char *s2, size_t s2_len) { - bool equal = s1_len == s2_len; - - for (size_t i = 0; i < s1_len && equal; i++) { - equal = s1[i] == s2[i] || - (('a' <= s1[i]) && (s1[i] <= 'z') && (s1[i] - 32 == s2[i])); - } - - return equal; -} - -uint64_t lnm_ipow(uint64_t base, uint64_t power) { - uint64_t res = 1; - - while (power > 0) { - res *= base; - power--; - } - - return res; -} - -uint64_t lnm_atoi(const char *s, size_t len) { - uint64_t res = 0; - - for (size_t i = 0; i < len; i++) { - if (s[i] < '0' || '9' < s[i]) { - return 0; - } - - uint64_t val = s[i] - '0'; - res += val * lnm_ipow(10, (len - 1) - i); - } - - return res; -} - -uint64_t lnm_digits(uint64_t num) { - int digits = 1; - - while (num > 9) { - digits++; - - num /= 10; - } - - return digits; -} diff --git a/lnm/src/loop/lnm_loop.c b/lnm/src/loop/lnm_loop.c deleted file mode 100644 index 6c9a854..0000000 --- a/lnm/src/loop/lnm_loop.c +++ /dev/null @@ -1,232 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "lnm/common.h" -#include "lnm/log.h" -#include "lnm/loop.h" -#include "lnm/loop_internal.h" - -static const char *section = "loop"; - -lnm_err lnm_loop_init(lnm_loop **out, void *gctx, - lnm_err (*ctx_init)(void **out, void *gctx), - void (*ctx_free)(void *ctx), - void (*data_read)(lnm_loop_conn *conn), - void (*data_write)(lnm_loop_conn *conn)) { - lnm_loop *l = calloc(1, sizeof(lnm_loop)); - - if (l == NULL) { - return lnm_err_failed_alloc; - } - - l->gctx = gctx; - l->ctx_init = ctx_init; - l->ctx_free = ctx_free; - l->data_read = data_read; - l->data_write = data_write; - - *out = l; - - return lnm_err_ok; -} - -lnm_err lnm_loop_accept(lnm_loop *l) { - int conn_fd = accept(l->listen_fd, NULL, NULL); - - if (conn_fd < 0) { - lnm_lcritical(section, "accept failed: %i", conn_fd); - - return lnm_err_failed_network; - } - - // Set socket to non-blocking - int flags = fcntl(conn_fd, F_GETFL); - flags |= O_NONBLOCK; - fcntl(conn_fd, F_SETFL, flags); - - lnm_loop_conn *conn; - LNM_RES2(lnm_loop_conn_init(&conn, l), close(conn_fd)); - - conn->fd = conn_fd; - conn->state = lnm_loop_state_req; - - struct epoll_event event = {.data.ptr = conn, - .events = EPOLLIN | EPOLLET | EPOLLONESHOT}; - - epoll_ctl(l->epoll_fd, EPOLL_CTL_ADD, conn_fd, &event); - - l->open++; - - lnm_ldebug(section, "connection opened with fd %i", conn_fd); - - return lnm_err_ok; -} - -lnm_err lnm_loop_setup(lnm_loop *l, uint16_t port) { - int listen_fd = socket(AF_INET, SOCK_STREAM, 0); - - if (listen_fd < 0) { - return lnm_err_failed_network; - } - - int val = 1; - int res = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(int)); - - if (res < 0) { - return lnm_err_failed_network; - } - - struct sockaddr_in addr = {.sin_family = AF_INET, - .sin_port = ntohs(port), - .sin_addr.s_addr = ntohl(0)}; - - res = bind(listen_fd, (const struct sockaddr *)&addr, sizeof(addr)); - - if (res < 0) { - return lnm_err_failed_network; - } - - res = listen(listen_fd, SOMAXCONN); - - if (res < 0) { - return lnm_err_failed_network; - } - - int flags = fcntl(listen_fd, F_GETFL); - fcntl(listen_fd, F_SETFL, flags | O_NONBLOCK); - - int epoll_fd = epoll_create1(0); - - if (epoll_fd < 0) { - return lnm_err_failed_network; - } - - struct epoll_event event = { - // The listening socket is marked using a NULL data field - .data.ptr = NULL, - .events = EPOLLIN | EPOLLET | EPOLLONESHOT}; - - res = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event); - - if (res < 0) { - return lnm_err_failed_network; - } - - l->listen_fd = listen_fd; - l->epoll_fd = epoll_fd; - - return lnm_err_ok; -} - -typedef struct lnm_loop_thread_args { - lnm_loop *l; - int id; - int thread_count; -} lnm_loop_thread_args; - -lnm_err lnm_loop_run_thread(lnm_loop_thread_args *args) { - lnm_loop *l = args->l; - int thread_id = args->id; - int thread_count = args->thread_count; - - struct epoll_event *events = calloc(1, sizeof(struct epoll_event)); - int events_cap = 1; - - if (events == NULL) { - return lnm_err_failed_alloc; - } - - lnm_linfo(section, "thread %i started", thread_id); - - struct epoll_event listen_event = { - .data.ptr = NULL, .events = EPOLLIN | EPOLLET | EPOLLONESHOT}; - - while (1) { - int polled = epoll_wait(l->epoll_fd, events, events_cap, -1); - lnm_ldebug(section, "polled (thread %i): %i", thread_id, polled); - - if (polled < 0) { - return lnm_err_failed_poll; - } - - for (int i = 0; i < polled; i++) { - if (events[i].data.ptr == NULL) { - lnm_loop_accept(l); - - epoll_ctl(l->epoll_fd, EPOLL_CTL_MOD, l->listen_fd, &listen_event); - } else { - lnm_loop_conn *conn = events[i].data.ptr; - lnm_loop_conn_io(l, conn); - - if (conn->state == lnm_loop_state_end) { - int conn_fd = conn->fd; - - lnm_loop_conn_free(l, conn); - close(conn_fd); - l->open--; - - epoll_ctl(l->epoll_fd, EPOLL_CTL_DEL, conn_fd, NULL); - - lnm_ldebug(section, "connection closed with fd %i", conn_fd); - } else { - struct epoll_event event = { - .data.ptr = conn, - .events = - (conn->state == lnm_loop_state_req ? EPOLLIN : EPOLLOUT) | - EPOLLET | EPOLLONESHOT}; - epoll_ctl(l->epoll_fd, EPOLL_CTL_MOD, conn->fd, &event); - } - } - } - - int open = l->open; - int cap_per_thread = - open + 1 > thread_count ? (open + 1) / thread_count : 1; - - if (cap_per_thread > events_cap) { - struct epoll_event *new_events = - malloc(cap_per_thread * sizeof(struct epoll_event)); - - if (new_events == NULL) { - return lnm_err_failed_alloc; - } - - free(events); - events = new_events; - events_cap = cap_per_thread; - } - } - - return lnm_err_ok; -} - -lnm_err lnm_loop_run(lnm_loop *l, int thread_count) { - if (l->epoll_fd == 0) { - return lnm_err_not_setup; - } - - lnm_loop_thread_args args[thread_count]; - - for (int i = 1; i < thread_count; i++) { - args[i].l = l; - args[i].id = i; - args[i].thread_count = thread_count; - - pthread_t thread; - pthread_create(&thread, NULL, (void *(*)(void *))lnm_loop_run_thread, - &args[i]); - } - - args[0].l = l; - args[0].id = 0; - args[0].thread_count = thread_count; - - lnm_loop_run_thread(&args[0]); - - return lnm_err_ok; -} diff --git a/lnm/src/loop/lnm_loop_conn.c b/lnm/src/loop/lnm_loop_conn.c deleted file mode 100644 index 49b86ad..0000000 --- a/lnm/src/loop/lnm_loop_conn.c +++ /dev/null @@ -1,22 +0,0 @@ -#include "lnm/loop_internal.h" - -lnm_err lnm_loop_conn_init(lnm_loop_conn **out, lnm_loop *l) { - lnm_loop_conn *conn = calloc(1, sizeof(lnm_loop_conn)); - - if (conn == NULL) { - return lnm_err_failed_alloc; - } - - void *ctx; - LNM_RES2(l->ctx_init(&ctx, l->gctx), free(conn)); - - conn->ctx = ctx; - *out = conn; - - return lnm_err_ok; -} - -void lnm_loop_conn_free(lnm_loop *l, lnm_loop_conn *conn) { - l->ctx_free(conn->ctx); - free(conn); -} diff --git a/lnm/src/loop/lnm_loop_io.c b/lnm/src/loop/lnm_loop_io.c deleted file mode 100644 index 363d6af..0000000 --- a/lnm/src/loop/lnm_loop_io.c +++ /dev/null @@ -1,77 +0,0 @@ -#include -#include -#include - -#include "lnm/loop.h" -#include "lnm/loop_internal.h" - -void lnm_loop_conn_io_req(lnm_loop *l, lnm_loop_conn *conn) { - do { - // Move remaining data to front of buffer - memmove(conn->r.buf, &conn->r.buf[conn->r.read], - conn->r.size - conn->r.read); - conn->r.size -= conn->r.read; - conn->r.read = 0; - - ssize_t res; - size_t cap = LNM_LOOP_BUF_SIZE - conn->r.size; - - do { - res = read(conn->fd, &conn->r.buf[conn->r.size], cap); - } while (res < 0 && errno == EINTR); - - // Read can't be performed without blocking; we come back later - if (res < 0 && errno == EAGAIN) { - return; - } - - if (res <= 0) { - conn->state = lnm_loop_state_end; - - return; - } - - conn->r.size += res; - l->data_read(conn); - } while (conn->state == lnm_loop_state_req); -} - -void lnm_loop_conn_io_res(lnm_loop *l, lnm_loop_conn *conn) { - do { - l->data_write(conn); - - ssize_t res; - - do { - res = write(conn->fd, conn->w.buf, conn->w.size); - } while (res < 0 && errno == EINTR); - - // Write can't be performed without blocking; we come back later - if (res < 0 && errno == EAGAIN) { - return; - } - - if (res < 0) { - conn->state = lnm_loop_state_end; - - return; - } - - // Move remaining data to front of buffer. Doing this here gives the data - // writer function more space to work with - memmove(conn->w.buf, &conn->w.buf[res], conn->w.size - res); - conn->w.size -= res; - } while (conn->state == lnm_loop_state_res); -} - -void lnm_loop_conn_io(lnm_loop *l, lnm_loop_conn *conn) { - switch (conn->state) { - case lnm_loop_state_req: - lnm_loop_conn_io_req(l, conn); - break; - case lnm_loop_state_res: - lnm_loop_conn_io_res(l, conn); - break; - default:; - } -} diff --git a/lnm/src/picohttpparser.c b/lnm/src/picohttpparser.c deleted file mode 100644 index 5e5783a..0000000 --- a/lnm/src/picohttpparser.c +++ /dev/null @@ -1,665 +0,0 @@ -/* - * Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, - * Shigeo Mitsunari - * - * The software is licensed under either the MIT License (below) or the Perl - * license. - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to - * deal in the Software without restriction, including without limitation the - * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS - * IN THE SOFTWARE. - */ - -#include -#include -#include -#ifdef __SSE4_2__ -#ifdef _MSC_VER -#include -#else -#include -#endif -#endif -#include "picohttpparser.h" - -#if __GNUC__ >= 3 -#define likely(x) __builtin_expect(!!(x), 1) -#define unlikely(x) __builtin_expect(!!(x), 0) -#else -#define likely(x) (x) -#define unlikely(x) (x) -#endif - -#ifdef _MSC_VER -#define ALIGNED(n) _declspec(align(n)) -#else -#define ALIGNED(n) __attribute__((aligned(n))) -#endif - -#define IS_PRINTABLE_ASCII(c) ((unsigned char)(c)-040u < 0137u) - -#define CHECK_EOF() \ - if (buf == buf_end) { \ - *ret = -2; \ - return NULL; \ - } - -#define EXPECT_CHAR_NO_CHECK(ch) \ - if (*buf++ != ch) { \ - *ret = -1; \ - return NULL; \ - } - -#define EXPECT_CHAR(ch) \ - CHECK_EOF(); \ - EXPECT_CHAR_NO_CHECK(ch); - -#define ADVANCE_TOKEN(tok, toklen) \ - do { \ - const char *tok_start = buf; \ - static const char ALIGNED(16) ranges2[16] = "\000\040\177\177"; \ - int found2; \ - buf = findchar_fast(buf, buf_end, ranges2, 4, &found2); \ - if (!found2) { \ - CHECK_EOF(); \ - } \ - while (1) { \ - if (*buf == ' ') { \ - break; \ - } else if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { \ - if ((unsigned char)*buf < '\040' || *buf == '\177') { \ - *ret = -1; \ - return NULL; \ - } \ - } \ - ++buf; \ - CHECK_EOF(); \ - } \ - tok = tok_start; \ - toklen = buf - tok_start; \ - } while (0) - -static const char *token_char_map = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\1\0\1\1\1\1\1\0\0\1\1\0\1\1\0\1\1\1\1\1\1\1\1\1\1\0\0\0\0\0\0" - "\0\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\0\0\1\1" - "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\0\1\0\1\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" - "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"; - -static const char *findchar_fast(const char *buf, const char *buf_end, const char *ranges, size_t ranges_size, int *found) -{ - *found = 0; -#if __SSE4_2__ - if (likely(buf_end - buf >= 16)) { - __m128i ranges16 = _mm_loadu_si128((const __m128i *)ranges); - - size_t left = (buf_end - buf) & ~15; - do { - __m128i b16 = _mm_loadu_si128((const __m128i *)buf); - int r = _mm_cmpestri(ranges16, ranges_size, b16, 16, _SIDD_LEAST_SIGNIFICANT | _SIDD_CMP_RANGES | _SIDD_UBYTE_OPS); - if (unlikely(r != 16)) { - buf += r; - *found = 1; - break; - } - buf += 16; - left -= 16; - } while (likely(left != 0)); - } -#else - /* suppress unused parameter warning */ - (void)buf_end; - (void)ranges; - (void)ranges_size; -#endif - return buf; -} - -static const char *get_token_to_eol(const char *buf, const char *buf_end, const char **token, size_t *token_len, int *ret) -{ - const char *token_start = buf; - -#ifdef __SSE4_2__ - static const char ALIGNED(16) ranges1[16] = "\0\010" /* allow HT */ - "\012\037" /* allow SP and up to but not including DEL */ - "\177\177"; /* allow chars w. MSB set */ - int found; - buf = findchar_fast(buf, buf_end, ranges1, 6, &found); - if (found) - goto FOUND_CTL; -#else - /* find non-printable char within the next 8 bytes, this is the hottest code; manually inlined */ - while (likely(buf_end - buf >= 8)) { -#define DOIT() \ - do { \ - if (unlikely(!IS_PRINTABLE_ASCII(*buf))) \ - goto NonPrintable; \ - ++buf; \ - } while (0) - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); - DOIT(); -#undef DOIT - continue; - NonPrintable: - if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { - goto FOUND_CTL; - } - ++buf; - } -#endif - for (;; ++buf) { - CHECK_EOF(); - if (unlikely(!IS_PRINTABLE_ASCII(*buf))) { - if ((likely((unsigned char)*buf < '\040') && likely(*buf != '\011')) || unlikely(*buf == '\177')) { - goto FOUND_CTL; - } - } - } -FOUND_CTL: - if (likely(*buf == '\015')) { - ++buf; - EXPECT_CHAR('\012'); - *token_len = buf - 2 - token_start; - } else if (*buf == '\012') { - *token_len = buf - token_start; - ++buf; - } else { - *ret = -1; - return NULL; - } - *token = token_start; - - return buf; -} - -static const char *is_complete(const char *buf, const char *buf_end, size_t last_len, int *ret) -{ - int ret_cnt = 0; - buf = last_len < 3 ? buf : buf + last_len - 3; - - while (1) { - CHECK_EOF(); - if (*buf == '\015') { - ++buf; - CHECK_EOF(); - EXPECT_CHAR('\012'); - ++ret_cnt; - } else if (*buf == '\012') { - ++buf; - ++ret_cnt; - } else { - ++buf; - ret_cnt = 0; - } - if (ret_cnt == 2) { - return buf; - } - } - - *ret = -2; - return NULL; -} - -#define PARSE_INT(valp_, mul_) \ - if (*buf < '0' || '9' < *buf) { \ - buf++; \ - *ret = -1; \ - return NULL; \ - } \ - *(valp_) = (mul_) * (*buf++ - '0'); - -#define PARSE_INT_3(valp_) \ - do { \ - int res_ = 0; \ - PARSE_INT(&res_, 100) \ - *valp_ = res_; \ - PARSE_INT(&res_, 10) \ - *valp_ += res_; \ - PARSE_INT(&res_, 1) \ - *valp_ += res_; \ - } while (0) - -/* returned pointer is always within [buf, buf_end), or null */ -static const char *parse_token(const char *buf, const char *buf_end, const char **token, size_t *token_len, char next_char, - int *ret) -{ - /* We use pcmpestri to detect non-token characters. This instruction can take no more than eight character ranges (8*2*8=128 - * bits that is the size of a SSE register). Due to this restriction, characters `|` and `~` are handled in the slow loop. */ - static const char ALIGNED(16) ranges[] = "\x00 " /* control chars and up to SP */ - "\"\"" /* 0x22 */ - "()" /* 0x28,0x29 */ - ",," /* 0x2c */ - "//" /* 0x2f */ - ":@" /* 0x3a-0x40 */ - "[]" /* 0x5b-0x5d */ - "{\xff"; /* 0x7b-0xff */ - const char *buf_start = buf; - int found; - buf = findchar_fast(buf, buf_end, ranges, sizeof(ranges) - 1, &found); - if (!found) { - CHECK_EOF(); - } - while (1) { - if (*buf == next_char) { - break; - } else if (!token_char_map[(unsigned char)*buf]) { - *ret = -1; - return NULL; - } - ++buf; - CHECK_EOF(); - } - *token = buf_start; - *token_len = buf - buf_start; - return buf; -} - -/* returned pointer is always within [buf, buf_end), or null */ -static const char *parse_http_version(const char *buf, const char *buf_end, int *minor_version, int *ret) -{ - /* we want at least [HTTP/1.] to try to parse */ - if (buf_end - buf < 9) { - *ret = -2; - return NULL; - } - EXPECT_CHAR_NO_CHECK('H'); - EXPECT_CHAR_NO_CHECK('T'); - EXPECT_CHAR_NO_CHECK('T'); - EXPECT_CHAR_NO_CHECK('P'); - EXPECT_CHAR_NO_CHECK('/'); - EXPECT_CHAR_NO_CHECK('1'); - EXPECT_CHAR_NO_CHECK('.'); - PARSE_INT(minor_version, 1); - return buf; -} - -static const char *parse_headers(const char *buf, const char *buf_end, struct phr_header *headers, size_t *num_headers, - size_t max_headers, int *ret) -{ - for (;; ++*num_headers) { - CHECK_EOF(); - if (*buf == '\015') { - ++buf; - EXPECT_CHAR('\012'); - break; - } else if (*buf == '\012') { - ++buf; - break; - } - if (*num_headers == max_headers) { - *ret = -1; - return NULL; - } - if (!(*num_headers != 0 && (*buf == ' ' || *buf == '\t'))) { - /* parsing name, but do not discard SP before colon, see - * http://www.mozilla.org/security/announce/2006/mfsa2006-33.html */ - if ((buf = parse_token(buf, buf_end, &headers[*num_headers].name, &headers[*num_headers].name_len, ':', ret)) == NULL) { - return NULL; - } - if (headers[*num_headers].name_len == 0) { - *ret = -1; - return NULL; - } - ++buf; - for (;; ++buf) { - CHECK_EOF(); - if (!(*buf == ' ' || *buf == '\t')) { - break; - } - } - } else { - headers[*num_headers].name = NULL; - headers[*num_headers].name_len = 0; - } - const char *value; - size_t value_len; - if ((buf = get_token_to_eol(buf, buf_end, &value, &value_len, ret)) == NULL) { - return NULL; - } - /* remove trailing SPs and HTABs */ - const char *value_end = value + value_len; - for (; value_end != value; --value_end) { - const char c = *(value_end - 1); - if (!(c == ' ' || c == '\t')) { - break; - } - } - headers[*num_headers].value = value; - headers[*num_headers].value_len = value_end - value; - } - return buf; -} - -static const char *parse_request(const char *buf, const char *buf_end, const char **method, size_t *method_len, const char **path, - size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, - size_t max_headers, int *ret) -{ - /* skip first empty line (some clients add CRLF after POST content) */ - CHECK_EOF(); - if (*buf == '\015') { - ++buf; - EXPECT_CHAR('\012'); - } else if (*buf == '\012') { - ++buf; - } - - /* parse request line */ - if ((buf = parse_token(buf, buf_end, method, method_len, ' ', ret)) == NULL) { - return NULL; - } - do { - ++buf; - CHECK_EOF(); - } while (*buf == ' '); - ADVANCE_TOKEN(*path, *path_len); - do { - ++buf; - CHECK_EOF(); - } while (*buf == ' '); - if (*method_len == 0 || *path_len == 0) { - *ret = -1; - return NULL; - } - if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { - return NULL; - } - if (*buf == '\015') { - ++buf; - EXPECT_CHAR('\012'); - } else if (*buf == '\012') { - ++buf; - } else { - *ret = -1; - return NULL; - } - - return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); -} - -int phr_parse_request(const char *buf_start, size_t len, const char **method, size_t *method_len, const char **path, - size_t *path_len, int *minor_version, struct phr_header *headers, size_t *num_headers, size_t last_len) -{ - const char *buf = buf_start, *buf_end = buf_start + len; - size_t max_headers = *num_headers; - int r; - - *method = NULL; - *method_len = 0; - *path = NULL; - *path_len = 0; - *minor_version = -1; - *num_headers = 0; - - /* if last_len != 0, check if the request is complete (a fast countermeasure - againt slowloris */ - if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { - return r; - } - - if ((buf = parse_request(buf, buf_end, method, method_len, path, path_len, minor_version, headers, num_headers, max_headers, - &r)) == NULL) { - return r; - } - - return (int)(buf - buf_start); -} - -static const char *parse_response(const char *buf, const char *buf_end, int *minor_version, int *status, const char **msg, - size_t *msg_len, struct phr_header *headers, size_t *num_headers, size_t max_headers, int *ret) -{ - /* parse "HTTP/1.x" */ - if ((buf = parse_http_version(buf, buf_end, minor_version, ret)) == NULL) { - return NULL; - } - /* skip space */ - if (*buf != ' ') { - *ret = -1; - return NULL; - } - do { - ++buf; - CHECK_EOF(); - } while (*buf == ' '); - /* parse status code, we want at least [:digit:][:digit:][:digit:] to try to parse */ - if (buf_end - buf < 4) { - *ret = -2; - return NULL; - } - PARSE_INT_3(status); - - /* get message including preceding space */ - if ((buf = get_token_to_eol(buf, buf_end, msg, msg_len, ret)) == NULL) { - return NULL; - } - if (*msg_len == 0) { - /* ok */ - } else if (**msg == ' ') { - /* Remove preceding space. Successful return from `get_token_to_eol` guarantees that we would hit something other than SP - * before running past the end of the given buffer. */ - do { - ++*msg; - --*msg_len; - } while (**msg == ' '); - } else { - /* garbage found after status code */ - *ret = -1; - return NULL; - } - - return parse_headers(buf, buf_end, headers, num_headers, max_headers, ret); -} - -int phr_parse_response(const char *buf_start, size_t len, int *minor_version, int *status, const char **msg, size_t *msg_len, - struct phr_header *headers, size_t *num_headers, size_t last_len) -{ - const char *buf = buf_start, *buf_end = buf + len; - size_t max_headers = *num_headers; - int r; - - *minor_version = -1; - *status = 0; - *msg = NULL; - *msg_len = 0; - *num_headers = 0; - - /* if last_len != 0, check if the response is complete (a fast countermeasure - against slowloris */ - if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { - return r; - } - - if ((buf = parse_response(buf, buf_end, minor_version, status, msg, msg_len, headers, num_headers, max_headers, &r)) == NULL) { - return r; - } - - return (int)(buf - buf_start); -} - -int phr_parse_headers(const char *buf_start, size_t len, struct phr_header *headers, size_t *num_headers, size_t last_len) -{ - const char *buf = buf_start, *buf_end = buf + len; - size_t max_headers = *num_headers; - int r; - - *num_headers = 0; - - /* if last_len != 0, check if the response is complete (a fast countermeasure - against slowloris */ - if (last_len != 0 && is_complete(buf, buf_end, last_len, &r) == NULL) { - return r; - } - - if ((buf = parse_headers(buf, buf_end, headers, num_headers, max_headers, &r)) == NULL) { - return r; - } - - return (int)(buf - buf_start); -} - -enum { - CHUNKED_IN_CHUNK_SIZE, - CHUNKED_IN_CHUNK_EXT, - CHUNKED_IN_CHUNK_DATA, - CHUNKED_IN_CHUNK_CRLF, - CHUNKED_IN_TRAILERS_LINE_HEAD, - CHUNKED_IN_TRAILERS_LINE_MIDDLE -}; - -static int decode_hex(int ch) -{ - if ('0' <= ch && ch <= '9') { - return ch - '0'; - } else if ('A' <= ch && ch <= 'F') { - return ch - 'A' + 0xa; - } else if ('a' <= ch && ch <= 'f') { - return ch - 'a' + 0xa; - } else { - return -1; - } -} - -ssize_t phr_decode_chunked(struct phr_chunked_decoder *decoder, char *buf, size_t *_bufsz) -{ - size_t dst = 0, src = 0, bufsz = *_bufsz; - ssize_t ret = -2; /* incomplete */ - - while (1) { - switch (decoder->_state) { - case CHUNKED_IN_CHUNK_SIZE: - for (;; ++src) { - int v; - if (src == bufsz) - goto Exit; - if ((v = decode_hex(buf[src])) == -1) { - if (decoder->_hex_count == 0) { - ret = -1; - goto Exit; - } - break; - } - if (decoder->_hex_count == sizeof(size_t) * 2) { - ret = -1; - goto Exit; - } - decoder->bytes_left_in_chunk = decoder->bytes_left_in_chunk * 16 + v; - ++decoder->_hex_count; - } - decoder->_hex_count = 0; - decoder->_state = CHUNKED_IN_CHUNK_EXT; - /* fallthru */ - case CHUNKED_IN_CHUNK_EXT: - /* RFC 7230 A.2 "Line folding in chunk extensions is disallowed" */ - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] == '\012') - break; - } - ++src; - if (decoder->bytes_left_in_chunk == 0) { - if (decoder->consume_trailer) { - decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; - break; - } else { - goto Complete; - } - } - decoder->_state = CHUNKED_IN_CHUNK_DATA; - /* fallthru */ - case CHUNKED_IN_CHUNK_DATA: { - size_t avail = bufsz - src; - if (avail < decoder->bytes_left_in_chunk) { - if (dst != src) - memmove(buf + dst, buf + src, avail); - src += avail; - dst += avail; - decoder->bytes_left_in_chunk -= avail; - goto Exit; - } - if (dst != src) - memmove(buf + dst, buf + src, decoder->bytes_left_in_chunk); - src += decoder->bytes_left_in_chunk; - dst += decoder->bytes_left_in_chunk; - decoder->bytes_left_in_chunk = 0; - decoder->_state = CHUNKED_IN_CHUNK_CRLF; - } - /* fallthru */ - case CHUNKED_IN_CHUNK_CRLF: - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] != '\015') - break; - } - if (buf[src] != '\012') { - ret = -1; - goto Exit; - } - ++src; - decoder->_state = CHUNKED_IN_CHUNK_SIZE; - break; - case CHUNKED_IN_TRAILERS_LINE_HEAD: - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] != '\015') - break; - } - if (buf[src++] == '\012') - goto Complete; - decoder->_state = CHUNKED_IN_TRAILERS_LINE_MIDDLE; - /* fallthru */ - case CHUNKED_IN_TRAILERS_LINE_MIDDLE: - for (;; ++src) { - if (src == bufsz) - goto Exit; - if (buf[src] == '\012') - break; - } - ++src; - decoder->_state = CHUNKED_IN_TRAILERS_LINE_HEAD; - break; - default: - assert(!"decoder is corrupt"); - } - } - -Complete: - ret = bufsz - src; -Exit: - if (dst != src) - memmove(buf + dst, buf + src, bufsz - src); - *_bufsz = dst; - return ret; -} - -int phr_decode_chunked_is_in_data(struct phr_chunked_decoder *decoder) -{ - return decoder->_state == CHUNKED_IN_CHUNK_DATA; -} - -#undef CHECK_EOF -#undef EXPECT_CHAR -#undef ADVANCE_TOKEN From 44ba4b053ed27c9b18a98269f2bb3f2ee4cffb65 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 2 Mar 2024 23:31:34 +0100 Subject: [PATCH 41/45] refactor(lander): partial migration to updated lnm framework --- .gitmodules | 3 ++ lnm | 1 + src/lander/lander_get.c | 9 +++-- src/lander/lander_post.c | 23 +++++------ src/main.c | 84 ++++++++++++++++++++++++---------------- 5 files changed, 71 insertions(+), 49 deletions(-) create mode 100644 .gitmodules create mode 160000 lnm diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..2fe4b2f --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lnm"] + path = lnm + url = https://git.rustybever.be/Chewing_Bever/lnm diff --git a/lnm b/lnm new file mode 160000 index 0000000..6eab5d6 --- /dev/null +++ b/lnm @@ -0,0 +1 @@ +Subproject commit 6eab5d616c2772a625b521e19d50020156c72ca2 diff --git a/src/lander/lander_get.c b/src/lander/lander_get.c index aaf35de..7d4a186 100644 --- a/src/lander/lander_get.c +++ b/src/lander/lander_get.c @@ -3,6 +3,7 @@ #include "lnm/http/consts.h" #include "lnm/http/loop.h" +#include "lnm/http/req.h" #include "lnm/log.h" #include "lnm/loop.h" #include "lsm/store.h" @@ -102,9 +103,8 @@ lnm_http_step_err lander_get_entry(lnm_http_conn *conn) { lnm_http_loop_gctx *gctx = ctx->g; lander_gctx *c_gctx = gctx->c; - const char *key_s = - &ctx->req.buf.s[ctx->req.path.o + ctx->req.path.groups[1].rm_so]; - int key_len = ctx->req.path.groups[1].rm_eo - ctx->req.path.groups[1].rm_so; + const char *key_s; + size_t key_len = lnm_http_req_route_segment(&key_s, &ctx->req, "key"); lsm_str *key; lsm_str_init_copy_n(&key, (char *)key_s, key_len); @@ -138,6 +138,9 @@ lnm_http_step_err lander_get_entry(lnm_http_conn *conn) { case lander_entry_type_file: res = lander_get_file(conn); break; + default: + ctx->res.status = lnm_http_status_internal_server_error; + res = lnm_http_step_err_res; } return res; diff --git a/src/lander/lander_post.c b/src/lander/lander_post.c index 8b14410..f5e877a 100644 --- a/src/lander/lander_post.c +++ b/src/lander/lander_post.c @@ -1,6 +1,7 @@ #include #include "lnm/loop.h" +#include "lnm/http/req.h" #include "lsm/store.h" #include "lander.h" @@ -20,28 +21,24 @@ static void randomize_key(char *key, int len) { * * @return true on success, false otherwise */ -bool lander_insert_entry(lnm_http_loop_ctx *ctx) { +bool lander_insert_entry(lnm_http_loop_ctx *ctx, bool secure) { lnm_http_loop_gctx *gctx = ctx->g; lander_gctx *c_gctx = gctx->c; lander_ctx *c_ctx = ctx->c; - lsm_str *key; - int key_len; + const char *key_s; + size_t key_len = lnm_http_req_route_segment(&key_s, &ctx->req, "key"); - if (ctx->req.path.groups[2].rm_eo == ctx->req.path.groups[2].rm_so) { + lsm_str *key; + + if (key_len == 0) { // Generate a random key to insert - bool secure = - (ctx->req.path.groups[1].rm_eo - ctx->req.path.groups[1].rm_so) == 1; key_len = secure ? 16 : 4; char *key_s = malloc((key_len + 1) * sizeof(char)); randomize_key(key_s, key_len); lsm_str_init(&key, key_s); } else { - const char *key_s = - &ctx->req.buf.s[ctx->req.path.o + ctx->req.path.groups[2].rm_so]; - key_len = ctx->req.path.groups[2].rm_eo - ctx->req.path.groups[2].rm_so; - lsm_str_init_copy_n(&key, key_s, key_len); } @@ -73,7 +70,7 @@ lnm_http_step_err lander_post_redirect(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; - if (!lander_insert_entry(ctx)) { + if (!lander_insert_entry(ctx, false)) { return lnm_http_step_err_res; } @@ -98,7 +95,7 @@ lnm_http_step_err lander_post_paste(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; - if (!lander_insert_entry(ctx)) { + if (!lander_insert_entry(ctx, false)) { return lnm_http_step_err_res; } @@ -113,7 +110,7 @@ lnm_http_step_err lander_post_file(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; - if (!lander_insert_entry(ctx)) { + if (!lander_insert_entry(ctx, false)) { return lnm_http_step_err_res; } diff --git a/src/main.c b/src/main.c index 6cacf84..28d8ef4 100644 --- a/src/main.c +++ b/src/main.c @@ -12,49 +12,67 @@ const char *lander_server = "lander/" LANDER_VERSION; lnm_http_loop *loop_init(lander_gctx *gctx, const char *api_key) { lnm_http_loop *hl; - lnm_http_step *step = NULL; - lnm_http_route *route; lnm_http_loop_init(&hl, gctx, lander_ctx_init, (lnm_http_ctx_reset_fn)lander_ctx_reset, (lnm_http_ctx_free_fn)lander_ctx_free); lnm_http_loop_set_api_key(hl, api_key); lnm_http_loop_set_server(hl, lander_server); - lnm_http_step_init(&step, lander_get_index); - lnm_http_route_init_literal(&route, lnm_http_method_get, "/", step); - lnm_http_loop_route_add(hl, route); + lnm_http_router *router; + lnm_http_router_init(&router); - lnm_http_step_init(&step, lander_get_entry); - lnm_http_route_init_regex(&route, lnm_http_method_get, "^/([^/]+)$", 1, step); - lnm_http_loop_route_add(hl, route); + lnm_http_route *route; + lnm_http_router_add(&route, router, lnm_http_method_get, "/"); + lnm_http_route_step_append(route, lander_get_index, false); - lnm_http_step_init(&step, lnm_http_loop_step_auth); - lnm_http_route_init_regex(&route, lnm_http_method_post, "^/s(l?)/([^/]*)$", 2, - step); - lnm_http_step_append(&step, step, lander_post_redirect); - lnm_http_step_append(&step, step, lnm_http_loop_step_body_to_buf); - lnm_http_step_append(&step, step, lander_post_redirect_body_to_attr); - lnm_http_loop_route_add(hl, route); + lnm_http_router_add(&route, router, lnm_http_method_get, "/:key"); + lnm_http_route_step_append(route, lander_get_entry, false); - lnm_http_step_init(&step, lnm_http_loop_step_auth); - lnm_http_route_init_regex(&route, lnm_http_method_post, "^/p(l?)/([^/]*)$", 2, - step); - lnm_http_step_append(&step, step, lander_post_paste); - lnm_http_step_append(&step, step, lander_stream_body_to_entry); - lnm_http_loop_route_add(hl, route); + lnm_http_router_add(&route, router, lnm_http_method_post, "/s/:key"); + lnm_http_route_step_append(route, lnm_http_loop_step_auth, false); + lnm_http_route_step_append(route, lander_post_redirect, false); + lnm_http_route_step_append(route, lnm_http_loop_step_body_to_buf, false); + lnm_http_route_step_append(route, lander_post_redirect_body_to_attr, false); - lnm_http_step_init(&step, lnm_http_loop_step_auth); - lnm_http_route_init_regex(&route, lnm_http_method_post, "^/f(l?)/([^/]*)$", 2, - step); - lnm_http_step_append(&step, step, lander_post_file); - lnm_http_step_append(&step, step, lander_stream_body_to_entry); - lnm_http_loop_route_add(hl, route); + lnm_http_router_add(&route, router, lnm_http_method_post, "/p/:key"); + lnm_http_route_step_append(route, lnm_http_loop_step_auth, false); + lnm_http_route_step_append(route, lander_post_paste, false); + lnm_http_route_step_append(route, lander_stream_body_to_entry, false); - lnm_http_step_init(&step, lnm_http_loop_step_auth); - lnm_http_route_init_regex(&route, lnm_http_method_delete, "^/([^/]+)$", 1, - step); - lnm_http_step_append(&step, step, lander_remove_entry); - lnm_http_loop_route_add(hl, route); + lnm_http_router_add(&route, router, lnm_http_method_post, "/f/:key"); + lnm_http_route_step_append(route, lnm_http_loop_step_auth, false); + lnm_http_route_step_append(route, lander_post_file, false); + lnm_http_route_step_append(route, lander_stream_body_to_entry, false); + + /* lnm_http_step_init(&step, lnm_http_loop_step_auth); */ + /* lnm_http_route_init_regex(&route, lnm_http_method_post, "^/s(l?)/([^/]*)$", 2, */ + /* step); */ + /* lnm_http_step_append(&step, step, lander_post_redirect); */ + /* lnm_http_step_append(&step, step, lnm_http_loop_step_body_to_buf); */ + /* lnm_http_step_append(&step, step, lander_post_redirect_body_to_attr); */ + /* lnm_http_loop_route_add(hl, route); */ + + /* lnm_http_step_init(&step, lnm_http_loop_step_auth); */ + /* lnm_http_route_init_regex(&route, lnm_http_method_post, "^/p(l?)/([^/]*)$", 2, */ + /* step); */ + /* lnm_http_step_append(&step, step, lander_post_paste); */ + /* lnm_http_step_append(&step, step, lander_stream_body_to_entry); */ + /* lnm_http_loop_route_add(hl, route); */ + + /* lnm_http_step_init(&step, lnm_http_loop_step_auth); */ + /* lnm_http_route_init_regex(&route, lnm_http_method_post, "^/f(l?)/([^/]*)$", 2, */ + /* step); */ + /* lnm_http_step_append(&step, step, lander_post_file); */ + /* lnm_http_step_append(&step, step, lander_stream_body_to_entry); */ + /* lnm_http_loop_route_add(hl, route); */ + + /* lnm_http_step_init(&step, lnm_http_loop_step_auth); */ + /* lnm_http_route_init_regex(&route, lnm_http_method_delete, "^/([^/]+)$", 1, */ + /* step); */ + /* lnm_http_step_append(&step, step, lander_remove_entry); */ + /* lnm_http_loop_route_add(hl, route); */ + + lnm_http_loop_router_set(hl, router); return hl; } @@ -104,7 +122,7 @@ int main() { lnm_linfo("main", "Store loaded containing %lu entries", lsm_store_size(c_gctx->store)); lnm_http_loop *hl = loop_init(c_gctx, api_key); - lnm_http_loop_run(hl, port, 1); + lnm_http_loop_run(hl, port, 1, 0); /* http_loop *hl = http_loop_init( */ /* lander_routes, sizeof(lander_routes) / sizeof(lander_routes[0]), From 94da7584aa655ab9b0c95620572e2257e1ec3930 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sun, 3 Mar 2024 09:41:56 +0100 Subject: [PATCH 42/45] refactor(lander): fully integrate new lnm framework router --- include/lander.h | 6 ++++ src/lander/lander_delete.c | 6 ++-- src/lander/lander_post.c | 39 ++++++++++++++++++----- src/main.c | 64 +++++++++++++++++++++----------------- 4 files changed, 77 insertions(+), 38 deletions(-) diff --git a/include/lander.h b/include/lander.h index ae2f714..7b418bc 100644 --- a/include/lander.h +++ b/include/lander.h @@ -43,8 +43,12 @@ lnm_http_step_err lander_get_entry(lnm_http_conn *conn); lnm_http_step_err lander_post_redirect(lnm_http_conn *conn); +lnm_http_step_err lander_post_redirect_secure(lnm_http_conn *conn); + lnm_http_step_err lander_post_paste(lnm_http_conn *conn); +lnm_http_step_err lander_post_paste_secure(lnm_http_conn *conn); + lnm_http_step_err lander_stream_body_to_entry(lnm_http_conn *conn); lnm_http_step_err lander_post_redirect_body_to_attr(lnm_http_conn *conn); @@ -53,6 +57,8 @@ lnm_http_step_err lander_remove_entry(lnm_http_conn *conn); lnm_http_step_err lander_post_file(lnm_http_conn *conn); +lnm_http_step_err lander_post_file_secure(lnm_http_conn *conn); + /** * Store the requested header as an attribute, if it's present. */ diff --git a/src/lander/lander_delete.c b/src/lander/lander_delete.c index f773ab5..f13326b 100644 --- a/src/lander/lander_delete.c +++ b/src/lander/lander_delete.c @@ -1,3 +1,4 @@ +#include "lnm/http/req.h" #include "lnm/loop.h" #include "lander.h" @@ -8,9 +9,8 @@ lnm_http_step_err lander_remove_entry(lnm_http_conn *conn) { lnm_http_loop_gctx *gctx = ctx->g; lander_gctx *c_gctx = gctx->c; - const char *key_s = - &ctx->req.buf.s[ctx->req.path.o + ctx->req.path.groups[1].rm_so]; - int key_len = ctx->req.path.groups[1].rm_eo - ctx->req.path.groups[1].rm_so; + const char *key_s; + size_t key_len = lnm_http_req_route_segment(&key_s, &ctx->req, "key"); lsm_str *key; lsm_str_init_copy_n(&key, (char *)key_s, key_len); diff --git a/src/lander/lander_post.c b/src/lander/lander_post.c index f5e877a..a5beaaf 100644 --- a/src/lander/lander_post.c +++ b/src/lander/lander_post.c @@ -1,7 +1,7 @@ #include -#include "lnm/loop.h" #include "lnm/http/req.h" +#include "lnm/loop.h" #include "lsm/store.h" #include "lander.h" @@ -66,11 +66,12 @@ bool lander_insert_entry(lnm_http_loop_ctx *ctx, bool secure) { return true; } -lnm_http_step_err lander_post_redirect(lnm_http_conn *conn) { +static lnm_http_step_err __lander_post_redirect(lnm_http_conn *conn, + bool secure) { lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; - if (!lander_insert_entry(ctx, false)) { + if (!lander_insert_entry(ctx, secure)) { return lnm_http_step_err_res; } @@ -80,6 +81,14 @@ lnm_http_step_err lander_post_redirect(lnm_http_conn *conn) { return lnm_http_step_err_done; } +lnm_http_step_err lander_post_redirect(lnm_http_conn *conn) { + return __lander_post_redirect(conn, false); +} + +lnm_http_step_err lander_post_redirect_secure(lnm_http_conn *conn) { + return __lander_post_redirect(conn, true); +} + lnm_http_step_err lander_post_redirect_body_to_attr(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; @@ -91,11 +100,11 @@ lnm_http_step_err lander_post_redirect_body_to_attr(lnm_http_conn *conn) { return lnm_http_step_err_done; } -lnm_http_step_err lander_post_paste(lnm_http_conn *conn) { +static lnm_http_step_err __lander_post_paste(lnm_http_conn *conn, bool secure) { lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; - if (!lander_insert_entry(ctx, false)) { + if (!lander_insert_entry(ctx, secure)) { return lnm_http_step_err_res; } @@ -106,11 +115,19 @@ lnm_http_step_err lander_post_paste(lnm_http_conn *conn) { return lnm_http_step_err_done; } -lnm_http_step_err lander_post_file(lnm_http_conn *conn) { +lnm_http_step_err lander_post_paste(lnm_http_conn *conn) { + return __lander_post_paste(conn, false); +} + +lnm_http_step_err lander_post_paste_secure(lnm_http_conn *conn) { + return __lander_post_paste(conn, true); +} + +static lnm_http_step_err __lander_post_file(lnm_http_conn *conn, bool secure) { lnm_http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; - if (!lander_insert_entry(ctx, false)) { + if (!lander_insert_entry(ctx, secure)) { return lnm_http_step_err_res; } @@ -122,3 +139,11 @@ lnm_http_step_err lander_post_file(lnm_http_conn *conn) { return lnm_http_step_err_done; } + +lnm_http_step_err lander_post_file(lnm_http_conn *conn) { + return __lander_post_file(conn, false); +} + +lnm_http_step_err lander_post_file_secure(lnm_http_conn *conn) { + return __lander_post_file(conn, true); +} diff --git a/src/main.c b/src/main.c index 28d8ef4..4ac8911 100644 --- a/src/main.c +++ b/src/main.c @@ -28,50 +28,58 @@ lnm_http_loop *loop_init(lander_gctx *gctx, const char *api_key) { lnm_http_router_add(&route, router, lnm_http_method_get, "/:key"); lnm_http_route_step_append(route, lander_get_entry, false); + lnm_http_router_add(&route, router, lnm_http_method_delete, "/:key"); + lnm_http_route_step_append(route, lnm_http_loop_step_auth, false); + lnm_http_route_step_append(route, lander_remove_entry, false); + + lnm_http_router_add(&route, router, lnm_http_method_post, "/s/"); + lnm_http_route_step_append(route, lnm_http_loop_step_auth, false); + lnm_http_route_step_append(route, lander_post_redirect, false); + lnm_http_route_step_append(route, lnm_http_loop_step_body_to_buf, false); + lnm_http_route_step_append(route, lander_post_redirect_body_to_attr, false); + + lnm_http_router_add(&route, router, lnm_http_method_post, "/sl/"); + lnm_http_route_step_append(route, lnm_http_loop_step_auth, false); + lnm_http_route_step_append(route, lander_post_redirect_secure, false); + lnm_http_route_step_append(route, lnm_http_loop_step_body_to_buf, false); + lnm_http_route_step_append(route, lander_post_redirect_body_to_attr, false); + lnm_http_router_add(&route, router, lnm_http_method_post, "/s/:key"); lnm_http_route_step_append(route, lnm_http_loop_step_auth, false); lnm_http_route_step_append(route, lander_post_redirect, false); lnm_http_route_step_append(route, lnm_http_loop_step_body_to_buf, false); lnm_http_route_step_append(route, lander_post_redirect_body_to_attr, false); + lnm_http_router_add(&route, router, lnm_http_method_post, "/p/"); + lnm_http_route_step_append(route, lnm_http_loop_step_auth, false); + lnm_http_route_step_append(route, lander_post_paste, false); + lnm_http_route_step_append(route, lander_stream_body_to_entry, false); + + lnm_http_router_add(&route, router, lnm_http_method_post, "/pl/"); + lnm_http_route_step_append(route, lnm_http_loop_step_auth, false); + lnm_http_route_step_append(route, lander_post_paste_secure, false); + lnm_http_route_step_append(route, lander_stream_body_to_entry, false); + lnm_http_router_add(&route, router, lnm_http_method_post, "/p/:key"); lnm_http_route_step_append(route, lnm_http_loop_step_auth, false); lnm_http_route_step_append(route, lander_post_paste, false); lnm_http_route_step_append(route, lander_stream_body_to_entry, false); + lnm_http_router_add(&route, router, lnm_http_method_post, "/f/"); + lnm_http_route_step_append(route, lnm_http_loop_step_auth, false); + lnm_http_route_step_append(route, lander_post_file, false); + lnm_http_route_step_append(route, lander_stream_body_to_entry, false); + + lnm_http_router_add(&route, router, lnm_http_method_post, "/fl/"); + lnm_http_route_step_append(route, lnm_http_loop_step_auth, false); + lnm_http_route_step_append(route, lander_post_file_secure, false); + lnm_http_route_step_append(route, lander_stream_body_to_entry, false); + lnm_http_router_add(&route, router, lnm_http_method_post, "/f/:key"); lnm_http_route_step_append(route, lnm_http_loop_step_auth, false); lnm_http_route_step_append(route, lander_post_file, false); lnm_http_route_step_append(route, lander_stream_body_to_entry, false); - /* lnm_http_step_init(&step, lnm_http_loop_step_auth); */ - /* lnm_http_route_init_regex(&route, lnm_http_method_post, "^/s(l?)/([^/]*)$", 2, */ - /* step); */ - /* lnm_http_step_append(&step, step, lander_post_redirect); */ - /* lnm_http_step_append(&step, step, lnm_http_loop_step_body_to_buf); */ - /* lnm_http_step_append(&step, step, lander_post_redirect_body_to_attr); */ - /* lnm_http_loop_route_add(hl, route); */ - - /* lnm_http_step_init(&step, lnm_http_loop_step_auth); */ - /* lnm_http_route_init_regex(&route, lnm_http_method_post, "^/p(l?)/([^/]*)$", 2, */ - /* step); */ - /* lnm_http_step_append(&step, step, lander_post_paste); */ - /* lnm_http_step_append(&step, step, lander_stream_body_to_entry); */ - /* lnm_http_loop_route_add(hl, route); */ - - /* lnm_http_step_init(&step, lnm_http_loop_step_auth); */ - /* lnm_http_route_init_regex(&route, lnm_http_method_post, "^/f(l?)/([^/]*)$", 2, */ - /* step); */ - /* lnm_http_step_append(&step, step, lander_post_file); */ - /* lnm_http_step_append(&step, step, lander_stream_body_to_entry); */ - /* lnm_http_loop_route_add(hl, route); */ - - /* lnm_http_step_init(&step, lnm_http_loop_step_auth); */ - /* lnm_http_route_init_regex(&route, lnm_http_method_delete, "^/([^/]+)$", 1, */ - /* step); */ - /* lnm_http_step_append(&step, step, lander_remove_entry); */ - /* lnm_http_loop_route_add(hl, route); */ - lnm_http_loop_router_set(hl, router); return hl; From ce0abcdc251332ebadeb736b0350c19cde60af3b Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 9 Mar 2024 13:29:10 +0100 Subject: [PATCH 43/45] chore: updated readme and makefile --- CHANGELOG.md | 8 +++++++- Makefile | 3 --- src/lander/lander_post.c | 6 +++--- src/main.c | 12 +----------- 4 files changed, 11 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1790cc2..43c89a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,7 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * LNM - Lander Network Module * Rewrite of the event loop & HTTP loop - * Fully independent library + * Fully independent library, maintained in its own repository * Numerous improvements * Streaming of headers * Allow custom & an arbitrary number of response headers @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Automatically support HEAD requests for all GET requests * Event loop uses `epoll` instead of `poll` * Configurable multithreading using `epoll` + * Trie-based router (no more RegEx) * Landerctl * `-c` flag to use custom config file (useful for testing) @@ -27,6 +28,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Removed Content-Disposition header for files +## Removed + +* Secure routes with a specified key (e.g. `/sl/:key`), as these were identical + to `/s/:key` routes + ## [0.2.0](https://git.rustybever.be/Chewing_Bever/lander/src/tag/0.2.0) ### Added diff --git a/Makefile b/Makefile index cedc8b8..9cd57fe 100644 --- a/Makefile +++ b/Makefile @@ -106,14 +106,12 @@ $(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 @@ -130,7 +128,6 @@ check: -j$(shell nproc) \ $(SRCS) make -C lsm check - make -C lnm check make -C landerctl check .PHONY: clean diff --git a/src/lander/lander_post.c b/src/lander/lander_post.c index a5beaaf..7befe03 100644 --- a/src/lander/lander_post.c +++ b/src/lander/lander_post.c @@ -34,10 +34,10 @@ bool lander_insert_entry(lnm_http_loop_ctx *ctx, bool secure) { if (key_len == 0) { // Generate a random key to insert key_len = secure ? 16 : 4; - char *key_s = malloc((key_len + 1) * sizeof(char)); + key_s = malloc((key_len + 1) * sizeof(char)); - randomize_key(key_s, key_len); - lsm_str_init(&key, key_s); + randomize_key((char *)key_s, key_len); + lsm_str_init(&key, (char *)key_s); } else { lsm_str_init_copy_n(&key, key_s, key_len); } diff --git a/src/main.c b/src/main.c index 4ac8911..f0f8e65 100644 --- a/src/main.c +++ b/src/main.c @@ -6,7 +6,6 @@ #include "lnm/log.h" #include "lander.h" -#include "log.h" const char *lander_server = "lander/" LANDER_VERSION; @@ -102,7 +101,7 @@ int main() { srand(time(NULL)); lnm_log_init_global(); - lnm_log_register_stdout(lnm_log_level_debug); + lnm_log_register_stdout(lnm_log_level_info); ENV(api_key, "LANDER_API_KEY"); ENV_OPT(port_str, "LANDER_PORT", "18080"); @@ -131,13 +130,4 @@ int main() { lsm_store_size(c_gctx->store)); lnm_http_loop *hl = loop_init(c_gctx, api_key); lnm_http_loop_run(hl, port, 1, 0); - - /* http_loop *hl = http_loop_init( */ - /* lander_routes, sizeof(lander_routes) / sizeof(lander_routes[0]), - * c_gctx, */ - /* lander_ctx_init, (void (*)(void *))lander_ctx_reset, */ - /* (void (*)(void *))lander_ctx_free); */ - /* http_loop_set_api_key(hl, api_key); */ - - /* http_loop_run(hl, port); */ } From 8aad8b2d0f672d4cf937d50d0f9c28a1749c7a83 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 9 Mar 2024 20:30:50 +0100 Subject: [PATCH 44/45] chore: update changelog and lnm commit --- CHANGELOG.md | 5 +---- lnm | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43c89a5..ebf809c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,12 +24,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Landerctl * `-c` flag to use custom config file (useful for testing) -## Changed - -* Removed Content-Disposition header for files - ## Removed +* Content-Disposition header for files * Secure routes with a specified key (e.g. `/sl/:key`), as these were identical to `/s/:key` routes diff --git a/lnm b/lnm index 6eab5d6..195eb9e 160000 --- a/lnm +++ b/lnm @@ -1 +1 @@ -Subproject commit 6eab5d616c2772a625b521e19d50020156c72ca2 +Subproject commit 195eb9eb4832f80ee8ab2372192196018362b252 From 675c9b78ff9a380531ba76a704171ab27f88fd81 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 9 Mar 2024 20:45:34 +0100 Subject: [PATCH 45/45] chore: update version to 0.2.1 --- CHANGELOG.md | 2 ++ config.mk | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ebf809c..aca5284 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased](https://git.rustybever.be/Chewing_Bever/lander/src/branch/dev) +## [0.2.1](https://git.rustybever.be/Chewing_Bever/lander/src/tag/0.2.1) + ### Added * LNM - Lander Network Module diff --git a/config.mk b/config.mk index 39269d8..d5230d7 100644 --- a/config.mk +++ b/config.mk @@ -1,4 +1,4 @@ -VERSION := 0.2.0 +VERSION := 0.2.1 BIN_FILENAME = lander