chore: remove lnm directory
							parent
							
								
									dc0a3a7349
								
							
						
					
					
						commit
						7b195c75b0
					
				
							
								
								
									
										128
									
								
								lnm/Makefile
								
								
								
								
							
							
						
						
									
										128
									
								
								lnm/Makefile
								
								
								
								
							|  | @ -1,128 +0,0 @@ | |||
| # https://spin.atomicobject.com/2016/08/26/makefile-c-projects/ was a great
 | ||||
| # base for this Makefile
 | ||||
| 
 | ||||
| -include config.mk | ||||
| 
 | ||||
| LIB := $(BUILD_DIR)/$(LIB_FILENAME) | ||||
| 
 | ||||
| SRCS != find '$(SRC_DIR)' -iname '*.c' | ||||
| SRCS_H != find include -iname '*.h' | ||||
| SRCS_H_INTERNAL != find $(SRC_DIR) -iname '*.h' | ||||
| SRCS_TEST != find '$(TEST_DIR)' -iname '*.c' | ||||
| 
 | ||||
| OBJS := $(SRCS:%=$(BUILD_DIR)/%.o) | ||||
| OBJS_TEST := $(SRCS_TEST:%=$(BUILD_DIR)/%.o) | ||||
| DEPS := $(SRCS:%=$(BUILD_DIR)/%.d) $(SRCS_TEST:%=$(BUILD_DIR)/%.d) | ||||
| 
 | ||||
| BINS_TEST := $(OBJS_TEST:%.c.o=%) | ||||
| TARGETS_TEST := $(BINS_TEST:%=test-%) | ||||
| TARGETS_MEM_TEST := $(BINS_TEST:%=test-mem-%) | ||||
| 
 | ||||
| _CFLAGS := $(addprefix -I,$(INC_DIRS)) $(CFLAGS) -Wall -Wextra | ||||
| 
 | ||||
| .PHONY: all | ||||
| all: lib | ||||
| 
 | ||||
| 
 | ||||
| # =====COMPILATION=====
 | ||||
| # Utility used by the CI to lint
 | ||||
| .PHONY: objs | ||||
| objs: $(OBJS) | ||||
| 
 | ||||
| .PHONY: lib | ||||
| lib: $(LIB) | ||||
| $(LIB): $(OBJS) | ||||
| 	ar -rcs $@ $(OBJS) | ||||
| 
 | ||||
| $(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c | ||||
| 	mkdir -p $(dir $@) | ||||
| 	$(CC) -c $(_CFLAGS) $< -o $@ | ||||
| 
 | ||||
| # =====TESTING=====
 | ||||
| .PHONY: test | ||||
| test: $(TARGETS_TEST) | ||||
| 
 | ||||
| .PHONY: test-mem | ||||
| test-mem: $(TARGETS_MEM_TEST) | ||||
| 
 | ||||
| .PHONY: $(TARGETS_TEST) | ||||
| $(TARGETS_TEST): test-%: % | ||||
| 	./$^ | ||||
| 
 | ||||
| .PHONY: $(TARGETS_MEM_TEST) | ||||
| $(TARGETS_MEM_TEST): test-mem-%: % | ||||
| 	valgrind --tool=memcheck --error-exitcode=1 --track-origins=yes --leak-check=full ./$^ | ||||
| 
 | ||||
| .PHONY: build-test | ||||
| build-test: $(BINS_TEST) | ||||
| 
 | ||||
| $(BINS_TEST): %: %.c.o $(LIB) | ||||
| 	$(CC) \
 | ||||
| 		$^ -o $@ | ||||
| 
 | ||||
| # Along with the include directory, each test includes $(TEST_DIR) (which
 | ||||
| # contains the acutest.h header file), and the src directory of the module it's
 | ||||
| # testing. This allows tests to access internal methods, which aren't publicly
 | ||||
| # exposed.
 | ||||
| $(BUILD_DIR)/$(TEST_DIR)/%.c.o: $(TEST_DIR)/%.c | ||||
| 	mkdir -p $(dir $@) | ||||
| 	$(CC) $(_CFLAGS) -I$(TEST_DIR) \
 | ||||
| 		-I$(dir $(@:$(BUILD_DIR)/$(TEST_DIR)/%=$(SRC_DIR)/%)) \
 | ||||
| 		-c $< -o $@ | ||||
| 
 | ||||
| # =====EXAMPLES=====
 | ||||
| .PHONY: build-example | ||||
| build-example: $(BINS_EXAMPLE) | ||||
| 
 | ||||
| $(BINS_EXAMPLE): %: %.c.o $(LIB) | ||||
| 	$(CC) \
 | ||||
| 		$^ -o $@ | ||||
| 
 | ||||
| # Example binaries link the resulting library
 | ||||
| $(BUILD_DIR)/$(EXAMPLE_DIR)/%.c.o: $(EXAMPLE_DIR)/%.c | ||||
| 	mkdir -p $(dir $@) | ||||
| 	$(CC) $(_CFLAGS) -I$(PUB_INC_DIR) -c $< -o $@ | ||||
| 
 | ||||
| # =====MAINTENANCE=====
 | ||||
| .PHONY: lint | ||||
| lint: | ||||
| 	clang-format -n --Werror \
 | ||||
| 		$(filter-out $(THIRDPARTY),$(SRCS)) \
 | ||||
| 		$(filter-out $(THIRDPARTY),$(SRCS_H)) \
 | ||||
| 		$(filter-out $(THIRDPARTY),$(SRCS_H_INTERNAL)) | ||||
| 
 | ||||
| .PHONY: fmt | ||||
| fmt: | ||||
| 	clang-format -i \
 | ||||
| 		$(filter-out $(THIRDPARTY),$(SRCS)) \
 | ||||
| 		$(filter-out $(THIRDPARTY),$(SRCS_H)) \
 | ||||
| 		$(filter-out $(THIRDPARTY),$(SRCS_H_INTERNAL)) | ||||
| 
 | ||||
| .PHONY: check | ||||
| check: | ||||
| 	mkdir -p $(BUILD_DIR)/cppcheck | ||||
| 	cppcheck \
 | ||||
| 		$(addprefix -I,$(INC_DIRS)) \
 | ||||
| 		--cppcheck-build-dir=$(BUILD_DIR)/cppcheck \
 | ||||
| 		--error-exitcode=1 \
 | ||||
| 		--enable=warning,style \
 | ||||
| 		--inline-suppr \
 | ||||
| 		--check-level=exhaustive \
 | ||||
| 		--quiet \
 | ||||
| 		-j$(shell nproc) \
 | ||||
| 		$(filter-out $(THIRDPARTY),$(SRCS)) | ||||
| 
 | ||||
| .PHONY: clean | ||||
| clean: | ||||
| 	rm -rf '$(BUILD_DIR)' | ||||
| 
 | ||||
| 
 | ||||
| .PHONY: bear | ||||
| bear: clean | ||||
| 	bear -- make | ||||
| 	bear --append -- make build-test | ||||
| 	bear --append -- make build-example | ||||
| 
 | ||||
| 
 | ||||
| # Make make aware of the .d files
 | ||||
| -include $(DEPS) | ||||
|  | @ -1,16 +0,0 @@ | |||
| LIB_FILENAME = liblnm.a | ||||
| 
 | ||||
| BUILD_DIR = build | ||||
| SRC_DIR = src | ||||
| TEST_DIR = test | ||||
| THIRDPARTY = src/picohttpparser.c include/picohttpparser.h | ||||
| 
 | ||||
| PUB_INC_DIR = include | ||||
| INC_DIRS = $(PUB_INC_DIR) src/_include | ||||
| 
 | ||||
| # -MMD: generate a .d file for every source file. This file can be imported by
 | ||||
| #  make and makes make aware that a header file has been changed, ensuring an
 | ||||
| #  object file is also recompiled if only a header is changed.
 | ||||
| # -MP: generate a dummy target for every header file (according to the  docs it
 | ||||
| #  prevents some errors when removing header files)
 | ||||
| CFLAGS ?= -MMD -MP -g | ||||
|  | @ -1,89 +0,0 @@ | |||
| #ifndef LNM_COMMON | ||||
| #define LNM_COMMON | ||||
| 
 | ||||
| #include <stdbool.h> | ||||
| #include <stdint.h> | ||||
| #include <stdlib.h> | ||||
| 
 | ||||
| #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 | ||||
|  | @ -1,98 +0,0 @@ | |||
| #ifndef LNM_HTTP_CONSTS | ||||
| #define LNM_HTTP_CONSTS | ||||
| 
 | ||||
| #include <stdlib.h> | ||||
| 
 | ||||
| extern const char *lnm_http_method_names[]; | ||||
| extern const size_t lnm_http_method_names_len; | ||||
| 
 | ||||
| typedef enum lnm_http_method { | ||||
|   lnm_http_method_get = 0, | ||||
|   lnm_http_method_post, | ||||
|   lnm_http_method_put, | ||||
|   lnm_http_method_patch, | ||||
|   lnm_http_method_delete, | ||||
|   lnm_http_method_head, | ||||
| } lnm_http_method; | ||||
| 
 | ||||
| extern const char *lnm_http_status_names[][32]; | ||||
| 
 | ||||
| typedef enum lnm_http_status { | ||||
|   // 1xx
 | ||||
|   lnm_http_status_continue = 100, | ||||
|   lnm_http_status_switching_protocols = 101, | ||||
|   lnm_http_status_processing = 102, | ||||
|   lnm_http_status_early_hints = 103, | ||||
|   // 2xx
 | ||||
|   lnm_http_status_ok = 200, | ||||
|   lnm_http_status_created = 201, | ||||
|   lnm_http_status_accepted = 202, | ||||
|   lnm_http_status_non_authoritative_information = 203, | ||||
|   lnm_http_status_no_content = 204, | ||||
|   lnm_http_status_reset_content = 205, | ||||
|   lnm_http_status_partial_content = 206, | ||||
|   lnm_http_status_multi_status = 207, | ||||
|   lnm_http_status_already_reported = 208, | ||||
|   // 3xx
 | ||||
|   lnm_http_status_multiple_choices = 300, | ||||
|   lnm_http_status_moved_permanently = 301, | ||||
|   lnm_http_status_found = 302, | ||||
|   lnm_http_status_see_other = 303, | ||||
|   lnm_http_status_not_modified = 304, | ||||
|   lnm_http_status_temporary_redirect = 307, | ||||
|   lnm_http_status_permanent_redirect = 308, | ||||
|   // 4xx
 | ||||
|   lnm_http_status_bad_request = 400, | ||||
|   lnm_http_status_unauthorized = 401, | ||||
|   lnm_http_status_payment_required = 402, | ||||
|   lnm_http_status_forbidden = 403, | ||||
|   lnm_http_status_not_found = 404, | ||||
|   lnm_http_status_method_not_allowed = 405, | ||||
|   lnm_http_status_not_acceptable = 406, | ||||
|   lnm_http_status_proxy_authentication_required = 407, | ||||
|   lnm_http_status_request_timeout = 408, | ||||
|   lnm_http_status_conflict = 409, | ||||
|   lnm_http_status_gone = 410, | ||||
|   lnm_http_status_length_required = 411, | ||||
|   lnm_http_status_precondition_failed = 412, | ||||
|   lnm_http_status_content_too_large = 413, | ||||
|   lnm_http_status_uri_too_long = 414, | ||||
|   lnm_http_status_unsupported_media_type = 415, | ||||
|   lnm_http_status_range_not_satisfiable = 416, | ||||
|   lnm_http_status_expection_failed = 417, | ||||
|   lnm_http_status_im_a_teapot = 418, | ||||
|   lnm_http_status_misdirected_request = 421, | ||||
|   lnm_http_status_unprocessable_content = 422, | ||||
|   lnm_http_status_locked = 423, | ||||
|   lnm_http_status_failed_dependency = 424, | ||||
|   lnm_http_status_too_early = 425, | ||||
|   lnm_http_status_upgrade_required = 426, | ||||
|   lnm_http_status_precondition_required = 428, | ||||
|   lnm_http_status_too_many_requests = 429, | ||||
|   lnm_http_status_request_header_fields_too_large = 431, | ||||
|   // 5xx
 | ||||
|   lnm_http_status_internal_server_error = 500, | ||||
|   lnm_http_status_method_not_implemented = 501, | ||||
|   lnm_http_status_bad_gateway = 502, | ||||
|   lnm_http_status_service_unavailable = 503, | ||||
|   lnm_http_status_gateway_timeout = 504, | ||||
|   lnm_http_status_http_status_version_not_supported = 505, | ||||
|   lnm_http_status_variant_also_negotiates = 506, | ||||
|   lnm_http_status_insufficient_storage = 507, | ||||
|   lnm_http_status_loop_detected = 508, | ||||
|   lnm_http_status_not_extended = 510, | ||||
|   lnm_http_status_network_authentication_required = 511 | ||||
| } lnm_http_status; | ||||
| 
 | ||||
| extern const char *lnm_http_header_names[]; | ||||
| 
 | ||||
| typedef enum lnm_http_header { | ||||
|   lnm_http_header_connection = 0, | ||||
|   lnm_http_header_location, | ||||
|   lnm_http_header_content_type, | ||||
|   lnm_http_header_content_disposition, | ||||
|   lnm_http_header_server, | ||||
|   lnm_http_header_content_length | ||||
| } lnm_http_header; | ||||
| 
 | ||||
| #endif | ||||
|  | @ -1,141 +0,0 @@ | |||
| #ifndef LNM_HTTP_LOOP | ||||
| #define LNM_HTTP_LOOP | ||||
| 
 | ||||
| #include <stdlib.h> | ||||
| 
 | ||||
| #include "lnm/common.h" | ||||
| #include "lnm/http/req.h" | ||||
| #include "lnm/http/res.h" | ||||
| 
 | ||||
| typedef enum lnm_http_step_err { | ||||
|   lnm_http_step_err_done = 0, | ||||
|   lnm_http_step_err_io_needed, | ||||
|   lnm_http_step_err_close, | ||||
|   lnm_http_step_err_res, | ||||
| } lnm_http_step_err; | ||||
| 
 | ||||
| typedef lnm_http_step_err (*lnm_http_step_fn)(lnm_http_conn *conn); | ||||
| 
 | ||||
| typedef lnm_err (*lnm_http_ctx_init_fn)(void **c_ctx, void *gctx); | ||||
| 
 | ||||
| typedef void (*lnm_http_ctx_reset_fn)(void *c_ctx); | ||||
| 
 | ||||
| typedef void (*lnm_http_ctx_free_fn)(void *c_ctx); | ||||
| 
 | ||||
| /**
 | ||||
|  * Initialize a new `lnm_http_loop`. | ||||
|  * | ||||
|  * @param out where to store pointer to new `lnm_http_loop` | ||||
|  */ | ||||
| lnm_err lnm_http_loop_init(lnm_http_loop **out, void *c_gctx, | ||||
|                            lnm_http_ctx_init_fn ctx_init, | ||||
|                            lnm_http_ctx_reset_fn ctx_reset, | ||||
|                            lnm_http_ctx_free_fn ctx_free); | ||||
| 
 | ||||
| /**
 | ||||
|  * Initialize a new step. | ||||
|  * | ||||
|  * @param out where to store pointer to new `lnm_http_step` | ||||
|  * @param fn step function | ||||
|  */ | ||||
| lnm_err lnm_http_step_init(lnm_http_step **out, lnm_http_step_fn fn); | ||||
| 
 | ||||
| /**
 | ||||
|  * Append the given step fn to the step. | ||||
|  * | ||||
|  * @param out where to store pointer to new `lnm_http_step` | ||||
|  * @param step step to append new step to | ||||
|  * @param fn step function | ||||
|  */ | ||||
| lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step *step, | ||||
|                              lnm_http_step_fn fn); | ||||
| 
 | ||||
