From 16103f9b24970e52020f21e9ae4c0fc5e143f87f Mon Sep 17 00:00:00 2001 From: Chewing_Bever Date: Mon, 29 May 2023 23:26:15 +0200 Subject: [PATCH] feat: add step to receive request body in buffer --- Makefile | 2 +- include/http.h | 4 ++ include/http_loop.h | 8 ++++ include/lander.h | 4 +- src/http_loop/http_loop_ctx.c | 5 +++ src/http_loop/http_loop_req.c | 1 + src/http_loop/http_loop_res.c | 2 - src/http_loop/http_loop_tools.c | 66 +++++++++++++++++++++++++++++++++ src/lander/lander.c | 21 +++++++---- src/lander/lander_post.c | 12 ++++++ 10 files changed, 113 insertions(+), 12 deletions(-) create mode 100644 src/lander/lander_post.c diff --git a/Makefile b/Makefile index 9e98f19..45f72f7 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ objs: $(OBJS) .PHONY: bin bin: $(BIN) $(BIN): $(OBJS) - $(CC) $(INTERNALCFLAGS) $(LDFLAGS) -o $@ $^ + $(CC) $(INTERNALCFLAGS) $(LDFLAGS) -lm -o $@ $^ $(BUILD_DIR)/$(SRC_DIR)/%.c.o: $(SRC_DIR)/%.c mkdir -p $(dir $@) diff --git a/include/http.h b/include/http.h index d9b9676..b3531ce 100644 --- a/include/http.h +++ b/include/http.h @@ -35,8 +35,12 @@ typedef struct http_request { size_t path_len; const char *query; size_t query_len; + char *body; + size_t body_len; + size_t body_received; regmatch_t regex_groups[HTTP_MAX_REGEX_GROUPS]; struct phr_header headers[HTTP_MAX_ALLOWED_HEADERS]; + size_t num_headers; } http_request; typedef enum http_parse_error { diff --git a/include/http_loop.h b/include/http_loop.h index e2872c1..d215ee6 100644 --- a/include/http_loop.h +++ b/include/http_loop.h @@ -7,6 +7,9 @@ #include "http.h" #include "trie.h" +#define MIN(x, y) (((x) < (y)) ? (x) : (y)) +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) + typedef enum http_route_type { http_route_literal = 0, http_route_regex = 1, @@ -102,6 +105,11 @@ void http_loop_res_set_body_file(http_loop_ctx *ctx, const char *filename); void http_loop_res_add_header(http_loop_ctx *ctx, http_header type, const char *value, bool owned); +/* + * Request step that consumes the request body and stores it in a buffer + */ +bool http_loop_step_body_to_buf(event_loop_conn *conn); + /** * Initialize a new http loop */ diff --git a/include/lander.h b/include/lander.h index c990cef..8e2f4db 100644 --- a/include/lander.h +++ b/include/lander.h @@ -3,6 +3,8 @@ #include "http_loop.h" -extern http_route lander_routes[2]; +extern http_route lander_routes[3]; + +bool lander_post_redirect(event_loop_conn *conn); #endif diff --git a/src/http_loop/http_loop_ctx.c b/src/http_loop/http_loop_ctx.c index 7029654..6945ff8 100644 --- a/src/http_loop/http_loop_ctx.c +++ b/src/http_loop/http_loop_ctx.c @@ -26,6 +26,11 @@ void http_loop_ctx_reset(http_loop_ctx *ctx) { ctx->route = NULL; ctx->current_step = 0; + if (ctx->req.body != NULL) { + free((void *)ctx->req.body); + ctx->req.body = NULL; + } + if (ctx->res.head != NULL) { free((void *)ctx->res.head); ctx->res.head = NULL; diff --git a/src/http_loop/http_loop_req.c b/src/http_loop/http_loop_req.c index 5712197..154d134 100644 --- a/src/http_loop/http_loop_req.c +++ b/src/http_loop/http_loop_req.c @@ -27,6 +27,7 @@ http_parse_error http_loop_parse_request(event_loop_conn *conn) { return http_parse_error_incomplete; } + req->num_headers = num_headers; req->len = res; // Try to parse the method type diff --git a/src/http_loop/http_loop_res.c b/src/http_loop/http_loop_res.c index 196f653..98563a2 100644 --- a/src/http_loop/http_loop_res.c +++ b/src/http_loop/http_loop_res.c @@ -1,8 +1,6 @@ #include "http_loop.h" #include "log.h" -#define MIN(x, y) (((x) < (y)) ? (x) : (y)) - static const char *http_response_format = "HTTP/1.1 %i %s\n" "Server: lander/" LANDER_VERSION "\n" "Content-Length: %lu\n"; diff --git a/src/http_loop/http_loop_tools.c b/src/http_loop/http_loop_tools.c index 565f6d3..c089f7d 100644 --- a/src/http_loop/http_loop_tools.c +++ b/src/http_loop/http_loop_tools.c @@ -1,3 +1,4 @@ +#include #include #include @@ -31,3 +32,68 @@ void http_loop_res_add_header(http_loop_ctx *ctx, http_header type, ctx->res.header_count++; } + +/* + * Converts a string to a number, returning true if the string contained a valid + * positive number. + */ +static bool string_to_num(size_t *res, const char *s, size_t len) { + *res = 0; + + for (size_t i = 0; i < len; i++) { + int val = s[i] - '0'; + + if (val < 0 || val > 9) { + return false; + } + + *res += val * (int)pow(10, (len - 1) - i); + } + + return true; +} + +bool http_loop_step_body_to_buf(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; + + if (ctx->req.body_len == 0) { + for (size_t i = 0; i < ctx->req.num_headers; i++) { + struct phr_header *header = &ctx->req.headers[i]; + + if (strncmp(header->name, "Content-Length", header->name_len) == 0) { + // If the content length header is present but contains an invalid + // number, we return a bad request error + if (!string_to_num(&ctx->req.body_len, header->value, + header->value_len)) { + ctx->res.status = http_bad_request; + conn->state = event_loop_conn_state_res; + + return true; + } + // The content length was actually 0, so we can instantly return here + else if (ctx->req.body_len == 0) { + return true; + } + } + } + + // A zero here means there's no content length header + if (ctx->req.body_len == 0) { + ctx->res.status = http_length_required; + conn->state = event_loop_conn_state_res; + + return true; + } + + ctx->req.body = malloc(ctx->req.body_len * sizeof(uint8_t)); + ctx->req.body_received = 0; + } + + size_t bytes_to_copy = MIN(conn->rbuf_size - conn->rbuf_read, + ctx->req.body_len - ctx->req.body_received); + memcpy(&ctx->req.body[ctx->req.body_received], &conn->rbuf[conn->rbuf_read], + bytes_to_copy); + ctx->req.body_received += bytes_to_copy; + + return ctx->req.body_received == ctx->req.body_len; +} diff --git a/src/lander/lander.c b/src/lander/lander.c index 422651d..af30e71 100644 --- a/src/lander/lander.c +++ b/src/lander/lander.c @@ -47,11 +47,16 @@ bool lander_get_entry(event_loop_conn *conn) { return true; } -http_route lander_routes[] = {{.type = http_route_literal, - .method = http_get, - .path = "/", - .steps = {lander_get_index, NULL}}, - {.type = http_route_regex, - .method = http_get, - .path = "^/\\([^/]\\+\\)$", - .steps = {lander_get_entry, NULL}}}; +http_route lander_routes[] = { + {.type = http_route_literal, + .method = http_get, + .path = "/", + .steps = {lander_get_index, NULL}}, + {.type = http_route_regex, + .method = http_get, + .path = "^/\\([^/]\\+\\)$", + .steps = {lander_get_entry, NULL}}, + {.type = http_route_literal, + .method = http_post, + .path = "/s/", + .steps = {http_loop_step_body_to_buf, lander_post_redirect, NULL}}}; diff --git a/src/lander/lander_post.c b/src/lander/lander_post.c new file mode 100644 index 0000000..33aa720 --- /dev/null +++ b/src/lander/lander_post.c @@ -0,0 +1,12 @@ +#include "lander.h" +#include "log.h" + +bool lander_post_redirect(event_loop_conn *conn) { + http_loop_ctx *ctx = conn->ctx; + + info("%.*s", ctx->req.body_len, ctx->req.body); + + conn->state = event_loop_conn_state_res; + + return true; +}