From 65031ca3502e0c27780be847fd97c112546741a9 Mon Sep 17 00:00:00 2001 From: Xavier Del Campo Romero Date: Sat, 18 Nov 2023 00:56:04 +0100 Subject: [PATCH] Send HTTP headers to payload callback Even if libweb already parses some common headers, such as Content-Length, some users might find it interesting to inspect which headers were received from a request. Since HTTP/1.1 does not define a limit on the number of maximum headers a client can send, for security reasons a maximum value must be provided by the user. Any extra headers shall be then discarded by libweb. An example application showing this new feature is also provided. --- doc/man7/libweb_handler.7 | 8 ++-- doc/man7/libweb_http.7 | 19 +++++++- examples/CMakeLists.txt | 1 + examples/Makefile | 4 ++ examples/headers/CMakeLists.txt | 4 ++ examples/headers/Makefile | 26 +++++++++++ examples/headers/README.md | 16 +++++++ examples/headers/main.c | 81 +++++++++++++++++++++++++++++++++ handler.c | 3 +- http.c | 68 +++++++++++++++++++++++++-- include/libweb/handler.h | 1 + include/libweb/http.h | 14 ++++-- 12 files changed, 232 insertions(+), 13 deletions(-) create mode 100644 examples/headers/CMakeLists.txt create mode 100644 examples/headers/Makefile create mode 100644 examples/headers/README.md create mode 100644 examples/headers/main.c diff --git a/doc/man7/libweb_handler.7 b/doc/man7/libweb_handler.7 index 5c910e0..b9fd53f 100644 --- a/doc/man7/libweb_handler.7 +++ b/doc/man7/libweb_handler.7 @@ -1,4 +1,4 @@ -.TH LIBWEB_HANDLER 7 2023-09-15 0.1.0 "libweb Library Reference" +.TH LIBWEB_HANDLER 7 2023-11-18 0.2.0 "libweb Library Reference" .SH NAME libweb_handler \- libweb high-level website configuration @@ -69,15 +69,17 @@ struct handler_cfg const char *\fItmpdir\fP; int (*\fIlength\fP)(unsigned long long len, const struct http_cookie *c, struct http_response *r, void *user); void *\fIuser\fP; + size_t \fImax_headers\fP; }; .EE .in .PP .IR tmpdir , -.I length -and +.IR length , .I user +and +.I max_headers are passed directly to the .I struct http_cfg object used to initialize a diff --git a/doc/man7/libweb_http.7 b/doc/man7/libweb_http.7 index 329a616..93bb882 100644 --- a/doc/man7/libweb_http.7 +++ b/doc/man7/libweb_http.7 @@ -1,4 +1,4 @@ -.TH LIBWEB_HTTP 7 2023-09-15 0.1.0 "libweb Library Reference" +.TH LIBWEB_HTTP 7 2023-11-18 0.2.0 "libweb Library Reference" .SH NAME libweb_http \- libweb HTTP connection handling and utilities @@ -84,6 +84,7 @@ struct http_cfg int (*\fIlength\fP)(unsigned long long \fIlen\fP, const struct http_cookie *\fIc\fP, struct http_response *\fIr\fP, void *\fIuser\fP); const char *\fItmpdir\fP; void *\fIuser\fP; + size_t \fImax_headers\fP; }; .EE .in @@ -195,6 +196,15 @@ other function pointers defined by .I user can be a null pointer. +.I max_headers +refers to the maximum number of header fields that shall be passed to the +.IR "struct http_payload" +object passed to the function pointed to by +.IR payload . +Any extra headers sent by the client outside this maximum value shall be +silently ignored by +.IR libweb . + .SS HTTP payload When a client submits a request to the server, @@ -224,6 +234,8 @@ struct http_payload const struct http_arg *\fIargs\fP; size_t \fIn_args\fP; + const struct http_header *\fIheaders\fP; + size_t \fIn_headers\fP; }; .EE .in @@ -260,6 +272,11 @@ defines a list of key-value pairs containing URL parameters. Its length is defined by .IR n_args . +.I headers +defines a list of key-value pairs containing header fields. Its length +is defined by +.IR n_headers . + .SS HTTP POST payload As opposed to payload-less HTTP/1.1 operations, such as diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d9bf4cc..7353458 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,3 +1,4 @@ cmake_minimum_required(VERSION 3.13) +add_subdirectory(headers) add_subdirectory(hello) add_subdirectory(html) diff --git a/examples/Makefile b/examples/Makefile index b26a13a..672f209 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -1,6 +1,7 @@ .POSIX: all: \ + headers \ hello \ html @@ -10,6 +11,9 @@ clean: FORCE: +headers: FORCE + +cd headers && $(MAKE) + hello: FORCE +cd hello && $(MAKE) diff --git a/examples/headers/CMakeLists.txt b/examples/headers/CMakeLists.txt new file mode 100644 index 0000000..8c91fd7 --- /dev/null +++ b/examples/headers/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required(VERSION 3.13) +project(headers C) +add_executable(${PROJECT_NAME} main.c) +target_link_libraries(${PROJECT_NAME} PRIVATE web) diff --git a/examples/headers/Makefile b/examples/headers/Makefile new file mode 100644 index 0000000..4905fbd --- /dev/null +++ b/examples/headers/Makefile @@ -0,0 +1,26 @@ +.POSIX: + +PROJECT = headers +DEPS = \ + main.o +LIBWEB = ../../libweb.a +DYNSTR = ../../dynstr/libdynstr.a +CFLAGS = -I ../../include -I ../../dynstr/include +LIBWEB_FLAGS = -L ../../ -l web +DYNSTR_FLAGS = -L ../../dynstr -l dynstr + +all: $(PROJECT) + +clean: + rm -f $(DEPS) + +FORCE: + +$(PROJECT): $(DEPS) $(LIBWEB) $(DYNSTR) + $(CC) $(LDFLAGS) $(DEPS) $(LIBWEB_FLAGS) $(DYNSTR_FLAGS) -o $@ + +$(LIBWEB): FORCE + +cd ../../ && $(MAKE) + +$(DYNSTR): FORCE + +cd ../../dynstr && $(MAKE) diff --git a/examples/headers/README.md b/examples/headers/README.md new file mode 100644 index 0000000..01c243a --- /dev/null +++ b/examples/headers/README.md @@ -0,0 +1,16 @@ +# HTTP headers example + +This example shows a HTTP/1.1 server that listens to port `8080` and prints +the headers received from the client (up to a maximum of `max_headers`) to +standard output. + +## How to build + +If using `make(1)`, just run `make` from this directory. + +If using CMake, examples are built by default when configuring the project +from [the top-level `CMakeLists.txt`](../../CMakeLists.txt). + +## How to run + +Run the executable without any command line arguments. diff --git a/examples/headers/main.c b/examples/headers/main.c new file mode 100644 index 0000000..b465aa1 --- /dev/null +++ b/examples/headers/main.c @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include +#include + +static const size_t max_headers = 5; + +static int hello(const struct http_payload *const pl, + struct http_response *const r, void *const user) +{ + printf("Got %zu headers from the client (max %zu).\n", + pl->n_headers, max_headers); + + for (size_t i = 0; i < pl->n_headers; i++) + { + const struct http_header *const h = &pl->headers[i]; + + printf("%s: %s\n", h->header, h->value); + } + + *r = (const struct http_response) + { + .status = HTTP_STATUS_OK + }; + + return 0; +} + +static int on_length(const unsigned long long len, + const struct http_cookie *const c, struct http_response *const r, + void *const user) +{ + *r = (const struct http_response) + { + .status = HTTP_STATUS_FORBIDDEN + }; + + return 1; +} + +int main(int argc, char *argv[]) +{ + int ret = EXIT_FAILURE; + const short port = 8080; + const struct handler_cfg cfg = + { + .length = on_length, + .max_headers = max_headers + }; + + struct handler *const h = handler_alloc(&cfg); + static const char *const urls[] = {"/", "/index.html"}; + + if (!h) + { + fprintf(stderr, "%s: handler_alloc failed\n", __func__); + goto end; + } + + for (size_t i = 0; i < sizeof urls / sizeof *urls; i++) + if (handler_add(h, urls[i], HTTP_OP_GET, hello, NULL)) + { + fprintf(stderr, "%s: handler_add failed\n", __func__); + goto end; + } + + if (handler_listen(h, port)) + { + fprintf(stderr, "%s: handler_listen failed\n", __func__); + goto end; + } + + ret = EXIT_SUCCESS; + +end: + handler_free(h); + return ret; +} diff --git a/handler.c b/handler.c index 61a2806..f6e47a3 100644 --- a/handler.c +++ b/handler.c @@ -109,7 +109,8 @@ static struct client *find_or_alloc_client(struct handler *const h, .payload = on_payload, .length = on_length, .user = ret, - .tmpdir = h->cfg.tmpdir + .tmpdir = h->cfg.tmpdir, + .max_headers = h->cfg.max_headers }; *ret = (const struct client) diff --git a/http.c b/http.c index 48a5ab0..80086e1 100644 --- a/http.c +++ b/http.c @@ -88,7 +88,8 @@ struct http_ctx } u; struct http_arg *args; - size_t n_args; + size_t n_args, n_headers; + struct http_header *headers; } ctx; struct write_ctx @@ -475,6 +476,15 @@ static void ctx_free(struct ctx *const c) for (size_t i = 0; i < c->n_args; i++) arg_free(&c->args[i]); + for (size_t i = 0; i < c->n_headers; i++) + { + const struct http_header *const hdr = &c->headers[i]; + + free(hdr->header); + free(hdr->value); + } + + free(c->headers); free(c->args); *c = (const struct ctx){0}; } @@ -946,7 +956,9 @@ static struct http_payload ctx_to_payload(const struct ctx *const c) .op = c->op, .resource = c->resource, .args = c->args, - .n_args = c->n_args + .n_args = c->n_args, + .headers = c->headers, + .n_headers = c->n_headers }; } @@ -1020,6 +1032,56 @@ static int expect(struct http_ctx *const h, const char *const value) return 0; } +static int append_header(struct http_ctx *const h, const char *const line, + const size_t n, const char *const value) +{ + struct ctx *const c = &h->ctx; + + if (c->n_headers >= h->cfg.max_headers) + return 0; + + struct http_header *const headers = realloc(c->headers, + (c->n_headers + 1) * sizeof *headers); + char *headerdup = NULL, *valuedup = NULL; + int ret = -1; + + if (!headers) + { + fprintf(stderr, "%s: realloc(3): %s\n", __func__, strerror(errno)); + return -1; + } + + c->headers = headers; + + if (!(headerdup = strndup(line, n))) + { + fprintf(stderr, "%s: strndup(3): %s\n", __func__, strerror(errno)); + goto end; + } + else if (!(valuedup = strdup(value))) + { + fprintf(stderr, "%s: strdup(3): %s\n", __func__, strerror(errno)); + goto end; + } + + c->headers[c->n_headers++] = (const struct http_header) + { + .header = headerdup, + .value = valuedup + }; + + ret = 0; + +end: + if (ret) + { + free(headerdup); + free(valuedup); + } + + return ret; +} + static int process_header(struct http_ctx *const h, const char *const line, const size_t n, const char *const value) { @@ -1059,7 +1121,7 @@ static int process_header(struct http_ctx *const h, const char *const line, return ret; } - return 0; + return append_header(h, line, n, value); } static int check_length(struct http_ctx *const h) diff --git a/include/libweb/handler.h b/include/libweb/handler.h index 9cde129..f7bc76a 100644 --- a/include/libweb/handler.h +++ b/include/libweb/handler.h @@ -13,6 +13,7 @@ struct handler_cfg int (*length)(unsigned long long len, const struct http_cookie *c, struct http_response *r, void *user); void *user; + size_t max_headers; }; struct handler *handler_alloc(const struct handler_cfg *cfg); diff --git a/include/libweb/http.h b/include/libweb/http.h index 80030fb..ddb6a89 100644 --- a/include/libweb/http.h +++ b/include/libweb/http.h @@ -5,6 +5,11 @@ #include #include +struct http_header +{ + char *header, *value; +}; + struct http_payload { enum http_op @@ -46,7 +51,8 @@ struct http_payload char *key, *value; } *args; - size_t n_args; + size_t n_args, n_headers; + const struct http_header *headers; }; #define HTTP_STATUSES \ @@ -69,10 +75,7 @@ struct http_response #undef X } status; - struct http_header - { - char *header, *value; - } *headers; + struct http_header *headers; union { @@ -96,6 +99,7 @@ struct http_cfg struct http_response *r, void *user); const char *tmpdir; void *user; + size_t max_headers; }; struct http_ctx *http_alloc(const struct http_cfg *cfg);