1
0
Fork 0

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.
This commit is contained in:
Xavier Del Campo Romero 2023-11-18 00:56:04 +01:00
parent b71a6174e1
commit 65031ca350
Signed by untrusted user: xavi
GPG Key ID: 84FF3612A9BF43F2
12 changed files with 232 additions and 13 deletions

View File

@ -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

View File

@ -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

View File

@ -1,3 +1,4 @@
cmake_minimum_required(VERSION 3.13)
add_subdirectory(headers)
add_subdirectory(hello)
add_subdirectory(html)

View File

@ -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)

View File

@ -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)

26
examples/headers/Makefile Normal file
View File

@ -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)

View File

@ -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.

81
examples/headers/main.c Normal file
View File

@ -0,0 +1,81 @@
#include <dynstr.h>
#include <libweb/handler.h>
#include <libweb/html.h>
#include <libweb/http.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
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;
}

View File

@ -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)

68
http.c
View File

@ -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)

View File

@ -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);

View File

@ -5,6 +5,11 @@
#include <stddef.h>
#include <stdio.h>
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);