| /**
 | ||||
|  * Initialize a new route of type literal. | ||||
|  * | ||||
|  * @param out where to store pointer to new `lnm_http_route` | ||||
|  * @param path literal path to match | ||||
|  * @param step step to process request with | ||||
|  */ | ||||
| lnm_err lnm_http_route_init_literal(lnm_http_route **out, | ||||
|                                     lnm_http_method method, const char *path, | ||||
|                                     lnm_http_step *step); | ||||
| 
 | ||||
| /**
 | ||||
|  * Initialize a new route of type regex. | ||||
|  * | ||||
|  * @param out where to store pointer to new `lnm_http_route` | ||||
|  * @param pattern regex pattern | ||||
|  * @param regex_group_count how many regex groups are contained in the pattern | ||||
|  * @param step step to process request with | ||||
|  */ | ||||
| lnm_err lnm_http_route_init_regex(lnm_http_route **out, lnm_http_method method, | ||||
|                                   const char *pattern, int regex_group_count, | ||||
|                                   lnm_http_step *step); | ||||
| 
 | ||||
| /**
 | ||||
|  * Add a new route to the HTTP route. | ||||
|  * | ||||
|  * @param hl HTTP loop to modify | ||||
|  * @param route route to add | ||||
|  */ | ||||
| lnm_err lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route); | ||||
| 
 | ||||
| lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port, int thread_count); | ||||
| 
 | ||||
| void lnm_http_loop_set_api_key(lnm_http_loop *hl, const char *api_key); | ||||
| 
 | ||||
| void lnm_http_loop_set_server(lnm_http_loop *hl, const char *value); | ||||
| 
 | ||||
| /**
 | ||||
|  * Represents what state an HTTP loop request is currently in. | ||||
|  */ | ||||
| typedef enum lnm_http_loop_state { | ||||
|   // Parse the HTTP request
 | ||||
|   lnm_http_loop_state_parse_req = 0, | ||||
|   // Route the request
 | ||||
|   lnm_http_loop_state_route, | ||||
|   // Parse specific headers (e.g. Content-Length)
 | ||||
|   lnm_http_loop_state_parse_headers, | ||||
|   // Execute the various steps defined for the route
 | ||||
|   lnm_http_loop_state_steps, | ||||
|   // Add certain automatically added headers
 | ||||
|   lnm_http_loop_state_add_headers, | ||||
|   // Write the response status line
 | ||||
|   lnm_http_loop_state_write_status_line, | ||||
|   // Write the various response headers
 | ||||
|   lnm_http_loop_state_write_headers, | ||||
|   // Write the request body
 | ||||
|   lnm_http_loop_state_write_body, | ||||
|   // Clean up the request and reset the state for a next request
 | ||||
|   lnm_http_loop_state_finish, | ||||
| } lnm_http_loop_state; | ||||
| 
 | ||||
| typedef struct lnm_http_loop_gctx { | ||||
|   struct { | ||||
|     lnm_http_route **arr; | ||||
|     size_t len; | ||||
|   } routes; | ||||
|   lnm_http_ctx_init_fn ctx_init; | ||||
|   lnm_http_ctx_reset_fn ctx_reset; | ||||
|   lnm_http_ctx_free_fn ctx_free; | ||||
|   const char *api_key; | ||||
|   const char *server; | ||||
|   void *c; | ||||
| } lnm_http_loop_gctx; | ||||
| 
 | ||||
| typedef struct lnm_http_loop_ctx { | ||||
|   lnm_http_loop_state state; | ||||
|   lnm_http_req req; | ||||
|   lnm_http_res res; | ||||
|   lnm_http_route *route; | ||||
|   lnm_http_step *cur_step; | ||||
|   lnm_http_loop_gctx *g; | ||||
|   void *c; | ||||
| } lnm_http_loop_ctx; | ||||
| 
 | ||||
| lnm_http_step_err lnm_http_loop_step_body_to_buf(lnm_http_conn *conn); | ||||
| 
 | ||||
| lnm_http_step_err lnm_http_loop_step_auth(lnm_http_conn *conn); | ||||
| 
 | ||||
| #endif | ||||
|  | @ -1,111 +0,0 @@ | |||
| #ifndef LNM_HTTP_REQ | ||||
| #define LNM_HTTP_REQ | ||||
| 
 | ||||
| #include <regex.h> | ||||
| #include <stdint.h> | ||||
| #include <stdlib.h> | ||||
| 
 | ||||
| #include "picohttpparser.h" | ||||
| 
 | ||||
| #include "lnm/common.h" | ||||
| #include "lnm/http/consts.h" | ||||
| 
 | ||||
| #define LNM_HTTP_MAX_REQ_HEADERS 32 | ||||
| #define LNM_HTTP_MAX_REGEX_GROUPS 4 | ||||
| 
 | ||||
