diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml index eaaac5a..b6063bb 100644 --- a/.woodpecker/build.yml +++ b/.woodpecker/build.yml @@ -39,7 +39,8 @@ pipeline: - minio_access_key - minio_secret_key when: - branch: dev + branch: + exclude: [ release/* ] event: push publish-rel: diff --git a/CHANGELOG.md b/CHANGELOG.md index ea9588b..82b5c0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,17 +7,6 @@ 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) -### Added - -* 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) ### Added diff --git a/Makefile b/Makefile index 6e13422..f2c448c 100644 --- a/Makefile +++ b/Makefile @@ -39,11 +39,7 @@ objs: $(OBJS) liblsm: $(MAKE) -C lsm -.PHONY: liblnm -liblnm: - $(MAKE) -C lnm - -$(BIN): liblsm liblnm $(OBJS) +$(BIN): liblsm $(OBJS) $(CC) -o $@ $(OBJS) $(_LDFLAGS) $(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c @@ -72,7 +68,7 @@ run: $(BIN) valgrind: $(BIN) LANDER_API_KEY=test \ LANDER_DATA_DIR=data \ - valgrind --track-origins=yes '$(BUILD_DIR)/$(BIN_FILENAME)' + valgrind '$(BUILD_DIR)/$(BIN_FILENAME)' .PHONY: test test: $(TARGETS_TEST) @@ -111,14 +107,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 @@ -135,14 +129,12 @@ 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 39269d8..da3e0b8 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 lnm/include -LIBS = lsm lnm -LIB_DIRS = ./lsm/build ./lnm/build +INC_DIRS = include $(THIRDPARTY_DIR)/include lsm/include +LIBS = m lsm +LIB_DIRS = ./lsm/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/http/res.h b/include/http/res.h index 729958f..6fda002 100644 --- a/include/http/res.h +++ b/include/http/res.h @@ -24,11 +24,8 @@ typedef struct http_response { 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_header headers[4]; + size_t header_count; } http_response; /** diff --git a/include/http/types.h b/include/http/types.h index fbbe160..cccf0a0 100644 --- a/include/http/types.h +++ b/include/http/types.h @@ -125,9 +125,7 @@ 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_content_disposition } http_header; typedef enum http_body_type { diff --git a/include/lander.h b/include/lander.h index c61b7b6..a30c32d 100644 --- a/include/lander.h +++ b/include/lander.h @@ -1,11 +1,8 @@ #ifndef LANDER #define LANDER -#include "lnm/common.h" -#include "lnm/http/loop.h" -#include "lsm/store.h" - #include "http_loop.h" +#include "lsm/store.h" extern http_route lander_routes[6]; extern const char lander_key_charset[]; @@ -34,40 +31,44 @@ typedef enum lander_entry_type : uint8_t { void *lander_gctx_init(); -lnm_err lander_ctx_init(void **c_ctx, void *gctx); +void *lander_ctx_init(); void lander_ctx_reset(lander_ctx *ctx); void lander_ctx_free(lander_ctx *ctx); -lnm_http_step_err lander_get_index(lnm_http_conn *conn); +bool lander_get_index(event_loop_conn *conn); -lnm_http_step_err lander_get_entry(lnm_http_conn *conn); +bool lander_get_entry(event_loop_conn *conn); -lnm_http_step_err lander_post_redirect(lnm_http_conn *conn); +bool lander_post_redirect(event_loop_conn *conn); -lnm_http_step_err lander_post_paste(lnm_http_conn *conn); +bool lander_post_paste(event_loop_conn *conn); -lnm_http_step_err lander_stream_body_to_entry(lnm_http_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); -lnm_http_step_err lander_post_redirect_body_to_attr(lnm_http_conn *conn); +bool lander_post_redirect_body_to_attr(event_loop_conn *conn); -lnm_http_step_err lander_remove_entry(lnm_http_conn *conn); +bool lander_remove_entry(event_loop_conn *conn); -lnm_http_step_err lander_post_file(lnm_http_conn *conn); +bool lander_post_file(event_loop_conn *conn); /** * Store the requested header as an attribute, if it's present. */ -void lander_header_to_attr(lnm_http_loop_ctx *ctx, const char *header, +void lander_header_to_attr(http_loop_ctx *ctx, const char *header, lander_attr_type attr_type); /** * Store the attribute's value as the provided header, if present. */ -void lander_attr_to_header(lnm_http_loop_ctx *ctx, lander_attr_type attr_type, - lnm_http_header header_type); +void lander_attr_to_header(http_loop_ctx *ctx, lander_attr_type attr_type, + http_header header_type); #endif diff --git a/lnm/Makefile b/lnm/Makefile deleted file mode 100644 index 7371c9f..0000000 --- a/lnm/Makefile +++ /dev/null @@ -1,127 +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' -SRCS_THIRDPARTY != find '$(THIRDPARTY_DIR)/src' -iname '*.c' - -OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) $(SRCS_THIRDPARTY:%=$(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) - -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 $@ - -$(BUILD_DIR)/$(THIRDPARTY_DIR)/%.c.o: $(THIRDPARTY_DIR)/%.c - mkdir -p $(dir $@) - $(CC) $(_CFLAGS) -c $< -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) - -.PHONY: fmt -fmt: - clang-format -i $(SRCS) $(SRCS_H) $(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) \ - $(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 fde7e0e..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_DIR = thirdparty - -PUB_INC_DIR = 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 -# 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 89622a3..0000000 --- a/lnm/include/lnm/http/consts.h +++ /dev/null @@ -1,97 +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; - -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 8a199e7..0000000 --- a/lnm/include/lnm/http/loop.h +++ /dev/null @@ -1,138 +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); - -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. - */ -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; - 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 e40d388..0000000 --- a/lnm/include/lnm/http/req.h +++ /dev/null @@ -1,90 +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 - -/** - * 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; - regmatch_t groups[LNM_HTTP_MAX_REGEX_GROUPS]; - } path; - struct { - const char *s; - size_t len; - } query; - struct { - struct phr_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. - * - * @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/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/loop.h b/lnm/include/lnm/loop.h deleted file mode 100644 index 8493170..0000000 --- a/lnm/include/lnm/loop.h +++ /dev/null @@ -1,57 +0,0 @@ -#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, - 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; - 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); - 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); - -#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/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_loop.c b/lnm/src/http/lnm_http_loop.c deleted file mode 100644 index 0b9b812..0000000 --- a/lnm/src/http/lnm_http_loop.c +++ /dev/null @@ -1,133 +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) { - 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_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 8c50ec1..0000000 --- a/lnm/src/http/lnm_http_loop_process.c +++ /dev/null @@ -1,341 +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/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_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.len; - 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 - // 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: - 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.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; - break; - } - - // Remember the previous match levels - 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 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); - - 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; - - 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]; - - // 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++; - - 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 = 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) { - const 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 == 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[http_loop_state] : conn->state; -} 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 853a33e..0000000 --- a/lnm/src/http/lnm_http_req.c +++ /dev/null @@ -1,96 +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; - - 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, 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; - } - - 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.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; -} - -void lnm_http_req_reset(lnm_http_req *req) { - if (req->body.owned) { - free(req->body.buf); - } - - 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/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_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 350eb9b..0000000 --- a/lnm/src/loop/lnm_loop.c +++ /dev/null @@ -1,196 +0,0 @@ -#include -#include -#include -#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), - 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) { - 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) { - // 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++; - - 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) { - 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; - } - - 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; - - if (poll_args == NULL) { - return lnm_err_failed_alloc; - } - - // First argument is listening socket - 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 + 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); - - 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]; - - 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_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 = l->listen_fd; - buf[0].events = POLLIN; - - free(poll_args); - poll_args = buf; - - poll_args_cap = l->conns.open + 1; - } - } - - 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 5d89191..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 { - 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:; - } -} diff --git a/lnm/thirdparty/include/picohttpparser.h b/lnm/thirdparty/include/picohttpparser.h deleted file mode 100644 index 07537cf..0000000 --- a/lnm/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/lnm/thirdparty/src/picohttpparser.c b/lnm/thirdparty/src/picohttpparser.c deleted file mode 100644 index 5e5783a..0000000 --- a/lnm/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 diff --git a/src/event_loop/event_loop.c b/src/event_loop/event_loop.c new file mode 100644 index 0000000..a01ca37 --- /dev/null +++ b/src/event_loop/event_loop.c @@ -0,0 +1,192 @@ +#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 new file mode 100644 index 0000000..4791d8f --- /dev/null +++ b/src/event_loop/event_loop_conn.c @@ -0,0 +1,13 @@ +#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 new file mode 100644 index 0000000..674c6ec --- /dev/null +++ b/src/event_loop/event_loop_io.c @@ -0,0 +1,98 @@ +#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/lnm/src/http/lnm_http_consts.c b/src/http/http_consts.c similarity index 63% rename from lnm/src/http/lnm_http_consts.c rename to src/http/http_consts.c index 834816a..8aa6f4b 100644 --- a/lnm/src/http/lnm_http_consts.c +++ b/src/http/http_consts.c @@ -1,12 +1,15 @@ -#include "lnm/http/consts.h" +#include -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]); +#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 *lnm_http_status_names[][32] = { +const char *http_status_names[][32] = { // 1xx { "Continue", // 100 @@ -90,11 +93,39 @@ const char *lnm_http_status_names[][32] = { }, }; -const char *lnm_http_header_names[] = { +const char *http_header_names[] = { "Connection", "Location", "Content-Type", - "Content-Disposition", - "Server", - "Content-Length" + "Content-Disposition" }; + +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 new file mode 100644 index 0000000..c3e8806 --- /dev/null +++ b/src/http/res.c @@ -0,0 +1,37 @@ +#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) { + res->headers[res->header_count].type = type; + res->headers[res->header_count].value = value; + res->headers[res->header_count].owned = owned; + + res->header_count++; +} + +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 new file mode 100644 index 0000000..d7f2647 --- /dev/null +++ b/src/http/types.c @@ -0,0 +1,33 @@ +#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 new file mode 100644 index 0000000..cb4289e --- /dev/null +++ b/src/http_loop/http_loop.c @@ -0,0 +1,101 @@ +#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 new file mode 100644 index 0000000..8d0db1e --- /dev/null +++ b/src/http_loop/http_loop_ctx.c @@ -0,0 +1,52 @@ +#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(); + + return ctx; +} + +void http_loop_ctx_free(http_loop_ctx *ctx) { + http_loop_ctx_reset(ctx); + ctx->g->custom_ctx_free(ctx->c); + + 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.header_count; i++) { + if (ctx->res.headers[i].owned) { + free((void *)ctx->res.headers[i].value); + } + } + + ctx->res.header_count = 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 new file mode 100644 index 0000000..a8cd841 --- /dev/null +++ b/src/http_loop/http_loop_req.c @@ -0,0 +1,149 @@ +#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 new file mode 100644 index 0000000..b29550f --- /dev/null +++ b/src/http_loop/http_loop_res.c @@ -0,0 +1,127 @@ +#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"; + +/* + * 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; + } + + 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); + + // 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); + } + + // 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); + + 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); + } + + 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 new file mode 100644 index 0000000..99c5cce --- /dev/null +++ b/src/http_loop/http_loop_steps.c @@ -0,0 +1,179 @@ +#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 d6bf3f5..5d1c1fe 100644 --- a/src/lander/lander.c +++ b/src/lander/lander.c @@ -1,29 +1,66 @@ #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"; +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) { - 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_init() { return calloc(1, sizeof(lander_ctx)); } void lander_ctx_reset(lander_ctx *ctx) { if (ctx->entry != NULL) { @@ -35,30 +72,36 @@ void lander_ctx_reset(lander_ctx *ctx) { void lander_ctx_free(lander_ctx *ctx) { free(ctx); } -void lander_header_to_attr(lnm_http_loop_ctx *ctx, const char *header_name, +void lander_header_to_attr(http_loop_ctx *ctx, const char *header_name, lander_attr_type attr_type) { lander_ctx *c_ctx = ctx->c; - const char *header_value; - size_t header_value_len; + for (size_t i = 0; i < ctx->req.num_headers; i++) { + const struct phr_header *header = &ctx->req.headers[i]; - 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); + 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); - lsm_entry_attr_insert(c_ctx->entry, attr_type, value); + lsm_entry_attr_insert(c_ctx->entry, attr_type, value); + } + + return; + } } } -void lander_attr_to_header(lnm_http_loop_ctx *ctx, lander_attr_type attr_type, - lnm_http_header header_type) { +void lander_attr_to_header(http_loop_ctx *ctx, lander_attr_type attr_type, + 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) { - lnm_http_res_add_header_len(&ctx->res, header_type, - (char *)lsm_str_ptr(value), lsm_str_len(value), - false); + 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); } } diff --git a/src/lander/lander_delete.c b/src/lander/lander_delete.c index 349fac6..e91b6c9 100644 --- a/src/lander/lander_delete.c +++ b/src/lander/lander_delete.c @@ -1,30 +1,29 @@ -#include "lnm/loop.h" - #include "lander.h" -lnm_http_step_err lander_remove_entry(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; +bool lander_remove_entry(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; - lnm_http_loop_gctx *gctx = ctx->g; + 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]; - int key_len = ctx->req.path.groups[1].rm_eo - ctx->req.path.groups[1].rm_so; + 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; 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 = lnm_http_status_not_found; - break; + ctx->res.status = http_not_found; + return true; default: - ctx->res.status = lnm_http_status_internal_server_error; - break; + ctx->res.status = http_internal_server_error; + return true; } - return lnm_http_step_err_done; + lsm_entry_remove(c_ctx->entry); + + return true; } diff --git a/src/lander/lander_get.c b/src/lander/lander_get.c index 1bf4da0..102c631 100644 --- a/src/lander/lander_get.c +++ b/src/lander/lander_get.c @@ -1,9 +1,5 @@ #include -#include "lnm/http/consts.h" -#include "lnm/http/loop.h" -#include "lnm/loop.h" - #include "event_loop.h" #include "http/res.h" #include "http/types.h" @@ -21,19 +17,18 @@ static const char index_page[] = " \n" "\n"; -lnm_http_step_err lander_get_index(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; +bool lander_get_index(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; - 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); - return lnm_http_step_err_done; + conn->state = event_loop_conn_state_res; + return true; } -lnm_http_step_err lander_get_redirect(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; +void lander_get_redirect(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; // For redirects, the URL is stored as an in-memory attribute @@ -44,55 +39,43 @@ lnm_http_step_err lander_get_redirect(lnm_http_conn *conn) { lsm_error_ok) { error("Entry of type redirect detected without URL attribute"); - ctx->res.status = lnm_http_status_internal_server_error; + ctx->res.status = http_internal_server_error; lsm_entry_close(c_ctx->entry); c_ctx->entry = NULL; - return lnm_http_step_err_res; + return; } - 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); + char *buf = malloc(lsm_str_len(url_attr_val) + 1); + memcpy(buf, lsm_str_ptr(url_attr_val), lsm_str_len(url_attr_val)); - ctx->res.status = lnm_http_status_moved_permanently; + buf[lsm_str_len(url_attr_val)] = '\0'; - return lnm_http_step_err_done; + 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; } -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; +void lander_get_paste(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; - lsm_entry_data_read(written, buf, c_ctx->entry, len); - - return lnm_err_ok; + ctx->res.body.expected_len = lsm_entry_data_len(c_ctx->entry); + http_res_set_mime_type(&ctx->res, http_mime_txt); } -lnm_http_step_err lander_get_paste(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; +void lander_get_file(event_loop_conn *conn) { + 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); - - 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)); + ctx->res.body.expected_len = lsm_entry_data_len(c_ctx->entry); lander_attr_to_header(ctx, lander_attr_type_content_type, - lnm_http_header_content_type); + http_header_content_type); lsm_str *value; char *buf; @@ -101,26 +84,23 @@ lnm_http_step_err lander_get_file(lnm_http_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"); } - lnm_http_res_add_header(&ctx->res, lnm_http_header_content_disposition, buf, - true); - - return lnm_http_step_err_done; + http_res_add_header(&ctx->res, http_header_content_disposition, buf, true); } -lnm_http_step_err lander_get_entry(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; +bool lander_get_entry(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; - lnm_http_loop_gctx *gctx = ctx->g; + 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]; - int key_len = ctx->req.path.groups[1].rm_eo - ctx->req.path.groups[1].rm_so; + 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; lsm_str *key; lsm_str_init_copy_n(&key, (char *)key_s, key_len); @@ -129,32 +109,32 @@ lnm_http_step_err lander_get_entry(lnm_http_conn *conn) { case lsm_error_ok: break; case lsm_error_not_found: - ctx->res.status = lnm_http_status_not_found; - return lnm_http_step_err_res; + ctx->res.status = http_not_found; + conn->state = event_loop_conn_state_res; + return true; default: - ctx->res.status = lnm_http_status_internal_server_error; - return lnm_http_step_err_res; + ctx->res.status = http_internal_server_error; + conn->state = event_loop_conn_state_res; + return true; } 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: - res = lander_get_redirect(conn); + lander_get_redirect(conn); break; case lander_entry_type_paste: - res = lander_get_paste(conn); + lander_get_paste(conn); break; case lander_entry_type_file: - res = lander_get_file(conn); + lander_get_file(conn); break; } - return res; + return true; } 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 882cb04..9711d03 100644 --- a/src/lander/lander_post.c +++ b/src/lander/lander_post.c @@ -1,7 +1,6 @@ #include "http/res.h" #include "http/types.h" #include "lander.h" -#include "lnm/loop.h" #include "log.h" #include "lsm/store.h" @@ -20,26 +19,26 @@ static void randomize_key(char *key, int len) { * * @return true on success, false otherwise */ -bool lander_insert_entry(lnm_http_loop_ctx *ctx) { - lnm_http_loop_gctx *gctx = ctx->g; +bool lander_insert_entry(http_loop_ctx *ctx) { + 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.path.groups[2].rm_eo == ctx->req.path.groups[2].rm_so) { + if (ctx->req.regex_groups[2].rm_eo == ctx->req.regex_groups[2].rm_so) { // Generate a random key to insert bool secure = - (ctx->req.path.groups[1].rm_eo - ctx->req.path.groups[1].rm_so) == 1; + (ctx->req.regex_groups[1].rm_eo - ctx->req.regex_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.s[ctx->req.path.groups[2].rm_so]; - key_len = ctx->req.path.groups[2].rm_eo - ctx->req.path.groups[2].rm_so; + 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; lsm_str_init_copy_n(&key, key_s, key_len); } @@ -47,12 +46,12 @@ bool lander_insert_entry(lnm_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 = lnm_http_status_conflict; + ctx->res.status = http_conflict; return false; case lsm_error_ok: break; default: - ctx->res.status = lnm_http_status_internal_server_error; + ctx->res.status = http_internal_server_error; return false; } @@ -62,58 +61,61 @@ bool lander_insert_entry(lnm_http_loop_ctx *ctx) { buf[0] = '/'; buf[key_len + 1] = '\0'; - lnm_http_res_add_header(&ctx->res, lnm_http_header_location, buf, true); - ctx->res.status = lnm_http_status_created; + http_res_add_header(&ctx->res, http_header_location, buf, true); + ctx->res.status = http_created; return true; } -lnm_http_step_err lander_post_redirect(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; +bool lander_post_redirect(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; if (!lander_insert_entry(ctx)) { - return lnm_http_step_err_res; + conn->state = event_loop_conn_state_res; + return true; } lsm_entry_attr_insert_uint8_t(c_ctx->entry, lander_attr_type_entry_type, lander_entry_type_redirect); - return lnm_http_step_err_done; + return true; } -lnm_http_step_err lander_post_redirect_body_to_attr(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; +bool lander_post_redirect_body_to_attr(event_loop_conn *conn) { + 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 lnm_http_step_err_done; + return true; } -lnm_http_step_err lander_post_paste(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; +bool lander_post_paste(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; if (!lander_insert_entry(ctx)) { - return lnm_http_step_err_res; + conn->state = event_loop_conn_state_res; + return true; } 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 lnm_http_step_err_done; + return true; } -lnm_http_step_err lander_post_file(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; +bool lander_post_file(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; if (!lander_insert_entry(ctx)) { - return lnm_http_step_err_res; + conn->state = event_loop_conn_state_res; + return true; } lsm_entry_attr_insert_uint8_t(c_ctx->entry, lander_attr_type_entry_type, @@ -122,5 +124,5 @@ lnm_http_step_err lander_post_file(lnm_http_conn *conn) { lander_attr_type_content_type); lander_header_to_attr(ctx, "X-Lander-Filename", lander_attr_type_file_name); - return lnm_http_step_err_done; + return true; } diff --git a/src/lander/lander_steps.c b/src/lander/lander_steps.c index 6021dbd..7804df5 100644 --- a/src/lander/lander_steps.c +++ b/src/lander/lander_steps.c @@ -1,26 +1,22 @@ #include #include "lander.h" -#include "lnm/http/loop.h" -#include "lnm/loop.h" -lnm_http_step_err lander_stream_body_to_entry(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; +bool lander_stream_body_to_entry(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; lander_ctx *c_ctx = ctx->c; uint64_t to_append = - MIN(conn->r.size - conn->r.read, + MIN(conn->rbuf_size - conn->rbuf_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); + lsm_str_init_copy_n(&data, (char *)&conn->rbuf[conn->rbuf_read], to_append); lsm_entry_data_append(c_ctx->entry, data); - conn->r.read += to_append; + conn->rbuf_read += to_append; lsm_str_free(data); - return lsm_entry_data_len(c_ctx->entry) == ctx->req.body.expected_len - ? lnm_http_step_err_done - : lnm_http_step_err_io_needed; + return lsm_entry_data_len(c_ctx->entry) == ctx->req.body.expected_len; } diff --git a/src/main.c b/src/main.c index 04bff84..2f52fc1 100644 --- a/src/main.c +++ b/src/main.c @@ -2,59 +2,9 @@ #include #include -#include "lnm/http/loop.h" - #include "lander.h" #include "log.h" -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); - 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, 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); - - return hl; -} - #define ENV(var, env_var) \ const char *var = getenv(env_var); \ if (var == NULL) { \ @@ -94,15 +44,12 @@ int main() { } info("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); - /* 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); }