Skip to content

Commit

Permalink
net: http_server: serve resources only for their defined services
Browse files Browse the repository at this point in the history
Ensure that HTTP resources can only be served to a client connected on
the specific service(s) that the resource was registered against using
the HTTP_RESOURCE_DEFINE macro.

This allows different resources to be registered to different services,
for example to make some resources only available via an HTTPS service
and not via unencrypted HTTP.

Signed-off-by: Matt Rodgers <[email protected]>
  • Loading branch information
mrodgers-witekio committed Dec 31, 2024
1 parent ee250a1 commit 14d2d77
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 39 deletions.
3 changes: 3 additions & 0 deletions include/zephyr/net/http/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,9 @@ struct http_client_ctx {
/** Socket descriptor associated with the server. */
int fd;

/** HTTP service on which the client is connected */
const struct http_service_desc *service;

/** Client data buffer. */
unsigned char buffer[HTTP_SERVER_CLIENT_BUFFER_SIZE];

Expand Down
5 changes: 4 additions & 1 deletion include/zephyr/net/http/service.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ struct http_resource_desc {
struct http_service_desc {
const char *host;
uint16_t *port;
int *fd;
void *detail;
size_t concurrent;
size_t backlog;
Expand All @@ -80,9 +81,11 @@ struct http_service_desc {

#define __z_http_service_define(_name, _host, _port, _concurrent, _backlog, _detail, _res_begin, \
_res_end, ...) \
const STRUCT_SECTION_ITERABLE(http_service_desc, _name) = { \
static int _name##_fd = -1; \
const STRUCT_SECTION_ITERABLE(http_service_desc, _name) = { \
.host = _host, \
.port = (uint16_t *)(_port), \
.fd = &_name##_fd, \
.detail = (void *)(_detail), \
.concurrent = (_concurrent), \
.backlog = (_backlog), \
Expand Down
3 changes: 2 additions & 1 deletion subsys/net/lib/http/headers/server_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ int enter_http2_request(struct http_client_ctx *client);
int enter_http_done_state(struct http_client_ctx *client);

/* Others */
struct http_resource_detail *get_resource_detail(const char *path, int *len, bool is_ws);
struct http_resource_detail *get_resource_detail(const struct http_service_desc *service,
const char *path, int *len, bool is_ws);
int http_server_sendall(struct http_client_ctx *client, const void *buf, size_t len);
void http_server_get_content_type_from_extension(char *url, char *content_type,
size_t content_type_size);
Expand Down
64 changes: 41 additions & 23 deletions subsys/net/lib/http/http_server_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,7 @@ int http_server_init(struct http_server_ctx *ctx)
LOG_DBG("Initialized HTTP Service %s:%u",
svc->host ? svc->host : "<any>", *svc->port);

*svc->fd = fd;
ctx->fds[count].fd = fd;
ctx->fds[count].events = ZSOCK_POLLIN;
count++;
Expand Down Expand Up @@ -299,6 +300,10 @@ static void close_all_sockets(struct http_server_ctx *ctx)

ctx->fds[i].fd = -1;
}

HTTP_SERVICE_FOREACH(svc) {
*svc->fd = -1;
}
}

static void client_release_resources(struct http_client_ctx *client)
Expand Down Expand Up @@ -393,9 +398,22 @@ void http_client_timer_restart(struct http_client_ctx *client)
k_work_reschedule(&client->inactivity_timer, INACTIVITY_TIMEOUT);
}

static void init_client_ctx(struct http_client_ctx *client, int new_socket)
static const struct http_service_desc *lookup_service(int server_fd)
{
HTTP_SERVICE_FOREACH(svc) {
if (*svc->fd == server_fd) {
return svc;
}
}

return NULL;
}

static void init_client_ctx(struct http_client_ctx *client, const struct http_service_desc *svc,
int new_socket)
{
client->fd = new_socket;
client->service = svc;
client->data_len = 0;
client->server_state = HTTP_SERVER_PREFACE_STATE;
client->has_upgrade_header = false;
Expand Down Expand Up @@ -523,6 +541,7 @@ static int handle_http_request(struct http_client_ctx *client)
static int http_server_run(struct http_server_ctx *ctx)
{
struct http_client_ctx *client;
const struct http_service_desc *service;
eventfd_t value;
bool found_slot;
int new_socket;
Expand Down Expand Up @@ -600,6 +619,9 @@ static int http_server_run(struct http_server_ctx *ctx)
continue;
}

service = lookup_service(ctx->fds[i].fd);
__ASSERT(NULL != service, "fd not associated with a service");

found_slot = false;

for (j = ctx->listen_fds; j < ARRAY_SIZE(ctx->fds); j++) {
Expand All @@ -615,7 +637,7 @@ static int http_server_run(struct http_server_ctx *ctx)

LOG_DBG("Init client #%d", j - ctx->listen_fds);

init_client_ctx(&ctx->clients[j - ctx->listen_fds],
init_client_ctx(&ctx->clients[j - ctx->listen_fds], service,
new_socket);
found_slot = true;
break;
Expand Down Expand Up @@ -713,34 +735,30 @@ static bool skip_this(struct http_resource_desc *resource, bool is_websocket)
return false;
}

struct http_resource_detail *get_resource_detail(const char *path,
int *path_len,
bool is_websocket)
struct http_resource_detail *get_resource_detail(const struct http_service_desc *service,
const char *path, int *path_len, bool is_websocket)
{
HTTP_SERVICE_FOREACH(service) {
HTTP_SERVICE_FOREACH_RESOURCE(service, resource) {
if (skip_this(resource, is_websocket)) {
continue;
}

if (IS_ENABLED(CONFIG_HTTP_SERVER_RESOURCE_WILDCARD)) {
int ret;

ret = fnmatch(resource->resource, path,
(FNM_PATHNAME | FNM_LEADING_DIR));
if (ret == 0) {
*path_len = strlen(resource->resource);
return resource->detail;
}
}
HTTP_SERVICE_FOREACH_RESOURCE(service, resource) {
if (skip_this(resource, is_websocket)) {
continue;
}

if (compare_strings(path, resource->resource) == 0) {
NET_DBG("Got match for %s", resource->resource);
if (IS_ENABLED(CONFIG_HTTP_SERVER_RESOURCE_WILDCARD)) {
int ret;

ret = fnmatch(resource->resource, path, (FNM_PATHNAME | FNM_LEADING_DIR));
if (ret == 0) {
*path_len = strlen(resource->resource);
return resource->detail;
}
}

if (compare_strings(path, resource->resource) == 0) {
NET_DBG("Got match for %s", resource->resource);

*path_len = strlen(resource->resource);
return resource->detail;
}
}

NET_DBG("No match for %s", path);
Expand Down
4 changes: 2 additions & 2 deletions subsys/net/lib/http/http_server_http1.c
Original file line number Diff line number Diff line change
Expand Up @@ -859,7 +859,7 @@ int handle_http1_request(struct http_client_ctx *client)

if (client->websocket_upgrade) {
if (IS_ENABLED(CONFIG_HTTP_SERVER_WEBSOCKET)) {
detail = get_resource_detail(client->url_buffer,
detail = get_resource_detail(client->service, client->url_buffer,
&path_len, true);
if (detail == NULL) {
goto not_found;
Expand Down Expand Up @@ -899,7 +899,7 @@ int handle_http1_request(struct http_client_ctx *client)
}
}

detail = get_resource_detail(client->url_buffer, &path_len, false);
detail = get_resource_detail(client->service, client->url_buffer, &path_len, false);
if (detail != NULL) {
detail->path_len = path_len;

Expand Down
4 changes: 2 additions & 2 deletions subsys/net/lib/http/http_server_http2.c
Original file line number Diff line number Diff line change
Expand Up @@ -1029,7 +1029,7 @@ int handle_http1_to_http2_upgrade(struct http_client_ctx *client)
client->preface_sent = true;
}

detail = get_resource_detail(client->url_buffer, &path_len, false);
detail = get_resource_detail(client->service, client->url_buffer, &path_len, false);
if (detail != NULL) {
detail->path_len = path_len;

Expand Down Expand Up @@ -1509,7 +1509,7 @@ int handle_http_frame_headers(struct http_client_ctx *client)
return 0;
}

detail = get_resource_detail(client->url_buffer, &path_len, false);
detail = get_resource_detail(client->service, client->url_buffer, &path_len, false);
if (detail != NULL) {
detail->path_len = path_len;

Expand Down
34 changes: 24 additions & 10 deletions tests/net/lib/http_server/common/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -296,55 +296,69 @@ ZTEST(http_service, test_HTTP_RESOURCE_DEFINE)
}
}

extern struct http_resource_detail *get_resource_detail(const char *path,
extern struct http_resource_detail *get_resource_detail(const struct http_service_desc *service,
const char *path,
int *path_len,
bool is_websocket);

#define CHECK_PATH(path, len) ({ *len = 0; get_resource_detail(path, len, false); })
#define CHECK_PATH(svc, path, len) ({ *len = 0; get_resource_detail(&svc, path, len, false); })

ZTEST(http_service, test_HTTP_RESOURCE_WILDCARD)
{
struct http_resource_detail *res;
int len;

res = CHECK_PATH("/", &len);
res = CHECK_PATH(service_A, "/", &len);
zassert_not_null(res, "Cannot find resource");
zassert_true(len > 0, "Length not set");
zassert_equal(res, RES(0), "Resource mismatch");

res = CHECK_PATH("/f", &len);
res = CHECK_PATH(service_D, "/f", &len);
zassert_is_null(res, "Resource found");
zassert_equal(len, 0, "Length set");

res = CHECK_PATH("/foo1.html", &len);
res = CHECK_PATH(service_D, "/foo1.html", &len);
zassert_not_null(res, "Cannot find resource");
zassert_true(len > 0, "Length not set");
zassert_equal(res, RES(0), "Resource mismatch");

res = CHECK_PATH("/foo2222.html", &len);
res = CHECK_PATH(service_D, "/foo2222.html", &len);
zassert_not_null(res, "Cannot find resource");
zassert_true(len > 0, "Length not set");
zassert_equal(res, RES(1), "Resource mismatch");

res = CHECK_PATH("/fbo3.html", &len);
res = CHECK_PATH(service_D, "/fbo3.html", &len);
zassert_not_null(res, "Cannot find resource");
zassert_true(len > 0, "Length not set");
zassert_equal(res, RES(1), "Resource mismatch");

res = CHECK_PATH("/fbo3.htm", &len);
res = CHECK_PATH(service_D, "/fbo3.htm", &len);
zassert_not_null(res, "Cannot find resource");
zassert_true(len > 0, "Length not set");
zassert_equal(res, RES(0), "Resource mismatch");

res = CHECK_PATH("/fbo4.html", &len);
res = CHECK_PATH(service_D, "/fbo4.html", &len);
zassert_not_null(res, "Cannot find resource");
zassert_true(len > 0, "Length not set");
zassert_equal(res, RES(3), "Resource mismatch");

res = CHECK_PATH("/fs/index.html", &len);
res = CHECK_PATH(service_A, "/fs/index.html", &len);
zassert_not_null(res, "Cannot find resource");
zassert_true(len > 0, "Length not set");
zassert_equal(res, RES(5), "Resource mismatch");

/* Resources that only exist on one service should not be found on another */
res = CHECK_PATH(service_A, "/foo1.htm", &len);
zassert_is_null(res, "Resource found");
zassert_equal(len, 0, "Length set");

res = CHECK_PATH(service_A, "/foo2222.html", &len);
zassert_is_null(res, "Resource found");
zassert_equal(len, 0, "Length set");

res = CHECK_PATH(service_A, "/fbo3.htm", &len);
zassert_is_null(res, "Resource found");
zassert_equal(len, 0, "Length set");
}

extern void http_server_get_content_type_from_extension(char *url, char *content_type,
Expand Down

0 comments on commit 14d2d77

Please sign in to comment.