| typedef struct lnm_http_req_header { | ||||
|   struct { | ||||
|     size_t o; | ||||
|     size_t len; | ||||
|   } name; | ||||
|   struct { | ||||
|     size_t o; | ||||
|     size_t len; | ||||
|   } value; | ||||
| } lnm_http_req_header; | ||||
| 
 | ||||
| /**
 | ||||
|  * Represents the parsed HTTP request | ||||
|  */ | ||||
| typedef struct lnm_http_req { | ||||
|   struct { | ||||
|     char *s; | ||||
|     size_t len; | ||||
|     bool owned; | ||||
|   } buf; | ||||
|   int minor_version; | ||||
|   lnm_http_method method; | ||||
|   struct { | ||||
|     size_t o; | ||||
|     size_t len; | ||||
|     regmatch_t groups[LNM_HTTP_MAX_REGEX_GROUPS]; | ||||
|   } path; | ||||
|   struct { | ||||
|     size_t o; | ||||
|     size_t len; | ||||
|   } query; | ||||
|   struct { | ||||
|     lnm_http_req_header arr[LNM_HTTP_MAX_REQ_HEADERS]; | ||||
|     size_t len; | ||||
|   } headers; | ||||
|   struct { | ||||
|     uint64_t expected_len; | ||||
|     uint64_t len; | ||||
|     char *buf; | ||||
|     bool owned; | ||||
|   } body; | ||||
| } lnm_http_req; | ||||
| 
 | ||||
| typedef enum lnm_http_parse_err { | ||||
|   lnm_http_parse_err_ok = 0, | ||||
|   lnm_http_parse_err_incomplete, | ||||
|   lnm_http_parse_err_invalid, | ||||
|   lnm_http_parse_err_unknown_method, | ||||
| } lnm_http_parse_err; | ||||
| 
 | ||||
| /**
 | ||||
|  * Try to parse the given buffer into an HTTP request. | ||||
|  * | ||||
|  * @param req request to store parsed data in | ||||
|  * @param buf buffer to parse; might be modified | ||||
|  * @param len length of buf | ||||
|  */ | ||||
| lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, size_t len); | ||||
| 
 | ||||
| /**
 | ||||
|  * Reset the given request object, free'ing all its relevant parts and allowing | ||||
|  * it to be reused as a new object. | ||||
|  * | ||||
|  * @param req object to reset | ||||
|  */ | ||||
| void lnm_http_req_reset(lnm_http_req *req); | ||||
| 
 | ||||
| /**
 | ||||
|  * Retrieve a specific header from the request. | ||||
|  * | ||||
|  * Pointers retrieved from this function should never be used between step | ||||
|  * functions; simply request the header again if you need to. | ||||
|  * | ||||
|  * @param out where to write pointer to header value | ||||
|  * @param out_len where to store length of out value | ||||
|  * @param req request to look for header in | ||||
|  * @param type type of header to look for | ||||
|  */ | ||||
| lnm_err lnm_http_req_header_get(const char **out, size_t *out_len, | ||||
|                                 lnm_http_req *req, lnm_http_header type); | ||||
| 
 | ||||
| /**
 | ||||
|  * Retrieve a specific header from the request by specifying its name. | ||||
|  * | ||||
|  * Pointers retrieved from this function should never be used between step | ||||
|  * functions; simply request the header again if you need to. | ||||
|  * | ||||
|  * @param out where to write pointer to header value | ||||
|  * @param out_len where to store length of out value | ||||
|  * @param req request to look for header in | ||||
|  * @param name name of the header; matches case-insensitive | ||||
|  */ | ||||
| lnm_err lnm_http_req_header_get_s(const char **out, size_t *out_len, | ||||
|                                   lnm_http_req *req, const char *name); | ||||
| 
 | ||||
| #endif | ||||
|  | @ -1,119 +0,0 @@ | |||
| #ifndef LNM_HTTP_RES | ||||
| #define LNM_HTTP_RES | ||||
| 
 | ||||
| #include <stdbool.h> | ||||
| #include <stdio.h> | ||||
| 
 | ||||
| #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 | ||||
|  | @ -1,47 +0,0 @@ | |||
| #ifndef LNM_LOG | ||||
| #define LOG | ||||
| 
 | ||||
| #include <stdarg.h> | ||||
| 
 | ||||
| #include "lnm/common.h" | ||||
| 
 | ||||
| typedef struct lnm_logger lnm_logger; | ||||
| 
 | ||||
| typedef enum lnm_log_level { | ||||
|   lnm_log_level_debug = 0, | ||||
|   lnm_log_level_info, | ||||
|   lnm_log_level_notice, | ||||
|   lnm_log_level_warning, | ||||
|   lnm_log_level_error, | ||||
|   lnm_log_level_critical | ||||
| } lnm_log_level; | ||||
| 
 | ||||
| extern const char *lnm_log_level_names[]; | ||||
| 
 | ||||
| /**
 | ||||
|  * Initialize the global logger. | ||||
|  */ | ||||
| lnm_err lnm_log_init_global(); | ||||
| 
 | ||||
| /**
 | ||||
|  * Register stdout as one of the streams for the global logger. | ||||
|  */ | ||||
| lnm_err lnm_log_register_stdout(lnm_log_level level); | ||||
| 
 | ||||
| void lnm_log(lnm_log_level level, const char *section, const char *fmt, ...) | ||||
|     __attribute__((format(printf, 3, 4))); | ||||
| 
 | ||||
| #define lnm_ldebug(section, fmt, ...)                                          \ | ||||
|   lnm_log(lnm_log_level_debug, section, fmt, __VA_ARGS__) | ||||
| #define lnm_linfo(section, fmt, ...)                                           \ | ||||
|   lnm_log(lnm_log_level_info, section, fmt, __VA_ARGS__) | ||||
| #define lnm_lnotice(section, fmt, ...)                                         \ | ||||
|   lnm_log(lnm_log_level_notice, section, fmt, __VA_ARGS__) | ||||
| #define lnm_lwarning(section, fmt, ...)                                        \ | ||||
|   lnm_log(lnm_log_level_warning, section, fmt, __VA_ARGS__) | ||||
| #define lnm_lerror(section, fmt, ...)                                          \ | ||||
|   lnm_log(lnm_log_level_error, section, fmt, __VA_ARGS__) | ||||
| #define lnm_lcritical(section, fmt, ...)                                       \ | ||||
|   lnm_log(lnm_log_level_critical, section, fmt, __VA_ARGS__) | ||||
| 
 | ||||
| #endif | ||||
|  | @ -1,54 +0,0 @@ | |||
| #ifndef LNM_LOOP | ||||
| #define LNM_LOOP | ||||
| 
 | ||||
| #include <stdatomic.h> | ||||
| #include <stdint.h> | ||||
| #include <stdlib.h> | ||||
| 
 | ||||
| #include "lnm/common.h" | ||||
| 
 | ||||
| #define LNM_LOOP_BUF_SIZE 2048 | ||||
| 
 | ||||
| typedef enum { | ||||
|   lnm_loop_state_req = 0, | ||||
|   lnm_loop_state_res, | ||||
|   lnm_loop_state_end, | ||||
| } lnm_loop_state; | ||||
| 
 | ||||
| typedef struct lnm_loop_conn { | ||||
|   int fd; | ||||
|   lnm_loop_state state; | ||||
|   void *ctx; | ||||
|   struct { | ||||
|     char buf[LNM_LOOP_BUF_SIZE]; | ||||
|     size_t size; | ||||
|     size_t read; | ||||
|   } r; | ||||
|   struct { | ||||
|     char buf[LNM_LOOP_BUF_SIZE]; | ||||
|     size_t size; | ||||
|   } w; | ||||
| } lnm_loop_conn; | ||||
| 
 | ||||
| typedef struct lnm_loop { | ||||
|   int listen_fd; | ||||
|   int epoll_fd; | ||||
|   atomic_int open; | ||||
|   void *gctx; | ||||
|   lnm_err (*ctx_init)(void **out, void *gctx); | ||||
|   void (*ctx_free)(void *ctx); | ||||
|   void (*data_read)(lnm_loop_conn *conn); | ||||
|   void (*data_write)(lnm_loop_conn *conn); | ||||
| } lnm_loop; | ||||
| 
 | ||||
| lnm_err lnm_loop_init(lnm_loop **out, void *gctx, | ||||
|                       lnm_err (*ctx_init)(void **out, void *gctx), | ||||
|                       void (*ctx_free)(void *ctx), | ||||
|                       void (*data_read)(lnm_loop_conn *conn), | ||||
|                       void (*data_write)(lnm_loop_conn *conn)); | ||||
| 
 | ||||
| lnm_err lnm_loop_setup(lnm_loop *l, uint16_t port); | ||||
| 
 | ||||
| lnm_err lnm_loop_run(lnm_loop *l, int thread_count); | ||||
| 
 | ||||
| #endif | ||||
|  | @ -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 <sys/types.h> | ||||
| 
 | ||||
| #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 | ||||
|  | @ -1,69 +0,0 @@ | |||
| #ifndef LNM_HTTP_LOOP_INTERNAL | ||||
| #define LNM_HTTP_LOOP_INTERNAL | ||||
| 
 | ||||
| #include <regex.h> | ||||
| 
 | ||||
| #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 | ||||
|  | @ -1,26 +0,0 @@ | |||
| #ifndef LNM_LOG_INTERNAL | ||||
| #define LNM_LOG_INTERNAL | ||||
| 
 | ||||
| #include "lnm/log.h" | ||||
| 
 | ||||
| typedef enum lnm_logger_stream_type { | ||||
|   lnm_logger_stream_type_file = 0 | ||||
| } lnm_logger_stream_type; | ||||
| 
 | ||||
| typedef struct lnm_logger_stream { | ||||
|   void *ptr; | ||||
|   lnm_logger_stream_type type; | ||||
|   lnm_log_level level; | ||||
| } lnm_logger_stream; | ||||
| 
 | ||||
| struct lnm_logger { | ||||
|   struct { | ||||
|     lnm_logger_stream **arr; | ||||
|     size_t len; | ||||
|   } streams; | ||||
| }; | ||||
| 
 | ||||
| lnm_err lnm_logger_stream_register(lnm_logger *logger, | ||||
|                                    lnm_logger_stream *stream); | ||||
| 
 | ||||
| #endif | ||||
|  | @ -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 | ||||
|  | @ -1,101 +0,0 @@ | |||
| #include "lnm/http/consts.h" | ||||
| 
 | ||||
