forked from xavi/libweb
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:
parent
b71a6174e1
commit
65031ca350
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.13)
|
||||
add_subdirectory(headers)
|
||||
add_subdirectory(hello)
|
||||
add_subdirectory(html)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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.
|
|
@ -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;
|
||||
}
|
|
@ -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
68
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)
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue