diff --git a/lnm/Makefile b/lnm/Makefile index 78e1e0e..7371c9f 100644 --- a/lnm/Makefile +++ b/lnm/Makefile @@ -6,7 +6,7 @@ LIB := $(BUILD_DIR)/$(LIB_FILENAME) SRCS != find '$(SRC_DIR)' -iname '*.c' -SRCS_H != find $(INC_DIRS) -iname '*.h' +SRCS_H != find include -iname '*.h' SRCS_H_INTERNAL != find $(SRC_DIR) -iname '*.h' SRCS_TEST != find '$(TEST_DIR)' -iname '*.c' SRCS_THIRDPARTY != find '$(THIRDPARTY_DIR)/src' -iname '*.c' @@ -91,11 +91,11 @@ $(BUILD_DIR)/$(EXAMPLE_DIR)/%.c.o: $(EXAMPLE_DIR)/%.c # =====MAINTENANCE===== .PHONY: lint lint: - clang-format -n --Werror $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) $(SRCS_EXAMPLE) + clang-format -n --Werror $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) .PHONY: fmt fmt: - clang-format -i $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) $(SRCS_EXAMPLE) + clang-format -i $(SRCS) $(SRCS_H) $(SRCS_H_INTERNAL) .PHONY: check check: diff --git a/lnm/include/lnm/common.h b/lnm/include/lnm/common.h index 1500d73..381b4c9 100644 --- a/lnm/include/lnm/common.h +++ b/lnm/include/lnm/common.h @@ -23,6 +23,7 @@ typedef enum { lnm_err_failed_network, lnm_err_failed_poll, lnm_err_not_setup, + lnm_err_bad_regex } lnm_err; #endif diff --git a/lnm/include/lnm/http/consts.h b/lnm/include/lnm/http/consts.h index 4b6bc92..37f3239 100644 --- a/lnm/include/lnm/http/consts.h +++ b/lnm/include/lnm/http/consts.h @@ -12,7 +12,7 @@ typedef enum lnm_http_method { http_method_put, http_method_patch, http_method_delete -} http_method; +} lnm_http_method; extern const char *lnm_http_status_names[][32]; diff --git a/lnm/include/lnm/http/loop.h b/lnm/include/lnm/http/loop.h new file mode 100644 index 0000000..f84a1ea --- /dev/null +++ b/lnm/include/lnm/http/loop.h @@ -0,0 +1,75 @@ +#ifndef LNM_HTTP_LOOP +#define LNM_HTTP_LOOP + +#include "lnm/common.h" + +#define LNM_HTTP_MAX_REQ_HEADERS 32 + +typedef struct lnm_loop lnm_http_loop; + +typedef struct lnm_conn lnm_http_conn; + +typedef struct lnm_http_step lnm_http_step; + +typedef struct lnm_http_route lnm_http_route; + +typedef lnm_err (*lnm_http_step_fn)(lnm_http_conn *conn); + +/** + * Initialize a new `lnm_http_loop`. + * + * @param out where to store pointer to new `lnm_http_loop` + */ +lnm_err lnm_http_loop_init(lnm_http_loop **out); + +/** + * Append the given step fn to the step. + * + * @param out where to store pointer to new `lnm_http_step` + * @param step step to append new step to + * @param fn step funcitonn + */ +lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step *step, + lnm_http_step_fn fn); + +/** + * Initialize a new route of type literal. + * + * @param out where to store pointer to new `lnm_http_route` + * @param path literal path to match + * @param step step to process request with + */ +lnm_err lnm_http_route_init_literal(lnm_http_route **out, const char *path, + lnm_http_step *step); + +/** + * Initialize a new route of type regex. + * + * @param out where to store pointer to new `lnm_http_route` + * @param pattern regex pattern + * @param regex_group_count how many regex groups are contained in the pattern + * @param step step to process request with + */ +lnm_err lnm_http_route_init_regex(lnm_http_route **out, const char *pattern, + int regex_group_count, lnm_http_step *step); + +/** + * Add a new route to the HTTP route. + * + * @param hl HTTP loop to modify + * @param route route to add + */ +void lnm_http_loop_route_add(lnm_http_loop *hl, lnm_http_route *route); + +/** + * Represents what state an HTTP loop request is currently in. + */ +typedef enum lnm_http_loop_state { + lnm_http_loop_state_parse_req = 0, +} lnm_http_loop_state; + +typedef struct lnm_http_loop_ctx { + lnm_http_loop_state state; +} lnm_http_loop_ctx; + +#endif diff --git a/lnm/include/lnm/http/req.h b/lnm/include/lnm/http/req.h new file mode 100644 index 0000000..4741f4d --- /dev/null +++ b/lnm/include/lnm/http/req.h @@ -0,0 +1,48 @@ +#ifndef LNM_HTTP_REQ +#define LNM_HTTP_REQ + +#include + +#include "picohttpparser.h" + +#include "lnm/http/consts.h" +#include "lnm/http/loop.h" + +/** + * Represents the parsed HTTP request + */ +typedef struct lnm_http_req { + size_t len; + int minor_version; + lnm_http_method method; + struct { + const char *s; + size_t len; + } path; + struct { + const char *s; + size_t len; + } query; + struct { + struct phr_header arr[LNM_HTTP_MAX_REQ_HEADERS]; + size_t len; + } headers; +} lnm_http_req; + +typedef enum lnm_http_parse_err { + lnm_http_parse_err_ok = 0, + lnm_http_parse_err_incomplete, + lnm_http_parse_err_invalid, + lnm_http_parse_err_unknown_method, +} lnm_http_parse_err; + +/** + * Try to parse the given buffer into an HTTP request. + * + * @param req request to store parsed data in + * @param buf buffer to parse; might be modified + * @param len length of buf + */ +lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, size_t len); + +#endif diff --git a/lnm/include/lnm/loop.h b/lnm/include/lnm/loop.h index a3625c6..a84055a 100644 --- a/lnm/include/lnm/loop.h +++ b/lnm/include/lnm/loop.h @@ -30,7 +30,7 @@ typedef struct { } w; } lnm_loop_conn; -typedef struct { +typedef struct lnm_loop { int listen_fd; struct { lnm_loop_conn **arr; diff --git a/lnm/src/_include/lnm/http/loop_internal.h b/lnm/src/_include/lnm/http/loop_internal.h new file mode 100644 index 0000000..7410ade --- /dev/null +++ b/lnm/src/_include/lnm/http/loop_internal.h @@ -0,0 +1,43 @@ +#ifndef LNM_HTTP_LOOP_INTERNAL +#define LNM_HTTP_LOOP_INTERNAL + +#include + +#include "lnm/http/loop.h" + +typedef struct lnm_http_step { + lnm_http_step_fn fn; + struct lnm_http_step *next; +} lnm_http_step; + +typedef enum lnm_http_route_type { + lnm_http_route_type_literal = 0, + lnm_http_route_type_regex, +} lnm_http_route_type; + +typedef struct lnm_http_route { + union { + regex_t *regex; + const char *s; + } route; + lnm_http_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); + +#endif diff --git a/lnm/src/http/lnm_http_loop.c b/lnm/src/http/lnm_http_loop.c new file mode 100644 index 0000000..f082fe7 --- /dev/null +++ b/lnm/src/http/lnm_http_loop.c @@ -0,0 +1,86 @@ +#include + +#include "lnm/common.h" +#include "lnm/http/loop.h" +#include "lnm/http/loop_internal.h" +#include "lnm/loop_internal.h" + +lnm_err lnm_http_loop_init(lnm_http_loop **out) { + lnm_http_loop *hl = calloc(1, sizeof(lnm_http_loop)); + + if (hl == NULL) { + return lnm_err_failed_alloc; + } + + *out = hl; + + return lnm_err_ok; +} + +lnm_err lnm_http_step_init(lnm_http_step **out, lnm_http_step_fn fn) { + lnm_http_step *step = calloc(1, sizeof(lnm_http_step)); + + if (step == NULL) { + return lnm_err_failed_alloc; + } + + step->fn = fn; + *out = step; + + return lnm_err_ok; +} + +lnm_err lnm_http_step_append(lnm_http_step **out, lnm_http_step *step, + lnm_http_step_fn fn) { + LNM_RES(lnm_http_step_init(out, fn)); + + step->next = *out; + + return lnm_err_ok; +} + +lnm_err lnm_http_route_init(lnm_http_route **out) { + lnm_http_route *route = calloc(1, sizeof(lnm_http_route)); + + if (route == NULL) { + return lnm_err_failed_alloc; + } + + *out = route; + + return lnm_err_ok; +} + +lnm_err lnm_http_route_init_literal(lnm_http_route **out, const char *path, + lnm_http_step *step) { + LNM_RES(lnm_http_route_init(out)); + + (*out)->type = lnm_http_route_type_literal; + (*out)->route.s = path; + (*out)->step = step; + + return lnm_err_ok; +} + +lnm_err lnm_http_route_init_regex(lnm_http_route **out, const char *pattern, + int regex_group_count, lnm_http_step *step) { + regex_t *regex = calloc(1, sizeof(regex_t)); + + if (regex == NULL) { + return lnm_err_failed_alloc; + } + + if (regcomp(regex, pattern, REG_EXTENDED) != 0) { + free(regex); + return lnm_err_bad_regex; + } + + LNM_RES2(lnm_http_route_init(out), free(regex)); + + (*out)->type = lnm_http_route_type_regex; + (*out)->route.regex = regex; + (*out)->regex_group_count = regex_group_count; + (*out)->step = step; + + return lnm_err_ok; +} diff --git a/lnm/src/http/req.c b/lnm/src/http/req.c new file mode 100644 index 0000000..2327ce6 --- /dev/null +++ b/lnm/src/http/req.c @@ -0,0 +1,58 @@ +#include +#include + +#include "lnm/http/consts.h" +#include "lnm/http/loop.h" +#include "lnm/http/req.h" + +lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, + size_t len) { + const char *method; + char *path; + size_t method_len, path_len; + + req->headers.len = LNM_HTTP_MAX_REQ_HEADERS; + + int req_len = phr_parse_request( + buf, len, &method, &method_len, (const char **)&path, &path_len, + &req->minor_version, req->headers.arr, &req->headers.len, 0); + + if (req_len == -1) { + return lnm_http_parse_err_invalid; + } else if (req_len == -2) { + return lnm_http_parse_err_incomplete; + } + + bool known_method = false; + + for (size_t i = 0; i < lnm_http_method_names_len && !known_method; i++) { + if (strncmp(method, lnm_http_method_names[i], method_len) == 0) { + req->method = i; + known_method = true; + } + } + + if (!known_method) { + return lnm_http_parse_err_unknown_method; + } + + char *question_mark = strchr(path, '?'); + + // Only store query if the path doesn't simply end with a question mark + if ((question_mark != NULL) && (path_len - (question_mark + 1 - path) > 0)) { + req->query.s = question_mark + 1; + req->query.len = path_len - (question_mark + 1 - path); + + path_len = question_mark - path; + } + + // All parsed strings should be null-terminated. This character is either a + // newline (if at the end of the path), or a question mark (if a query is + // present). + path[path_len] = '\0'; + req->path.len = path_len; + req->path.s = path; + req->len = req_len; + + return lnm_http_parse_err_ok; +}