| const char *lnm_http_method_names[] = {"GET",   "POST",   "PUT", | ||||
|                                        "PATCH", "DELETE", "HEAD"}; | ||||
| const size_t lnm_http_method_names_len = | ||||
|     sizeof(lnm_http_method_names) / sizeof(lnm_http_method_names[0]); | ||||
| 
 | ||||
| // clang-format off
 | ||||
| 
 | ||||
| const char *lnm_http_status_names[][32] = { | ||||
|   // 1xx
 | ||||
|   { | ||||
|     "Continue", // 100
 | ||||
|     "Switching Protocols", // 101,
 | ||||
|     "Processing", // 102
 | ||||
|     "Early Hints", // 103
 | ||||
|   }, | ||||
|   // 2xx
 | ||||
|   { | ||||
|     "OK", // 200
 | ||||
|     "Created", // 201
 | ||||
|     "Accepted", // 202
 | ||||
|     "Non-Authoritative Information", // 203
 | ||||
|     "No Content", // 204
 | ||||
|     "Reset Content", // 205
 | ||||
|     "Partial Content", // 206
 | ||||
|     "Multi-Status", // 207
 | ||||
|     "Already Reported", // 208
 | ||||
|   }, | ||||
|   // 3xx
 | ||||
|   { | ||||
|     "Multiple Choices", // 300
 | ||||
|     "Moved Permanently", // 301
 | ||||
|     "Found", // 302
 | ||||
|     "See Other", // 303
 | ||||
|     "Not Modified", // 304
 | ||||
|     NULL, // 305
 | ||||
|     NULL, // 306
 | ||||
|     "Temporary Redirect", // 307
 | ||||
|     "Permanent Redirect", // 308
 | ||||
|   }, | ||||
|   // 4xx
 | ||||
|   { | ||||
|     "Bad Request", // 400
 | ||||
|     "Unauthorized", // 401
 | ||||
|     "Payment Required", // 402
 | ||||
|     "Forbidden", // 403
 | ||||
|     "Not Found", // 404
 | ||||
|     "Method Not Allowed", // 405
 | ||||
|     "Not Acceptable", // 406
 | ||||
|     "Proxy Authentication Required", // 407
 | ||||
|     "Request Timeout", // 408
 | ||||
|     "Conflict", // 409
 | ||||
|     "Gone", // 410
 | ||||
|     "Length Required", // 411
 | ||||
|     "Precondition Failed", // 412
 | ||||
|     "Content Too Large", // 413
 | ||||
|     "URI Too Long", // 414
 | ||||
|     "Unsupported Media Type", // 415
 | ||||
|     "Range Not Satisfiable", // 416
 | ||||
|     "Expectation Failed", // 417
 | ||||
|     "I'm a teapot", // 418
 | ||||
|     NULL, // 419
 | ||||
|     NULL, // 420
 | ||||
|     "Misdirected Request", // 421
 | ||||
|     "Unprocessable Content", // 422
 | ||||
|     "Locked", // 423
 | ||||
|     "Failed Dependency", // 424
 | ||||
|     "Too Early", // 425
 | ||||
|     "Upgrade Required", // 426
 | ||||
|     NULL, // 427
 | ||||
|     "Precondition Required", // 428
 | ||||
|     "Too Many Requests", // 429
 | ||||
|     NULL, // 430
 | ||||
|     "Request Header Fields Too Large", // 431
 | ||||
|   }, | ||||
|   // 5xx
 | ||||
|   { | ||||
|     "Internal Server Error", // 500
 | ||||
|     "Not Implemented", // 501
 | ||||
|     "Bad Gateway", // 502
 | ||||
|     "Service Unavailable", // 503
 | ||||
|     "Gateway Timeout", // 504
 | ||||
|     "HTTP Version Not Supported", // 505
 | ||||
|     "Variant Also Negotiates", // 506
 | ||||
|     "Insufficient Storage", // 507
 | ||||
|     "Loop Detected", // 508
 | ||||
|     NULL, // 509
 | ||||
|     "Not Extended", // 510
 | ||||
|     "Network Authentication Required" // 511
 | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| const char *lnm_http_header_names[] = { | ||||
|   "Connection", | ||||
|   "Location", | ||||
|   "Content-Type", | ||||
|   "Content-Disposition", | ||||
|   "Server", | ||||
|   "Content-Length" | ||||
| }; | ||||
|  | @ -1,138 +0,0 @@ | |||
| #include <stdlib.h> | ||||
| 
 | ||||
| #include "lnm/common.h" | ||||
| #include "lnm/http/loop.h" | ||||
| #include "lnm/http/loop_internal.h" | ||||
| #include "lnm/loop_internal.h" | ||||
| 
 | ||||
| lnm_err lnm_http_loop_init(lnm_http_loop **out, void *c_gctx, | ||||
|                            lnm_http_ctx_init_fn ctx_init, | ||||
|                            lnm_http_ctx_reset_fn ctx_reset, | ||||
|                            lnm_http_ctx_free_fn ctx_free) { | ||||
|   lnm_http_loop *hl = calloc(1, sizeof(lnm_http_loop)); | ||||
| 
 | ||||
|   if (hl == NULL) { | ||||
|     return lnm_err_failed_alloc; | ||||
|   } | ||||
| 
 | ||||
|   LNM_RES2(lnm_http_loop_gctx_init((lnm_http_loop_gctx **)&hl->gctx, c_gctx, | ||||
|                                    ctx_init, ctx_reset, ctx_free), | ||||
|            free(hl)); | ||||
| 
 | ||||
|   hl->data_read = lnm_http_loop_process; | ||||
|   hl->data_write = lnm_http_loop_process; | ||||
|   hl->ctx_init = (lnm_err(*)(void **, void *))lnm_http_loop_ctx_init; | ||||
|   hl->ctx_free = (void (*)(void *))lnm_http_loop_ctx_free; | ||||
|   *out = hl; | ||||
| 
 | ||||
|   return lnm_err_ok; | ||||
| } | ||||
| 
 | ||||
| lnm_err lnm_http_step_init(lnm_http_step **out, lnm_http_step_fn fn) { | ||||
|   lnm_http_step *step = calloc(1, sizeof(lnm_http_step)); | ||||
| 
 | ||||
|   if (step == NULL) { | ||||
|     return lnm_err_failed_alloc; | ||||
|   } | ||||
| 
 | ||||
|   step->fn = fn; | ||||
|   *out = step; | ||||
| 
 | ||||
|   return lnm_err_ok; | ||||
| } | ||||
| 
 | ||||
| lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step *step, | ||||
|                              lnm_http_step_fn fn) { | ||||
|   LNM_RES(lnm_http_step_init(out, fn)); | ||||
| 
 | ||||
|   if (step != NULL) { | ||||
|     step->next = *out; | ||||
|   } | ||||
| 
 | ||||
|   return lnm_err_ok; | ||||
| } | ||||
| 
 | ||||
| lnm_err lnm_http_route_init(lnm_http_route **out) { | ||||
|   lnm_http_route *route = calloc(1, sizeof(lnm_http_route)); | ||||
| 
 | ||||
|   if (route == NULL) { | ||||
|     return lnm_err_failed_alloc; | ||||
|   } | ||||
| 
 | ||||
|   *out = route; | ||||
| 
 | ||||
|   return lnm_err_ok; | ||||
| } | ||||
| 
 | ||||
| lnm_err lnm_http_route_init_literal(lnm_http_route **out, | ||||
|                                     lnm_http_method method, const char *path, | ||||
|                                     lnm_http_step *step) { | ||||
|   LNM_RES(lnm_http_route_init(out)); | ||||
| 
 | ||||
|   (*out)->type = lnm_http_route_type_literal; | ||||
|   (*out)->method = method; | ||||
|   (*out)->route.s = path; | ||||
|   (*out)->step = step; | ||||
| 
 | ||||
|   return lnm_err_ok; | ||||
| } | ||||
| 
 | ||||
| lnm_err lnm_http_route_init_regex(lnm_http_route **out, lnm_http_method method, | ||||
|                                   const char *pattern, int regex_group_count, | ||||
|                                   lnm_http_step *step) { | ||||
|   regex_t *regex = calloc(1, sizeof(regex_t)); | ||||
| 
 | ||||
|   if (regex == NULL) { | ||||
|     return lnm_err_failed_alloc; | ||||
|   } | ||||
| 
 | ||||
|   if (regcomp(regex, pattern, REG_EXTENDED) != 0) { | ||||
|     free(regex); | ||||
|     return lnm_err_bad_regex; | ||||
|   } | ||||
| 
 | ||||
|   LNM_RES2(lnm_http_route_init(out), free(regex)); | ||||
| 
 | ||||
|   (*out)->method = method; | ||||
|   (*out)->type = lnm_http_route_type_regex; | ||||
|   (*out)->route.regex = regex; | ||||
|   (*out)->regex_group_count = regex_group_count; | ||||
|   (*out)->step = step; | ||||
| 
 | ||||
|   return lnm_err_ok; | ||||
| } | ||||
| 
 | ||||
| lnm_err lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route) { | ||||
|   lnm_http_loop_gctx *gctx = hl->gctx; | ||||
| 
 | ||||
|   lnm_http_route **new_routes = | ||||
|       gctx->routes.len > 0 | ||||
|           ? realloc(gctx->routes.arr, | ||||
|                     (gctx->routes.len + 1) * sizeof(lnm_http_route *)) | ||||
|           : malloc(sizeof(lnm_http_route *)); | ||||
| 
 | ||||
|   if (new_routes == NULL) { | ||||
|     return lnm_err_failed_alloc; | ||||
|   } | ||||
| 
 | ||||
|   new_routes[gctx->routes.len] = route; | ||||
|   gctx->routes.arr = new_routes; | ||||
|   gctx->routes.len++; | ||||
| 
 | ||||
|   return lnm_err_ok; | ||||
| } | ||||
| 
 | ||||
| lnm_err lnm_http_loop_run(lnm_http_loop *hl, uint16_t port, int thread_count) { | ||||
|   LNM_RES(lnm_loop_setup(hl, port)); | ||||
|   return lnm_loop_run(hl, thread_count); | ||||
| } | ||||
| 
 | ||||
