From 3c1e62330c7f0d65c11a8e1582a3fd597ed076f4 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 2 Dec 2023 13:24:22 +0100 Subject: [PATCH 1/2] feat(lnm): implement context resetting --- lnm/include/lnm/http/req.h | 8 +++++++ lnm/include/lnm/http/res.h | 8 +++++++ lnm/src/http/lnm_http_loop_ctx.c | 10 ++++++-- lnm/src/http/lnm_http_loop_process.c | 17 ++++++++++---- lnm/src/http/lnm_http_req.c | 4 ++++ lnm/src/http/lnm_http_res.c | 35 ++++++++++++++++++++++++++++ 6 files changed, 76 insertions(+), 6 deletions(-) diff --git a/lnm/include/lnm/http/req.h b/lnm/include/lnm/http/req.h index def3ad7..1eddb26 100644 --- a/lnm/include/lnm/http/req.h +++ b/lnm/include/lnm/http/req.h @@ -49,4 +49,12 @@ typedef enum lnm_http_parse_err { */ lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, size_t len); +/** + * Reset the given request object, free'ing all its relevant parts and allowing + * it to be reused as a new object. + * + * @param req object to reset + */ +void lnm_http_req_reset(lnm_http_req *req); + #endif diff --git a/lnm/include/lnm/http/res.h b/lnm/include/lnm/http/res.h index 8ea7636..4364597 100644 --- a/lnm/include/lnm/http/res.h +++ b/lnm/include/lnm/http/res.h @@ -108,4 +108,12 @@ void lnm_http_res_body_set_buf(lnm_http_res *res, char *buf, size_t len, */ void lnm_http_res_body_set_fn(lnm_http_res *res, data_fn fn, size_t len); +/** + * Reset the given response object, properly free'ing any allocated buffers, + * allowing it to be reused for later connections. + * + * @param res res to reset + */ +void lnm_http_res_reset(lnm_http_res *res); + #endif diff --git a/lnm/src/http/lnm_http_loop_ctx.c b/lnm/src/http/lnm_http_loop_ctx.c index 64ac8eb..87741e2 100644 --- a/lnm/src/http/lnm_http_loop_ctx.c +++ b/lnm/src/http/lnm_http_loop_ctx.c @@ -38,12 +38,18 @@ lnm_err lnm_http_loop_ctx_init(lnm_http_loop_ctx **out, void lnm_http_loop_ctx_reset(lnm_http_loop_ctx *ctx) { ctx->g->ctx_reset(ctx->c); - // TODO actual reset stuff + + lnm_http_req_reset(&ctx->req); + lnm_http_res_reset(&ctx->res); + + ctx->route = NULL; + ctx->cur_step = NULL; } void lnm_http_loop_ctx_free(lnm_http_loop_ctx *ctx) { + lnm_http_loop_ctx_reset(ctx); + ctx->g->ctx_free(ctx->c); - // TODO actual free stuff free(ctx); } diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index c8aed94..5952538 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -160,7 +160,7 @@ void lnm_http_loop_process_write_headers(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_res *res = &ctx->res; - lnm_http_res_header *header = res->headers.current; + lnm_http_res_header *header; // Loop as long as we can still write new data and have headers to write while ((conn->w.size < LNM_LOOP_BUF_SIZE) && @@ -212,7 +212,7 @@ void lnm_http_loop_process_write_body(lnm_http_conn *conn) { case lnm_http_res_body_type_file: written = fread(&conn->w.buf[conn->w.size], 1, to_write, res->body.data.f); - if ((written == 0) && (!ferror(res->body.data.f))) { + if ((written == 0) && (ferror(res->body.data.f) != 0)) { ctx->state = lnm_http_loop_state_finish; } break; @@ -232,7 +232,16 @@ void lnm_http_loop_process_write_body(lnm_http_conn *conn) { } } -void lnm_http_loop_process_finish(lnm_http_conn *conn) {} +void lnm_http_loop_process_finish(lnm_http_conn *conn) { + // First we ensure the write buffer is fully flushed + if (conn->w.size > 0) { + return; + } + + lnm_http_loop_ctx_reset(conn->ctx); + + conn->state = lnm_loop_state_req; +} void (*process_fns[])(lnm_http_conn *conn) = { lnm_http_loop_process_parse_req, @@ -246,7 +255,7 @@ void (*process_fns[])(lnm_http_conn *conn) = { }; void lnm_http_loop_process(lnm_http_conn *conn) { - lnm_http_loop_ctx *ctx = conn->ctx; + const lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_loop_state http_loop_state; lnm_loop_state loop_state = conn->state; diff --git a/lnm/src/http/lnm_http_req.c b/lnm/src/http/lnm_http_req.c index 2327ce6..33c522c 100644 --- a/lnm/src/http/lnm_http_req.c +++ b/lnm/src/http/lnm_http_req.c @@ -56,3 +56,7 @@ lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, return lnm_http_parse_err_ok; } + +void lnm_http_req_reset(lnm_http_req *req) { + memset(req, 0, sizeof(lnm_http_req)); +} diff --git a/lnm/src/http/lnm_http_res.c b/lnm/src/http/lnm_http_res.c index adf65f0..3f2b0c0 100644 --- a/lnm/src/http/lnm_http_res.c +++ b/lnm/src/http/lnm_http_res.c @@ -62,3 +62,38 @@ void lnm_http_res_body_set_fn(lnm_http_res *res, data_fn fn, size_t len) { res->body.len = len; res->body.type = lnm_http_res_body_type_fn; } + +void lnm_http_res_reset(lnm_http_res *res) { + lnm_http_res_header *header = res->headers.head; + + while (header != NULL) { + lnm_http_res_header *next = header->next; + + if (header->name.owned) { + free(header->name.s); + } + + if (header->value.owned) { + free(header->value.s); + } + + free(header); + + header = next; + } + + if (res->body.owned) { + switch (res->body.type) { + case lnm_http_res_body_type_file: + fclose(res->body.data.f); + break; + case lnm_http_res_body_type_buf: + free(res->body.data.buf); + break; + case lnm_http_res_body_type_fn: + break; + } + } + + memset(res, 0, sizeof(lnm_http_res)); +} From 13ccfef94d52f9fca90aaa6a4ae64c7c135c34d9 Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Sat, 2 Dec 2023 16:28:52 +0100 Subject: [PATCH 2/2] feat(lnm): implement content-length header parsing --- lnm/include/lnm/common.h | 46 ++++++++++++++++++++++++++-- lnm/include/lnm/http/req.h | 25 +++++++++++++++ lnm/src/http/lnm_http_loop_process.c | 12 +++++++- lnm/src/http/lnm_http_req.c | 25 +++++++++++++++ lnm/src/lnm_utils.c | 45 +++++++++++++++++++++++++++ 5 files changed, 150 insertions(+), 3 deletions(-) create mode 100644 lnm/src/lnm_utils.c diff --git a/lnm/include/lnm/common.h b/lnm/include/lnm/common.h index 4dc789d..9c95d52 100644 --- a/lnm/include/lnm/common.h +++ b/lnm/include/lnm/common.h @@ -1,6 +1,10 @@ #ifndef LNM_COMMON #define LNM_COMMON +#include +#include +#include + #define LNM_RES(x) \ { \ lnm_err res = x; \ @@ -20,13 +24,14 @@ #define LNM_MIN(x, y) ((x) < (y) ? (x) : (y)) #define LNM_MAX(x, y) ((x) > (y) ? (x) : (y)) -typedef enum { +typedef enum lnm_err { lnm_err_ok = 0, lnm_err_failed_alloc, lnm_err_failed_network, lnm_err_failed_poll, lnm_err_not_setup, - lnm_err_bad_regex + lnm_err_bad_regex, + lnm_err_not_found, } lnm_err; typedef struct lnm_loop lnm_http_loop; @@ -37,4 +42,41 @@ typedef struct lnm_http_step lnm_http_step; typedef struct lnm_http_route lnm_http_route; +/** + * Returns whether the two strings are equal. + * + * @param s1 first string to compare + * @param s1_len length of `s1` + * @param s2 second string to compare + * @param s2_len length of `s2` + */ +bool lnm_strneq(const char *s1, size_t s1_len, const char *s2, size_t s2_len); + +/** + * Returns whether the two strings are equal, ignoring capitalisation. + * + * @param s1 first string to compare + * @param s1_len length of `s1` + * @param s2 second string to compare + * @param s2_len length of `s2` + */ +bool lnm_strnieq(const char *s1, size_t s1_len, const char *s2, size_t s2_len); + +/** + * Calculate integer exponentation. + * + * @param base + * @param power + */ +uint64_t lnm_ipow(uint64_t base, uint64_t power); + +/** + * Parse the given string into a number. + * + * @param s string to parse + * @param len length of s + * @return the parsed number, or 0 if the number is invalid + */ +uint64_t lnm_atoi(const char *s, size_t len); + #endif diff --git a/lnm/include/lnm/http/req.h b/lnm/include/lnm/http/req.h index 1eddb26..e43063c 100644 --- a/lnm/include/lnm/http/req.h +++ b/lnm/include/lnm/http/req.h @@ -2,10 +2,12 @@ #define LNM_HTTP_REQ #include +#include #include #include "picohttpparser.h" +#include "lnm/common.h" #include "lnm/http/consts.h" #define LNM_HTTP_MAX_REQ_HEADERS 32 @@ -31,6 +33,7 @@ typedef struct lnm_http_req { struct phr_header arr[LNM_HTTP_MAX_REQ_HEADERS]; size_t len; } headers; + uint64_t content_length; } lnm_http_req; typedef enum lnm_http_parse_err { @@ -57,4 +60,26 @@ lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, size_t len); */ void lnm_http_req_reset(lnm_http_req *req); +/** + * Retrieve a specific header from the request. + * + * @param out where to write pointer to header value + * @param out_len where to store length of out value + * @param req request to look for header in + * @param type type of header to look for + */ +lnm_err lnm_http_req_header_get(const char **out, size_t *out_len, + lnm_http_req *req, lnm_http_header type); + +/** + * Retrieve a specific header from the request by specifying its name. + * + * @param out where to write pointer to header value + * @param out_len where to store length of out value + * @param req request to look for header in + * @param name name of the header; matches case-insensitive + */ +lnm_err lnm_http_req_header_get_s(const char **out, size_t *out_len, + lnm_http_req *req, const char *name); + #endif diff --git a/lnm/src/http/lnm_http_loop_process.c b/lnm/src/http/lnm_http_loop_process.c index 5952538..9090df2 100644 --- a/lnm/src/http/lnm_http_loop_process.c +++ b/lnm/src/http/lnm_http_loop_process.c @@ -91,8 +91,14 @@ void lnm_http_loop_process_route(lnm_http_conn *conn) { void lnm_http_loop_process_parse_headers(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; + lnm_http_req *req = &ctx->req; - // TODO + const char *value; + size_t value_len; + if (lnm_http_req_header_get(&value, &value_len, req, + lnm_http_header_content_length) == lnm_err_ok) { + req->content_length = lnm_atoi(value, value_len); + } ctx->state = lnm_http_loop_state_steps; } @@ -134,6 +140,10 @@ void lnm_http_loop_process_write_status_line(lnm_http_conn *conn) { lnm_http_loop_ctx *ctx = conn->ctx; lnm_http_res *res = &ctx->res; + if (res->status == 0) { + res->status = lnm_http_status_ok; + } + const char *response_type_name = lnm_http_status_names[res->status / 100 - 1][res->status % 100]; diff --git a/lnm/src/http/lnm_http_req.c b/lnm/src/http/lnm_http_req.c index 33c522c..1588bcd 100644 --- a/lnm/src/http/lnm_http_req.c +++ b/lnm/src/http/lnm_http_req.c @@ -1,6 +1,7 @@ #include #include +#include "lnm/common.h" #include "lnm/http/consts.h" #include "lnm/http/loop.h" #include "lnm/http/req.h" @@ -60,3 +61,27 @@ lnm_http_parse_err lnm_http_req_parse(lnm_http_req *req, char *buf, void lnm_http_req_reset(lnm_http_req *req) { memset(req, 0, sizeof(lnm_http_req)); } + +lnm_err lnm_http_req_header_get(const char **out, size_t *out_len, + lnm_http_req *req, lnm_http_header type) { + return lnm_http_req_header_get_s(out, out_len, req, + lnm_http_header_names[type]); +} + +lnm_err lnm_http_req_header_get_s(const char **out, size_t *out_len, + lnm_http_req *req, const char *name) { + size_t name_len = strlen(name); + + for (size_t i = 0; i < req->headers.len; i++) { + const struct phr_header *header = &req->headers.arr[i]; + + if (lnm_strnieq(header->name, header->name_len, name, name_len)) { + *out = header->value; + *out_len = header->value_len; + + return lnm_err_ok; + } + } + + return lnm_err_not_found; +} diff --git a/lnm/src/lnm_utils.c b/lnm/src/lnm_utils.c new file mode 100644 index 0000000..ce60803 --- /dev/null +++ b/lnm/src/lnm_utils.c @@ -0,0 +1,45 @@ +#include +#include + +#include "lnm/common.h" + +bool lnm_strneq(const char *s1, size_t s1_len, const char *s2, size_t s2_len) { + return (s1_len == s2_len) && (memcmp(s1, s2, s1_len) == 0); +} + +bool lnm_strnieq(const char *s1, size_t s1_len, const char *s2, size_t s2_len) { + bool equal = s1_len == s2_len; + + for (size_t i = 0; i < s1_len && equal; i++) { + equal = s1[i] == s2[i] || + (('a' <= s1[i]) && (s1[i] <= 'z') && (s1[i] - 32 == s2[i])); + } + + return equal; +} + +uint64_t lnm_ipow(uint64_t base, uint64_t power) { + uint64_t res = 1; + + while (power > 0) { + res *= base; + power--; + } + + return res; +} + +uint64_t lnm_atoi(const char *s, size_t len) { + uint64_t res = 0; + + for (size_t i = 0; i < len; i++) { + if (s[i] < '0' || '9' < s[i]) { + return 0; + } + + uint64_t val = s[i] - '0'; + res += val * lnm_ipow(10, (len - 1) - i); + } + + return res; +}