| void lnm_http_loop_set_api_key(lnm_http_loop *hl, const char *api_key) { | ||||
|   lnm_http_loop_gctx *gctx = hl->gctx; | ||||
|   gctx->api_key = api_key; | ||||
| } | ||||
| 
 | ||||
| void lnm_http_loop_set_server(lnm_http_loop *hl, const char *server) { | ||||
|   lnm_http_loop_gctx *gctx = hl->gctx; | ||||
|   gctx->server = server; | ||||
| } | ||||
|  | @ -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); | ||||
| } | ||||
|  | @ -1,391 +0,0 @@ | |||
| #include <stdbool.h> | ||||
| #include <stdio.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include "lnm/http/consts.h" | ||||
| #include "lnm/http/loop.h" | ||||
| #include "lnm/http/loop_internal.h" | ||||
| #include "lnm/http/req.h" | ||||
| #include "lnm/log.h" | ||||
| #include "lnm/loop.h" | ||||
| #include "lnm/loop_internal.h" | ||||
| 
 | ||||
| static const char *section = "http"; | ||||
| 
 | ||||
| /* static const lnm_http_loop_state lnm_http_loop_state_first_req =
 | ||||
|  * lnm_http_loop_state_parse_req; */ | ||||
| static const lnm_http_loop_state lnm_http_loop_state_first_res = | ||||
|     lnm_http_loop_state_add_headers; | ||||
| 
 | ||||
| void lnm_http_loop_process_parse_req(lnm_http_conn *conn) { | ||||
|   lnm_http_loop_ctx *ctx = conn->ctx; | ||||
| 
 | ||||
|   lnm_http_parse_err res = lnm_http_req_parse( | ||||
|       &ctx->req, &conn->r.buf[conn->r.read], conn->r.size - conn->r.read); | ||||
| 
 | ||||
|   switch (res) { | ||||
|   case lnm_http_parse_err_ok: | ||||
|     conn->r.read += ctx->req.buf.len; | ||||
|     ctx->state = lnm_http_loop_state_route; | ||||
| 
 | ||||
|     lnm_linfo(section, "%s %.*s HTTP/1.%i", | ||||
|               lnm_http_method_names[ctx->req.method], (int)ctx->req.path.len, | ||||
|               ctx->req.buf.s + ctx->req.path.o, ctx->req.minor_version); | ||||
|     break; | ||||
|   case lnm_http_parse_err_incomplete: | ||||
|     // If the request is already the size of the read buffer, we close the
 | ||||
|     // request. Otherwise, we wait for anything read
 | ||||
|     if (conn->r.size - conn->r.read == LNM_LOOP_BUF_SIZE) { | ||||
|       lnm_linfo(section, "Received request larger than buffer (%i bytes)", | ||||
|                 LNM_LOOP_BUF_SIZE); | ||||
| 
 | ||||
|       conn->state = lnm_loop_state_end; | ||||
|     } | ||||
|     break; | ||||
|   case lnm_http_parse_err_invalid: | ||||
|     lnm_linfo(section, "%s", "Received invalid request"); | ||||
| 
 | ||||
|     conn->state = lnm_loop_state_end; | ||||
|     break; | ||||
|   case lnm_http_parse_err_unknown_method: | ||||
|     ctx->res.status = lnm_http_status_method_not_implemented; | ||||
|     ctx->state = lnm_http_loop_state_first_res; | ||||
|     break; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void lnm_http_loop_process_route(lnm_http_conn *conn) { | ||||
|   lnm_http_loop_ctx *ctx = conn->ctx; | ||||
|   lnm_http_loop_gctx *gctx = ctx->g; | ||||
| 
 | ||||
|   // 0: no match
 | ||||
|   // 1: matched route, but not method
 | ||||
|   // 2: fully matched route
 | ||||
|   int match_level = 0; | ||||
|   lnm_http_route *route; | ||||
| 
 | ||||
|   for (size_t i = 0; i < gctx->routes.len && match_level < 3; i++) { | ||||
|     route = gctx->routes.arr[i]; | ||||
|     bool matched_path = false; | ||||
| 
 | ||||
|     switch (route->type) { | ||||
|     case lnm_http_route_type_literal: | ||||
|       matched_path = strncmp(route->route.s, ctx->req.buf.s + ctx->req.path.o, | ||||
|                              ctx->req.path.len) == 0; | ||||
|       break; | ||||
|     case lnm_http_route_type_regex: | ||||
|       matched_path = | ||||
|           regexec(route->route.regex, ctx->req.buf.s + ctx->req.path.o, | ||||
|                   LNM_HTTP_MAX_REGEX_GROUPS, ctx->req.path.groups, 0) == 0; | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     // GET routes also automatically route HEAD requests
 | ||||
|     bool matched_method = route->method == ctx->req.method || | ||||
|                           (route->method == lnm_http_method_get && | ||||
|                            ctx->req.method == lnm_http_method_head); | ||||
|     int new_match_level = 2 * matched_path + matched_method; | ||||
| 
 | ||||
|     // Remember the previous match levels so we can return the correct status
 | ||||
|     // message
 | ||||
|     match_level = match_level < new_match_level ? new_match_level : match_level; | ||||
|   } | ||||
| 
 | ||||
|   switch (match_level) { | ||||
|   case 0: | ||||
|   case 1: | ||||
|     ctx->res.status = lnm_http_status_not_found; | ||||
|     ctx->state = lnm_http_loop_state_first_res; | ||||
|     break; | ||||
|   case 2: | ||||
|     ctx->res.status = lnm_http_status_method_not_allowed; | ||||
|     ctx->state = lnm_http_loop_state_first_res; | ||||
|     break; | ||||
|   case 3: | ||||
|     ctx->route = route; | ||||
|     ctx->cur_step = route->step; | ||||
|     ctx->state = lnm_http_loop_state_parse_headers; | ||||
|     break; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void lnm_http_loop_process_parse_headers(lnm_http_conn *conn) { | ||||
|   lnm_http_loop_ctx *ctx = conn->ctx; | ||||
|   lnm_http_req *req = &ctx->req; | ||||
| 
 | ||||
|   const char *value; | ||||
|   size_t value_len; | ||||
|   if (lnm_http_req_header_get(&value, &value_len, req, | ||||
|                               lnm_http_header_content_length) == lnm_err_ok) { | ||||
|     req->body.expected_len = lnm_atoi(value, value_len); | ||||
|   } | ||||
| 
 | ||||
|   ctx->state = lnm_http_loop_state_steps; | ||||
| } | ||||
| 
 | ||||
| void lnm_http_loop_process_steps(lnm_http_conn *conn) { | ||||
|   lnm_http_loop_ctx *ctx = conn->ctx; | ||||
|   lnm_http_step *step = NULL; | ||||
| 
 | ||||
|   // Loop until we either:
 | ||||
|   // - reach the end of the chain of steps, indicated by NULL
 | ||||
|   // - have a step that's waiting for I/O
 | ||||
|   while ((ctx->cur_step != NULL) && (step != ctx->cur_step)) { | ||||
|     step = ctx->cur_step; | ||||
| 
 | ||||
|     switch (step->fn(conn)) { | ||||
|     case lnm_http_step_err_done: | ||||
|       ctx->cur_step = ctx->cur_step->next; | ||||
|       break; | ||||
|     case lnm_http_step_err_io_needed: | ||||
|       break; | ||||
|     case lnm_http_step_err_close: | ||||
|       conn->state = lnm_loop_state_end; | ||||
|       break; | ||||
|     case lnm_http_step_err_res: | ||||
|       ctx->state = lnm_http_loop_state_first_res; | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   if (ctx->cur_step == NULL) { | ||||
|     ctx->state = lnm_http_loop_state_add_headers; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void lnm_http_loop_state_process_add_headers(lnm_http_conn *conn) { | ||||
|   lnm_http_loop_ctx *ctx = conn->ctx; | ||||
|   lnm_http_res *res = &ctx->res; | ||||
| 
 | ||||
|   uint64_t digits = lnm_digits(res->body.len); | ||||
|   char *buf = malloc(digits + 1); | ||||
| 
 | ||||
|   if (buf == NULL) { | ||||
|     conn->state = lnm_loop_state_end; | ||||
| 
 | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   sprintf(buf, "%lu", res->body.len); | ||||
|   lnm_http_res_add_header_len(res, lnm_http_header_content_length, buf, digits, | ||||
|                               true); | ||||
| 
 | ||||
|   if (ctx->g->server != NULL) { | ||||
|     lnm_http_res_add_header(res, lnm_http_header_server, (char *)ctx->g->server, | ||||
|                             false); | ||||
|   } | ||||
| 
 | ||||
|   if (res->status == 0) { | ||||
|     res->status = lnm_http_status_ok; | ||||
|   } | ||||
| 
 | ||||
|   lnm_linfo(section, "%i %s", res->status, | ||||
|             lnm_http_status_names[res->status / 100 - 1][res->status % 100]); | ||||
| 
 | ||||
|   ctx->state = lnm_http_loop_state_write_status_line; | ||||
| } | ||||
| 
 | ||||
| // This function is intentionally written inefficiently for now, as it will most
 | ||||
| // likely only have to run once for each response
 | ||||
| void lnm_http_loop_process_write_status_line(lnm_http_conn *conn) { | ||||
|   lnm_http_loop_ctx *ctx = conn->ctx; | ||||
|   lnm_http_res *res = &ctx->res; | ||||
| 
 | ||||
|   const char *response_type_name = | ||||
|       lnm_http_status_names[res->status / 100 - 1][res->status % 100]; | ||||
| 
 | ||||
|   // First we calculate the size of the start of the header
 | ||||
|   size_t buf_size = | ||||
|       snprintf(NULL, 0, "HTTP/1.1 %i %s\n", res->status, response_type_name); | ||||
|   char buf[buf_size + 1]; | ||||
|   sprintf(buf, "HTTP/1.1 %i %s\n", res->status, response_type_name); | ||||
| 
 | ||||
|   size_t to_write = | ||||
|       LNM_MIN(buf_size - res->written, LNM_LOOP_BUF_SIZE - conn->w.size); | ||||
|   memcpy(&conn->w.buf[conn->w.size], &buf[res->written], to_write); | ||||
| 
 | ||||
|   conn->w.size += to_write; | ||||
|   res->written += to_write; | ||||
| 
 | ||||
|   if (res->written == buf_size) { | ||||
|     res->written = 0; | ||||
|     ctx->state = lnm_http_loop_state_write_headers; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void lnm_http_loop_process_write_headers(lnm_http_conn *conn) { | ||||
|   lnm_http_loop_ctx *ctx = conn->ctx; | ||||
|   lnm_http_res *res = &ctx->res; | ||||
| 
 | ||||
|   lnm_http_res_header *header; | ||||
| 
 | ||||
|   // Loop as long as we can still write new data and have headers to write
 | ||||
|   while ((conn->w.size < LNM_LOOP_BUF_SIZE) && | ||||
|          ((header = res->headers.current) != NULL)) { | ||||
|     size_t buf_len = header->name.len + 2 + header->value.len + 1; | ||||
| 
 | ||||
|     // Here, we also constantly calculate the entire buffer as we assume each
 | ||||
|     // header will be written in one go
 | ||||
|     char buf[buf_len]; | ||||
|     memcpy(buf, header->name.s, header->name.len); | ||||
|     memcpy(&buf[header->name.len + 2], header->value.s, header->value.len); | ||||
|     buf[header->name.len] = ':'; | ||||
|     buf[header->name.len + 1] = ' '; | ||||
|     buf[buf_len - 1] = '\n'; | ||||
| 
 | ||||
|     size_t to_write = | ||||
|         LNM_MIN(buf_len - res->written, LNM_LOOP_BUF_SIZE - conn->w.size); | ||||
|     memcpy(&conn->w.buf[conn->w.size], &buf[res->written], to_write); | ||||
| 
 | ||||
|     conn->w.size += to_write; | ||||
|     res->written += to_write; | ||||
| 
 | ||||
|     if (res->written == buf_len) { | ||||
|       res->written = 0; | ||||
|       res->headers.current = res->headers.current->next; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   // The headers should end with an additional newline. If there's no space left
 | ||||
|   // in the write buffer, we don't switch states so we can re-try this write
 | ||||
|   // later
 | ||||
|   if ((res->headers.current == NULL) && (conn->w.size < LNM_LOOP_BUF_SIZE)) { | ||||
|     conn->w.buf[conn->w.size] = '\n'; | ||||
|     conn->w.size++; | ||||
| 
 | ||||
|     // HEAD requests function exactly the same as GET requests, except that they
 | ||||
|     // skip the body writing part
 | ||||
|     ctx->state = | ||||
|         ctx->req.method != lnm_http_method_head && ctx->res.body.len > 0 | ||||
|             ? lnm_http_loop_state_write_body | ||||
|             : lnm_http_loop_state_finish; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void lnm_http_loop_process_write_body(lnm_http_conn *conn) { | ||||
|   lnm_http_loop_ctx *ctx = conn->ctx; | ||||
|   lnm_http_res *res = &ctx->res; | ||||
| 
 | ||||
|   size_t to_write = | ||||
|       LNM_MIN(res->body.len - res->written, LNM_LOOP_BUF_SIZE - conn->w.size); | ||||
|   size_t written = 0; | ||||
| 
 | ||||
|   switch (res->body.type) { | ||||
|   case lnm_http_res_body_type_buf: | ||||
|     memcpy(&conn->w.buf[conn->w.size], &res->body.data.buf[res->written], | ||||
|            to_write); | ||||
|     written = to_write; | ||||
|     break; | ||||
|   case lnm_http_res_body_type_file: | ||||
|     written = fread(&conn->w.buf[conn->w.size], 1, to_write, res->body.data.f); | ||||
| 
 | ||||
|     if ((written == 0) && (ferror(res->body.data.f) != 0)) { | ||||
|       ctx->state = lnm_http_loop_state_finish; | ||||
|     } | ||||
|     break; | ||||
|   case lnm_http_res_body_type_fn: | ||||
|     if (res->body.data.fn(&written, &conn->w.buf[conn->w.size], conn, | ||||
|                           res->written, to_write) != lnm_err_ok) { | ||||
|       ctx->state = lnm_http_loop_state_finish; | ||||
|     } | ||||
|     break; | ||||
|   } | ||||
| 
 | ||||
|   conn->w.size += written; | ||||
|   res->written += written; | ||||
| 
 | ||||
|   if (res->written == res->body.len) { | ||||
|     ctx->state = lnm_http_loop_state_finish; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void lnm_http_loop_process_finish(lnm_http_conn *conn) { | ||||
|   // First we ensure the write buffer is fully flushed
 | ||||
|   if (conn->w.size > 0) { | ||||
|     return; | ||||
|   } | ||||
| 
 | ||||
|   lnm_http_loop_ctx *ctx = conn->ctx; | ||||
|   lnm_http_loop_ctx_reset(ctx); | ||||
| 
 | ||||
|   ctx->state = lnm_http_loop_state_parse_req; | ||||
| } | ||||
| 
 | ||||
| void (*process_fns[])(lnm_http_conn *conn) = { | ||||
|     lnm_http_loop_process_parse_req, | ||||
|     lnm_http_loop_process_route, | ||||
|     lnm_http_loop_process_parse_headers, | ||||
|     lnm_http_loop_process_steps, | ||||
|     lnm_http_loop_state_process_add_headers, | ||||
|     lnm_http_loop_process_write_status_line, | ||||
|     lnm_http_loop_process_write_headers, | ||||
|     lnm_http_loop_process_write_body, | ||||
|     lnm_http_loop_process_finish, | ||||
| }; | ||||
| 
 | ||||
| lnm_loop_state state_map[] = { | ||||
|     // parse_req
 | ||||
|     lnm_loop_state_req, | ||||
|     // route
 | ||||
|     lnm_loop_state_req, | ||||
|     // parse_headers
 | ||||
|     lnm_loop_state_req, | ||||
|     // steps
 | ||||
|     lnm_loop_state_req, | ||||
|     // add_headers
 | ||||
|     lnm_loop_state_req, | ||||
|     // write_status_line
 | ||||
|     lnm_loop_state_res, | ||||
|     // write_headers
 | ||||
|     lnm_loop_state_res, | ||||
|     // write_body
 | ||||
|     lnm_loop_state_res, | ||||
|     // finish
 | ||||
|     lnm_loop_state_res, | ||||
| }; | ||||
| 
 | ||||
| void lnm_http_loop_process(lnm_http_conn *conn) { | ||||
|   lnm_http_loop_ctx *ctx = conn->ctx; | ||||
| 
 | ||||
|   lnm_http_loop_state http_loop_state; | ||||
|   lnm_loop_state loop_state = conn->state; | ||||
| 
 | ||||
|   // We stop processing if:
 | ||||
|   // - the event loop state has been explicitely changed inside the executed
 | ||||
|   // step, as we need to switch to the other I/O loop
 | ||||
|   // - the event loop state needs to be changed because the next step should be
 | ||||
|   // run in another event loop state
 | ||||
|   // - the process fn returned without changing the HTTP loop state, indicating
 | ||||
|   // it's waiting for I/O
 | ||||
|   do { | ||||
|     http_loop_state = ctx->state; | ||||
| 
 | ||||
|     process_fns[http_loop_state](conn); | ||||
|   } while ((conn->state == state_map[ctx->state]) && | ||||
|            (http_loop_state != ctx->state)); | ||||
| 
 | ||||
|   // Check required to prevent overwriting manually set event loop state
 | ||||
|   conn->state = conn->state == loop_state ? state_map[ctx->state] : conn->state; | ||||
| 
 | ||||
|   // We move the request to a dedicated buffer if the read buffer needs to be
 | ||||
|   // reused
 | ||||
|   if ((conn->state == lnm_loop_state_req) && (conn->state == loop_state) && | ||||
|       (!ctx->req.buf.owned) && (ctx->req.buf.len > 0)) { | ||||
|     char *buf = malloc(ctx->req.buf.len); | ||||
| 
 | ||||
|     if (buf == NULL) { | ||||
|       lnm_lerror(section, | ||||
|                  "Failed to allocate request buffer; closing connection %i", | ||||
|                  conn->fd); | ||||
| 
 | ||||
|       conn->state = lnm_loop_state_end; | ||||
|     } else { | ||||
|       memcpy(buf, ctx->req.buf.s, ctx->req.buf.len); | ||||
|       ctx->req.buf.s = buf; | ||||
|       ctx->req.buf.owned = true; | ||||
| 
 | ||||
|       lnm_ldebug(section, "Allocated request buffer for connection %i", | ||||
|                  conn->fd); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | @ -1,44 +0,0 @@ | |||
| #include <string.h> | ||||
| 
 | ||||
| #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; | ||||
| } | ||||
|  | @ -1,114 +0,0 @@ | |||
| #include <stdbool.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #include "lnm/common.h" | ||||
| #include "lnm/http/consts.h" | ||||
| #include "lnm/http/loop.h" | ||||
| #include "lnm/http/req.h" | ||||
| 
 | ||||
| lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, | ||||
|                                       size_t len) { | ||||
|   const char *method; | ||||
|   char *path; | ||||
|   size_t method_len, path_len; | ||||
|   size_t num_headers = LNM_HTTP_MAX_REQ_HEADERS; | ||||
|   struct phr_header headers[LNM_HTTP_MAX_REQ_HEADERS]; | ||||
| 
 | ||||
|   int req_len = phr_parse_request( | ||||
|       buf, len, &method, &method_len, (const char **)&path, &path_len, | ||||
|       &req->minor_version, headers, &num_headers, req->buf.len); | ||||
| 
 | ||||
|   if (req_len == -1) { | ||||
|     req->buf.len = len; | ||||
| 
 | ||||
|     return lnm_http_parse_err_invalid; | ||||
|   } else if (req_len == -2) { | ||||
|     return lnm_http_parse_err_incomplete; | ||||
|   } | ||||
| 
 | ||||
|   bool known_method = false; | ||||
| 
 | ||||
|   for (size_t i = 0; i < lnm_http_method_names_len && !known_method; i++) { | ||||
|     if (strncmp(method, lnm_http_method_names[i], method_len) == 0) { | ||||
|       req->method = i; | ||||
|       known_method = true; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   if (!known_method) { | ||||
|     return lnm_http_parse_err_unknown_method; | ||||
|   } | ||||
| 
 | ||||
|   // Path will always end with a newline, which we can safely set to nul
 | ||||
|   path[path_len] = '\0'; | ||||
|   char *question_mark = strchr(path, '?'); | ||||
| 
 | ||||
|   // Only store query if the path doesn't simply end with a question mark
 | ||||
|   if ((question_mark != NULL) && (path_len - (question_mark + 1 - path) > 0)) { | ||||
|     req->query.o = question_mark + 1 - buf; | ||||
|     req->query.len = path_len - (question_mark + 1 - path); | ||||
| 
 | ||||
|     path_len = question_mark - path; | ||||
| 
 | ||||
|     // All parsed strings should be null-terminated. This character is either a
 | ||||
|     // newline (if at the end of the path), or a question mark (if a query is
 | ||||
|     // present).
 | ||||
|     path[path_len] = '\0'; | ||||
|   } | ||||
| 
 | ||||
|   // Also migrate headers to offset-based
 | ||||
|   for (size_t i = 0; i < num_headers; i++) { | ||||
|     req->headers.arr[i].name.o = headers[i].name - buf; | ||||
|     req->headers.arr[i].name.len = headers[i].name_len; | ||||
|     req->headers.arr[i].value.o = headers[i].value - buf; | ||||
|     req->headers.arr[i].value.len = headers[i].value_len; | ||||
|   } | ||||
| 
 | ||||
|   req->headers.len = num_headers; | ||||
| 
 | ||||
|   req->path.len = path_len; | ||||
|   req->path.o = path - buf; | ||||
| 
 | ||||
|   req->buf.len = req_len; | ||||
|   req->buf.s = buf; | ||||
|   req->buf.owned = false; | ||||
| 
 | ||||
|   return lnm_http_parse_err_ok; | ||||
| } | ||||
| 
 | ||||
| void lnm_http_req_reset(lnm_http_req *req) { | ||||
|   if (req->body.owned) { | ||||
|     free(req->body.buf); | ||||
|   } | ||||
| 
 | ||||
|   if (req->buf.owned) { | ||||
|     free(req->buf.s); | ||||
|   } | ||||
| 
 | ||||
|   memset(req, 0, sizeof(lnm_http_req)); | ||||
| } | ||||
| 
 | ||||
| lnm_err lnm_http_req_header_get(const char **out, size_t *out_len, | ||||
|                                 lnm_http_req *req, lnm_http_header type) { | ||||
|   return lnm_http_req_header_get_s(out, out_len, req, | ||||
|                                    lnm_http_header_names[type]); | ||||
| } | ||||
| 
 | ||||
| lnm_err lnm_http_req_header_get_s(const char **out, size_t *out_len, | ||||
|                                   lnm_http_req *req, const char *name) { | ||||
|   size_t name_len = strlen(name); | ||||
| 
 | ||||
|   for (size_t i = 0; i < req->headers.len; i++) { | ||||
|     const lnm_http_req_header *header = &req->headers.arr[i]; | ||||
| 
 | ||||
|     if (lnm_strnieq(req->buf.s + header->name.o, header->name.len, name, | ||||
|                     name_len)) { | ||||
|       *out = req->buf.s + header->value.o; | ||||
|       *out_len = header->value.len; | ||||
| 
 | ||||
|       return lnm_err_ok; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return lnm_err_not_found; | ||||
| } | ||||
|  | @ -1,99 +0,0 @@ | |||
| #include <string.h> | ||||
| 
 | ||||
| #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)); | ||||
| } | ||||
|  | @ -1,86 +0,0 @@ | |||
| #include <stdio.h> | ||||
| #include <time.h> | ||||
| 
 | ||||
| #include "lnm/common.h" | ||||
| #include "lnm/log_internal.h" | ||||
| 
 | ||||
| const char *lnm_log_level_names[] = {"DEBUG   ", "INFO    ", "NOTICE  ", | ||||
|                                      "WARNING ", "ERROR   ", "CRITICAL"}; | ||||
| 
 | ||||
| lnm_logger *global_logger = NULL; | ||||
| 
 | ||||
| lnm_err lnm_log_init_global() { | ||||
|   global_logger = calloc(1, sizeof(lnm_logger)); | ||||
| 
 | ||||
|   return global_logger == NULL ? lnm_err_failed_alloc : lnm_err_ok; | ||||
| } | ||||
| 
 | ||||
| lnm_err lnm_logger_stream_register(lnm_logger *logger, | ||||
|                                    lnm_logger_stream *stream) { | ||||
|   lnm_logger_stream **new = | ||||
|       logger->streams.len == 0 | ||||
|           ? malloc(sizeof(lnm_logger_stream *)) | ||||
|           : realloc(logger->streams.arr, | ||||
|                     (logger->streams.len + 1) * sizeof(lnm_logger_stream *)); | ||||
| 
 | ||||
|   if (new == NULL) { | ||||
|     return lnm_err_failed_alloc; | ||||
|   } | ||||
| 
 | ||||
|   new[logger->streams.len] = stream; | ||||
|   logger->streams.arr = new; | ||||
|   logger->streams.len++; | ||||
| 
 | ||||
|   return lnm_err_ok; | ||||
| } | ||||
| 
 | ||||
| lnm_err lnm_log_register_stdout(lnm_log_level level) { | ||||
|   lnm_logger_stream *stream = malloc(sizeof(lnm_logger_stream)); | ||||
| 
 | ||||
|   if (stream == NULL) { | ||||
|     return lnm_err_failed_alloc; | ||||
|   } | ||||
| 
 | ||||
|   stream->type = lnm_logger_stream_type_file; | ||||
|   stream->ptr = stdout; | ||||
|   stream->level = level; | ||||
| 
 | ||||
|   LNM_RES2(lnm_logger_stream_register(global_logger, stream), free(stream)); | ||||
| 
 | ||||
|   return lnm_err_ok; | ||||
| } | ||||
| 
 | ||||
| void lnm_vlog(lnm_log_level level, const char *section, const char *fmt, | ||||
|               va_list ap) { | ||||
|   char date_str[32]; | ||||
| 
 | ||||
|   time_t now = time(NULL); | ||||
|   strftime(date_str, sizeof(date_str) - 1, "%Y-%m-%d %H:%M:%S", | ||||
|            localtime(&now)); | ||||
| 
 | ||||
|   for (size_t i = 0; i < global_logger->streams.len; i++) { | ||||
|     lnm_logger_stream *stream = global_logger->streams.arr[i]; | ||||
| 
 | ||||
|     if (level < stream->level) { | ||||
|       continue; | ||||
|     } | ||||
| 
 | ||||
|     switch (stream->type) { | ||||
|     case lnm_logger_stream_type_file: | ||||
|       fprintf(stream->ptr, "[%s][%s][%s] ", date_str, | ||||
|               lnm_log_level_names[level], section); | ||||
|       vfprintf(stream->ptr, fmt, ap); | ||||
|       fprintf(stream->ptr, "\n"); | ||||
|       break; | ||||
|     } | ||||
| 
 | ||||
|     va_end(ap); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| void lnm_log(lnm_log_level level, const char *section, const char *fmt, ...) { | ||||
|   va_list ap; | ||||
|   va_start(ap, fmt); | ||||
|   lnm_vlog(level, section, fmt, ap); | ||||
|   va_end(ap); | ||||
| } | ||||
|  | @ -1,57 +0,0 @@ | |||
| #include <stdint.h> | ||||
| #include <string.h> | ||||
| 
 | ||||
| #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; | ||||
| } | ||||
|  | @ -1,232 +0,0 @@ | |||
| #include <fcntl.h> | ||||
| #include <netinet/in.h> | ||||
| #include <pthread.h> | ||||
| #include <stdio.h> | ||||
| #include <string.h> | ||||
| #include <sys/epoll.h> | ||||
| #include <unistd.h> | ||||
| 
 | ||||
| #include "lnm/common.h" | ||||
| #include "lnm/log.h" | ||||
| #include "lnm/loop.h" | ||||
| #include "lnm/loop_internal.h" | ||||
| 
 | ||||
| static const char *section = "loop"; | ||||
| 
 | ||||
| lnm_err lnm_loop_init(lnm_loop **out, void *gctx, | ||||
|                       lnm_err (*ctx_init)(void **out, void *gctx), | ||||
|                       void (*ctx_free)(void *ctx), | ||||
|                       void (*data_read)(lnm_loop_conn *conn), | ||||
|                       void (*data_write)(lnm_loop_conn *conn)) { | ||||
|   lnm_loop *l = calloc(1, sizeof(lnm_loop)); | ||||
| 
 | ||||
|   if (l == NULL) { | ||||
|     return lnm_err_failed_alloc; | ||||
|   } | ||||
| 
 | ||||
|   l->gctx = gctx; | ||||
|   l->ctx_init = ctx_init; | ||||
|   l->ctx_free = ctx_free; | ||||
|   l->data_read = data_read; | ||||
|   l->data_write = data_write; | ||||
| 
 | ||||
|   *out = l; | ||||
| 
 | ||||
|   return lnm_err_ok; | ||||
| } | ||||
| 
 | ||||
| lnm_err lnm_loop_accept(lnm_loop *l) { | ||||
|   int conn_fd = accept(l->listen_fd, NULL, NULL); | ||||
| 
 | ||||
|   if (conn_fd < 0) { | ||||
|     lnm_lcritical(section, "accept failed: %i", conn_fd); | ||||
| 
 | ||||
|     return lnm_err_failed_network; | ||||
|   } | ||||
| 
 | ||||
|   // Set socket to non-blocking
 | ||||
|   int flags = fcntl(conn_fd, F_GETFL); | ||||
|   flags |= O_NONBLOCK; | ||||
|   fcntl(conn_fd, F_SETFL, flags); | ||||
| 
 | ||||
|   lnm_loop_conn *conn; | ||||
|   LNM_RES2(lnm_loop_conn_init(&conn, l), close(conn_fd)); | ||||
| 
 | ||||
|   conn->fd = conn_fd; | ||||
|   conn->state = lnm_loop_state_req; | ||||
| 
 | ||||
|   struct epoll_event event = {.data.ptr = conn, | ||||
|                               .events = EPOLLIN | EPOLLET | EPOLLONESHOT}; | ||||
| 
 | ||||
|   epoll_ctl(l->epoll_fd, EPOLL_CTL_ADD, conn_fd, &event); | ||||
| 
 | ||||
|   l->open++; | ||||
| 
 | ||||
|   lnm_ldebug(section, "connection opened with fd %i", conn_fd); | ||||
| 
 | ||||
|   return lnm_err_ok; | ||||
| } | ||||
| 
 | ||||
| lnm_err lnm_loop_setup(lnm_loop *l, uint16_t port) { | ||||
|   int listen_fd = socket(AF_INET, SOCK_STREAM, 0); | ||||
| 
 | ||||
|   if (listen_fd < 0) { | ||||
|     return lnm_err_failed_network; | ||||
|   } | ||||
| 
 | ||||
|   int val = 1; | ||||
|   int res = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(int)); | ||||
| 
 | ||||
|   if (res < 0) { | ||||
|     return lnm_err_failed_network; | ||||
|   } | ||||
| 
 | ||||
|   struct sockaddr_in addr = {.sin_family = AF_INET, | ||||
|                              .sin_port = ntohs(port), | ||||
|                              .sin_addr.s_addr = ntohl(0)}; | ||||
| 
 | ||||
|   res = bind(listen_fd, (const struct sockaddr *)&addr, sizeof(addr)); | ||||
| 
 | ||||
|   if (res < 0) { | ||||
|     return lnm_err_failed_network; | ||||
|   } | ||||
| 
 | ||||
|   res = listen(listen_fd, SOMAXCONN); | ||||
| 
 | ||||
|   if (res < 0) { | ||||
|     return lnm_err_failed_network; | ||||
|   } | ||||
| 
 | ||||
|   int flags = fcntl(listen_fd, F_GETFL); | ||||
|   fcntl(listen_fd, F_SETFL, flags | O_NONBLOCK); | ||||
| 
 | ||||
|   int epoll_fd = epoll_create1(0); | ||||
| 
 | ||||
|   if (epoll_fd < 0) { | ||||
|     return lnm_err_failed_network; | ||||
|   } | ||||
| 
 | ||||
|   struct epoll_event event = { | ||||
|       // The listening socket is marked using a NULL data field
 | ||||
|       .data.ptr = NULL, | ||||
|       .events = EPOLLIN | EPOLLET | EPOLLONESHOT}; | ||||
| 
 | ||||
|   res = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &event); | ||||
| 
 | ||||
|   if (res < 0) { | ||||
|     return lnm_err_failed_network; | ||||
|   } | ||||
| 
 | ||||
|   l->listen_fd = listen_fd; | ||||
|   l->epoll_fd = epoll_fd; | ||||
| 
 | ||||
|   return lnm_err_ok; | ||||
| } | ||||
| 
 | ||||
| typedef struct lnm_loop_thread_args { | ||||
|   lnm_loop *l; | ||||
|   int id; | ||||
|   int thread_count; | ||||
| } lnm_loop_thread_args; | ||||
| 
 | ||||
| lnm_err lnm_loop_run_thread(lnm_loop_thread_args *args) { | ||||
|   lnm_loop *l = args->l; | ||||
|   int thread_id = args->id; | ||||
|   int thread_count = args->thread_count; | ||||
| 
 | ||||
|   struct epoll_event *events = calloc(1, sizeof(struct epoll_event)); | ||||
|   int events_cap = 1; | ||||
| 
 | ||||
|   if (events == NULL) { | ||||
|     return lnm_err_failed_alloc; | ||||
|   } | ||||
| 
 | ||||
|   lnm_linfo(section, "thread %i started", thread_id); | ||||
| 
 | ||||
|   struct epoll_event listen_event = { | ||||
|       .data.ptr = NULL, .events = EPOLLIN | EPOLLET | EPOLLONESHOT}; | ||||
| 
 | ||||
|   while (1) { | ||||
|     int polled = epoll_wait(l->epoll_fd, events, events_cap, -1); | ||||
|     lnm_ldebug(section, "polled (thread %i): %i", thread_id, polled); | ||||
| 
 | ||||
|     if (polled < 0) { | ||||
|       return lnm_err_failed_poll; | ||||
|     } | ||||
| 
 | ||||
|     for (int i = 0; i < polled; i++) { | ||||
|       if (events[i].data.ptr == NULL) { | ||||
|         lnm_loop_accept(l); | ||||
| 
 | ||||
|         epoll_ctl(l->epoll_fd, EPOLL_CTL_MOD, l->listen_fd, &listen_event); | ||||
|       } else { | ||||
|         lnm_loop_conn *conn = events[i].data.ptr; | ||||
|         lnm_loop_conn_io(l, conn); | ||||
| 
 | ||||
|         if (conn->state == lnm_loop_state_end) { | ||||
|           int conn_fd = conn->fd; | ||||
| 
 | ||||
|           lnm_loop_conn_free(l, conn); | ||||
|           close(conn_fd); | ||||
|           l->open--; | ||||
| 
 | ||||
|           epoll_ctl(l->epoll_fd, EPOLL_CTL_DEL, conn_fd, NULL); | ||||
| 
 | ||||
|           lnm_ldebug(section, "connection closed with fd %i", conn_fd); | ||||
|         } else { | ||||
|           struct epoll_event event = { | ||||
|               .data.ptr = conn, | ||||
|               .events = | ||||
|                   (conn->state == lnm_loop_state_req ? EPOLLIN : EPOLLOUT) | | ||||
|                   EPOLLET | EPOLLONESHOT}; | ||||
|           epoll_ctl(l->epoll_fd, EPOLL_CTL_MOD, conn->fd, &event); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     int open = l->open; | ||||
|     int cap_per_thread = | ||||
|         open + 1 > thread_count ? (open + 1) / thread_count : 1; | ||||
| 
 | ||||
|     if (cap_per_thread > events_cap) { | ||||
|       struct epoll_event *new_events = | ||||
|           malloc(cap_per_thread * sizeof(struct epoll_event)); | ||||
| 
 | ||||
|       if (new_events == NULL) { | ||||
|         return lnm_err_failed_alloc; | ||||
|       } | ||||
| 
 | ||||
|       free(events); | ||||
|       events = new_events; | ||||
|       events_cap = cap_per_thread; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return lnm_err_ok; | ||||
| } | ||||
| 
 | ||||
| lnm_err lnm_loop_run(lnm_loop *l, int thread_count) { | ||||
|   if (l->epoll_fd == 0) { | ||||
|     return lnm_err_not_setup; | ||||
|   } | ||||
| 
 | ||||
|   lnm_loop_thread_args args[thread_count]; | ||||
| 
 | ||||
|   for (int i = 1; i < thread_count; i++) { | ||||
|     args[i].l = l; | ||||
|     args[i].id = i; | ||||
|     args[i].thread_count = thread_count; | ||||
| 
 | ||||
|     pthread_t thread; | ||||
|     pthread_create(&thread, NULL, (void *(*)(void *))lnm_loop_run_thread, | ||||
|                    &args[i]); | ||||
|   } | ||||
| 
 | ||||
|   args[0].l = l; | ||||
|   args[0].id = 0; | ||||
|   args[0].thread_count = thread_count; | ||||
| 
 | ||||
|   lnm_loop_run_thread(&args[0]); | ||||
| 
 | ||||
|   return lnm_err_ok; | ||||
| } | ||||
|  | @ -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); | ||||
| } | ||||
|  | @ -1,77 +0,0 @@ | |||
| #include <errno.h> | ||||
| #include <string.h> | ||||
| #include <unistd.h> | ||||
| 
 | ||||
| #include "lnm/loop.h" | ||||
| #include "lnm/loop_internal.h" | ||||
| 
 | ||||
| void lnm_loop_conn_io_req(lnm_loop *l, lnm_loop_conn *conn) { | ||||
|   do { | ||||
|     // Move remaining data to front of buffer
 | ||||
|     memmove(conn->r.buf, &conn->r.buf[conn->r.read], | ||||
|             conn->r.size - conn->r.read); | ||||
|     conn->r.size -= conn->r.read; | ||||
|     conn->r.read = 0; | ||||
| 
 | ||||
|     ssize_t res; | ||||
|     size_t cap = LNM_LOOP_BUF_SIZE - conn->r.size; | ||||
| 
 | ||||
|     do { | ||||
|       res = read(conn->fd, &conn->r.buf[conn->r.size], cap); | ||||
|     } while (res < 0 && errno == EINTR); | ||||
| 
 | ||||
|     // Read can't be performed without blocking; we come back later
 | ||||
|     if (res < 0 && errno == EAGAIN) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (res <= 0) { | ||||
|       conn->state = lnm_loop_state_end; | ||||
| 
 | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     conn->r.size += res; | ||||
|     l->data_read(conn); | ||||
|   } while (conn->state == lnm_loop_state_req); | ||||
| } | ||||
| 
 | ||||
| void lnm_loop_conn_io_res(lnm_loop *l, lnm_loop_conn *conn) { | ||||
|   do { | ||||
|     l->data_write(conn); | ||||
| 
 | ||||
|     ssize_t res; | ||||
| 
 | ||||
|     do { | ||||
|       res = write(conn->fd, conn->w.buf, conn->w.size); | ||||
|     } while (res < 0 && errno == EINTR); | ||||
| 
 | ||||
|     // Write can't be performed without blocking; we come back later
 | ||||
|     if (res < 0 && errno == EAGAIN) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if (res < 0) { | ||||
|       conn->state = lnm_loop_state_end; | ||||
| 
 | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     // Move remaining data to front of buffer. Doing this here gives the data
 | ||||
|     // writer function more space to work with
 | ||||
|     memmove(conn->w.buf, &conn->w.buf[res], conn->w.size - res); | ||||
|     conn->w.size -= res; | ||||
|   } while (conn->state == lnm_loop_state_res); | ||||
| } | ||||
| 
 | ||||
| void lnm_loop_conn_io(lnm_loop *l, lnm_loop_conn *conn) { | ||||
|   switch (conn->state) { | ||||
|   case lnm_loop_state_req: | ||||
|     lnm_loop_conn_io_req(l, conn); | ||||
|     break; | ||||
|   case lnm_loop_state_res: | ||||
|     lnm_loop_conn_io_res(l, conn); | ||||
|     break; | ||||
|   default:; | ||||
|   } | ||||
| } | ||||
|  | @ -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 <assert.h> | ||||
| #include <stddef.h> | ||||
| #include <string.h> | ||||
| #ifdef __SSE4_2__ | ||||
| #ifdef _MSC_VER | ||||
| #include <nmmintrin.h> | ||||
| #else | ||||
| #include <x86intrin.h> | ||||
| #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.<two chars>] 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:]<other char> 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 | ||||
		Loading…
	
		Reference in New Issue