Implement cweb_match_domain by wrapping http_match_host

This commit is contained in:
2025-10-18 11:53:21 +02:00
parent d26b2b7b48
commit 288eccf43d
6 changed files with 5689 additions and 6577 deletions
+5123 -5559
View File
File diff suppressed because it is too large Load Diff
+505 -526
View File
File diff suppressed because it is too large Load Diff
+41 -483
View File
@@ -1,8 +1,8 @@
/*
Copyright © 2025 Francesco Cozzuto
Copyright © 2025 Francesco Cozzuto
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the Software),
copy of this software and associated documentation files (the Software),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
@@ -11,7 +11,7 @@ Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS
THE SOFTWARE IS PROVIDED AS IS, WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
@@ -569,6 +569,9 @@ HTTP_String http_get_cookie (HTTP_Request *req, HTTP_String name);
HTTP_String http_get_param (HTTP_String body, HTTP_String str, char *mem, int cap);
int http_get_param_i (HTTP_String body, HTTP_String str);
// Checks whether the request was meant for the host with the given
// domain an port. If port is -1, the default value of 80 is assumed.
bool http_match_host(HTTP_Request *req, HTTP_String domain, int port);
#endif // PARSE_INCLUDED
@@ -875,30 +878,6 @@ void http_response_builder_undo (HTTP_ResponseBuilder res);
void http_response_builder_done (HTTP_ResponseBuilder res);
#endif // HTTP_SERVER_INCLUDED
////////////////////////////////////////////////////////////////////////////////////////
// src/router.h
////////////////////////////////////////////////////////////////////////////////////////
#line 1 "src/router.h"
#ifndef HTTP_ROUTER_INCLUDED
#define HTTP_ROUTER_INCLUDED
#ifndef HTTP_AMALGAMATION
#include "server.h"
#endif
typedef struct HTTP_Router HTTP_Router;
typedef void (*HTTP_RouterFunc)(HTTP_Request*, HTTP_ResponseBuilder, void*);;
HTTP_Router* http_router_init (void);
void http_router_free (HTTP_Router *router);
void http_router_resolve (HTTP_Router *router, HTTP_Request *req, HTTP_ResponseBuilder res);
void http_router_dir (HTTP_Router *router, HTTP_String endpoint, HTTP_String path);
void http_router_func (HTTP_Router *router, HTTP_Method method, HTTP_String endpoint, HTTP_RouterFunc func, void*);
int http_serve (char *addr, int port, HTTP_Router *router);
#endif // HTTP_ROUTER_INCLUDED
#endif // HTTP_AMALGAMATION
////////////////////////////////////////////////////////////////////////////////////////
@@ -1273,6 +1252,7 @@ void print_bytes(HTTP_String prefix, HTTP_String src)
////////////////////////////////////////////////////////////////////////////////////////
#line 1 "src/parse.c"
#include <stdio.h> // snprintf
#include <stddef.h>
#include <stdint.h>
#include <string.h>
@@ -2575,6 +2555,22 @@ int http_get_param_i(HTTP_String body, HTTP_String str)
return res;
}
bool http_match_host(HTTP_Request *req, HTTP_String domain, int port)
{
int idx = http_find_header(req->headers, req->num_headers, HTTP_STR("Host"));
HTTP_ASSERT(idx != -1); // Requests without the host header are always rejected
char tmp[1<<8];
if (port > -1 && port != 80) {
int ret = snprintf(tmp, sizeof(tmp), "%.*s:%d", domain.len, domain.ptr, port);
HTTP_ASSERT(ret > 0);
domain = (HTTP_String) { tmp, ret };
}
HTTP_String host = req->headers[idx].value;
return http_streq(host, domain);
}
////////////////////////////////////////////////////////////////////////////////////////
// src/engine.c
////////////////////////////////////////////////////////////////////////////////////////
@@ -6012,459 +6008,6 @@ void http_response_builder_done(HTTP_ResponseBuilder res)
if (state == HTTP_ENGINE_STATE_SERVER_CLOSED)
socket_pool_close(server->socket_pool, conn->sock);
}
////////////////////////////////////////////////////////////////////////////////////////
// src/router.c
////////////////////////////////////////////////////////////////////////////////////////
#line 1 "src/router.c"
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#ifdef _WIN32
#include <windows.h>
#endif
#ifdef __linux__
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#endif
#ifndef HTTP_AMALGAMATION
#include "router.h"
#endif
#ifndef HTTP_AMALGAMATION
bool is_alpha(char c)
{
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
}
bool is_digit(char c)
{
return c >= '0' && c <= '9';
}
#endif // HTTP_AMALGAMATION
typedef enum {
ROUTE_STATIC_DIR,
ROUTE_DYNAMIC,
} RouteType;
typedef struct {
RouteType type;
HTTP_String endpoint;
HTTP_String path;
HTTP_RouterFunc func;
void *ptr;
} Route;
struct HTTP_Router {
int num_routes;
int max_routes;
Route routes[];
};
HTTP_Router *http_router_init(void)
{
int max_routes = 32;
HTTP_Router *router = malloc(sizeof(HTTP_Router) + max_routes * sizeof(Route));
if (router == NULL)
return NULL;
router->max_routes = max_routes;
router->num_routes = 0;
return router;
}
void http_router_free(HTTP_Router *router)
{
free(router);
}
void http_router_dir(HTTP_Router *router, HTTP_String endpoint, HTTP_String path)
{
if (router->num_routes == router->max_routes)
abort();
Route *route = &router->routes[router->num_routes++];
route->type = ROUTE_STATIC_DIR;
route->endpoint = endpoint;
route->path = path;
}
void http_router_func(HTTP_Router *router, HTTP_Method method,
HTTP_String endpoint, HTTP_RouterFunc func, void *ptr)
{
if (router->num_routes == router->max_routes)
abort();
Route *route = &router->routes[router->num_routes++];
(void) method; // TODO: Don't ignore the method
route->type = ROUTE_DYNAMIC;
route->endpoint = endpoint;
route->func = func;
route->ptr = ptr;
}
static int valid_component_char(char c)
{
return is_alpha(c) || is_digit(c) || c == '-' || c == '_' || c == '.'; // TODO
}
static int parse_and_sanitize_path(HTTP_String path, HTTP_String *comps, int max_comps)
{
// We treat relative and absolute paths the same
if (path.len > 0 && path.ptr[0] == '/') {
path.ptr++;
path.len--;
if (path.len == 0)
return 0;
}
int num = 0;
int cur = 0;
for (;;) {
if (cur == path.len || !valid_component_char(path.ptr[cur]))
return -1; // Empty component
int start = cur;
do
cur++;
while (cur < path.len && valid_component_char(path.ptr[cur]));
HTTP_String comp = { path.ptr + start, cur - start };
if (http_streq(comp, HTTP_STR(".."))) {
if (num == 0)
return -1;
num--;
} else if (!http_streq(comp, HTTP_STR("."))) {
if (num == max_comps)
return -1;
comps[num++] = comp;
}
if (cur < path.len) {
if (path.ptr[cur] != '/')
return -1;
cur++;
}
if (cur == path.len)
break;
}
return num;
}
static int
serialize_parsed_path(HTTP_String *comps, int num_comps, char *dst, int max)
{
int len = 0;
for (int i = 0; i < num_comps; i++)
len += comps[i].len + 1;
if (len >= max)
return -1;
int copied = 0;
for (int i = 0; i < num_comps; i++) {
if (i > 0)
dst[copied++] = '/';
memcpy(dst + copied,
comps[i].ptr,
comps[i].len);
copied += comps[i].len;
}
dst[copied] = '\0';
return copied;
}
#define MAX_COMPS 32
static int sanitize_path(HTTP_String path, char *dst, int max)
{
HTTP_String comps[MAX_COMPS];
int num_comps = parse_and_sanitize_path(path, comps, MAX_COMPS);
if (num_comps < 0) return -1;
return serialize_parsed_path(comps, num_comps, dst, max);
}
static int swap_parents(HTTP_String original_parent_path, HTTP_String new_parent_path, HTTP_String path, char *mem, int max)
{
int num_original_parent_path_comps;
HTTP_String original_parent_path_comps[MAX_COMPS];
int num_new_parent_path_comps;
HTTP_String new_parent_path_comps[MAX_COMPS];
int num_path_comps;
HTTP_String path_comps[MAX_COMPS];
num_original_parent_path_comps = parse_and_sanitize_path(original_parent_path, original_parent_path_comps, MAX_COMPS);
num_new_parent_path_comps = parse_and_sanitize_path(new_parent_path, new_parent_path_comps, MAX_COMPS);
num_path_comps = parse_and_sanitize_path(path, path_comps, MAX_COMPS);
if (num_original_parent_path_comps < 0 || num_new_parent_path_comps < 0 || num_path_comps < 0)
return -1;
int match = 1;
if (num_path_comps < num_original_parent_path_comps)
match = 0;
else {
for (int i = 0; i < num_original_parent_path_comps; i++)
if (!http_streq(original_parent_path_comps[i], path_comps[i])) {
match = 0;
break;
}
}
if (!match)
return 0;
int num_result_comps = num_new_parent_path_comps + num_path_comps - num_original_parent_path_comps;
if (num_result_comps < 0 || num_result_comps > MAX_COMPS)
return -1;
HTTP_String result_comps[MAX_COMPS];
for (int i = 0; i < num_new_parent_path_comps; i++)
result_comps[i] = new_parent_path_comps[i];
for (int i = 0; i < num_path_comps; i++)
result_comps[num_new_parent_path_comps + i] = path_comps[num_original_parent_path_comps + i];
return serialize_parsed_path(result_comps, num_result_comps, mem, max);
}
#if _WIN32
typedef HANDLE File;
#else
typedef int File;
#endif
static int file_open(const char *path, File *handle, int *size)
{
#ifdef _WIN32
*handle = CreateFileA(
path,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (*handle == INVALID_HANDLE_VALUE) {
DWORD error = GetLastError();
if (error == ERROR_FILE_NOT_FOUND)
return 1;
if (error == ERROR_ACCESS_DENIED)
return 1;
return -1;
}
LARGE_INTEGER fileSize;
if (!GetFileSizeEx(*handle, &fileSize)) {
CloseHandle(*handle);
return -1;
}
if (fileSize.QuadPart > INT_MAX) {
CloseHandle(*handle);
return -1;
}
*size = (int) fileSize.QuadPart;
return 0;
#else
*handle = open(path, O_RDONLY);
if (*handle < 0) {
if (errno == ENOENT)
return 1;
return -1;
}
struct stat info;
if (fstat(*handle, &info) < 0) {
close(*handle);
return -1;
}
if (S_ISDIR(info.st_mode)) {
close(*handle);
return 1;
}
if (info.st_size > INT_MAX) {
close(*handle);
return -1;
}
*size = (int) info.st_size;
return 0;
#endif
}
static void file_close(File file)
{
#ifdef _WIN32
CloseHandle(file);
#else
close(file);
#endif
}
static int file_read(File file, char *dst, int max)
{
#ifdef _WIN32
DWORD num;
BOOL ok = ReadFile(file, dst, max, &num, NULL);
if (!ok)
return -1;
return (int) num;
#else
return read(file, dst, max);
#endif
}
static int serve_file_or_index(HTTP_ResponseBuilder res, HTTP_String base_endpoint, HTTP_String base_path, HTTP_String endpoint)
{
char mem[1<<12];
int ret = swap_parents(base_endpoint, base_path, endpoint, mem, sizeof(mem));
if (ret <= 0)
return ret;
HTTP_String path = {mem, ret}; // Note that this is zero terminated
int size;
File file;
ret = file_open(path.ptr, &file, &size);
if (ret == -1) {
http_response_builder_status(res, 500);
http_response_builder_done(res);
return 1;
}
if (ret == 1) {
// File missing
char index[] = "index.html";
if (path.len + sizeof(index) + 1 > sizeof(mem)) {
http_response_builder_status(res, 500);
http_response_builder_done(res);
return 1;
}
path.ptr[path.len++] = '/';
memcpy(path.ptr + path.len, index, sizeof(index));
path.len += sizeof(index)-1;
ret = file_open(path.ptr, &file, &size);
if (ret == -1) {
http_response_builder_status(res, 500);
http_response_builder_done(res);
return 1;
}
if (ret == 1)
return 0; // File missing
}
HTTP_ASSERT(ret == 0);
int cap;
char *dst;
http_response_builder_status(res, 200);
http_response_builder_bodycap(res, size);
dst = http_response_builder_bodybuf(res, &cap);
if (dst) {
int copied = 0;
while (copied < size) {
int ret = file_read(file, dst + copied, size - copied);
if (ret < 0) goto err;
if (ret == 0) break;
copied += ret;
}
if (copied < size) goto err;
http_response_builder_bodyack(res, size);
}
http_response_builder_done(res);
file_close(file);
return 1;
err:
http_response_builder_bodyack(res, 0);
http_response_builder_undo(res);
http_response_builder_status(res, 500);
http_response_builder_done(res);
file_close(file);
return 1;
}
static int serve_dynamic_route(Route *route, HTTP_Request *req, HTTP_ResponseBuilder res)
{
char path_mem[1<<12];
int path_len = sanitize_path(req->url.path, path_mem, (int) sizeof(path_mem));
if (path_len < 0) {
http_response_builder_status(res, 400);
http_response_builder_body(res, HTTP_STR("Invalid path"));
http_response_builder_done(res);
return 1;
}
HTTP_String path = {path_mem, path_len};
if (!http_streq(path, route->endpoint))
return 0;
route->func(req, res, route->ptr);
return 1;
}
void http_router_resolve(HTTP_Router *router, HTTP_Request *req, HTTP_ResponseBuilder res)
{
for (int i = 0; i < router->num_routes; i++) {
Route *route = &router->routes[i];
switch (route->type) {
case ROUTE_STATIC_DIR:
if (serve_file_or_index(res,
route->endpoint,
route->path,
req->url.path))
return;
break;
case ROUTE_DYNAMIC:
if (serve_dynamic_route(route, req, res))
return;
break;
default:
http_response_builder_status(res, 500);
http_response_builder_done(res);
return;
}
}
http_response_builder_status(res, 404);
http_response_builder_done(res);
}
int http_serve(char *addr, int port, HTTP_Router *router)
{
int ret;
HTTP_Server *server = http_server_init_ex((HTTP_String) { addr, strlen(addr) }, port, 0, (HTTP_String) {}, (HTTP_String) {});
if (server == NULL) {
http_router_free(router);
return -1;
}
for (;;) {
HTTP_Request *req;
HTTP_ResponseBuilder res;
ret = http_server_wait(server, &req, &res);
if (ret < 0) {
http_server_free(server);
http_router_free(router);
return -1;
}
if (ret == 0)
continue;
http_router_resolve(router, req, res);
}
http_server_free(server);
http_router_free(router);
return 0;
}
#undef MIN
#undef MAX
#undef ASSERT
@@ -13638,6 +13181,9 @@ struct CWEB {
int pool_cap;
char *pool;
uint16_t port;
uint16_t secure_port;
// Login
SessionStorage *session_storage;
@@ -14674,6 +14220,8 @@ CWEB *cweb_init(CWEB_String addr, uint16_t port, uint16_t secure_port,
free(cweb);
return NULL;
}
cweb->port = port;
cweb->secure_port = secure_port;
#ifdef CWEB_ENABLE_DATABASE
cweb->db = NULL;
@@ -14826,10 +14374,20 @@ CWEB_Request *cweb_wait(CWEB *cweb)
bool cweb_match_domain(CWEB_Request *req, CWEB_String str)
{
int idx = http_find_header(req->req->headers, req->req->num_headers, HTTP_STR("host"));
if (idx < 0) return false;
uint16_t port = req->cweb->port;
uint16_t secure_port = req->cweb->secure_port;
return http_streq((HTTP_String) { str.ptr, str.len }, req->req->headers[idx].value);
HTTP_String str2 = { str.ptr, str.len };
if (port > 0
&& http_match_host(req->req, str2, port))
return true;
if (secure_port > 0
&& http_match_host(req->req, str2, secure_port))
return true;
return false;
}
bool cweb_match_endpoint(CWEB_Request *req, CWEB_String str)
-4
View File
@@ -55,24 +55,20 @@ static int user_exists(CWEB *cweb, CWEB_String name, CWEB_String pass)
CWEB_PasswordHash hash;
int ret = cweb_next_query_row(&res, &user_id, &hash);
if (ret < 0) {
printf("here %s:%d\n", __FILE__, __LINE__); // TODO: remove
cweb_free_query_result(&res);
return -1;
}
if (ret == 0) {
printf("here %s:%d\n", __FILE__, __LINE__); // TODO: remove
cweb_free_query_result(&res);
return 0;
}
ret = cweb_check_password(pass, hash);
if (ret < 0) {
printf("here %s:%d\n", __FILE__, __LINE__); // TODO: remove
cweb_free_query_result(&res);
return -1;
}
if (ret > 0) {
printf("here %s:%d\n", __FILE__, __LINE__); // TODO: remove
cweb_free_query_result(&res);
return 0;
}
+2 -2
View File
@@ -1,7 +1,7 @@
include "page.wl"
let post_id = $args(0)
let posts = $query("SELECT U.username, P.title, P.content FROM Posts as P, Users as U WHERE P.id=? AND U.id=P.author", post_id)
let post_id = $args(0)
let posts = $query("SELECT U.username, P.title, P.content FROM Posts as P, Users as U WHERE P.id=? AND U.id=P.author", post_id)
let comments = $query("SELECT C.id, U.username, C.content, C.parent_post, C.parent_comment FROM Comments as C, Users as U WHERE C.parent_post=? AND U.id=C.author", post_id)
let lookup = {}
+18 -3
View File
@@ -90,6 +90,9 @@ struct CWEB {
int pool_cap;
char *pool;
uint16_t port;
uint16_t secure_port;
// Login
SessionStorage *session_storage;
@@ -1126,6 +1129,8 @@ CWEB *cweb_init(CWEB_String addr, uint16_t port, uint16_t secure_port,
free(cweb);
return NULL;
}
cweb->port = port;
cweb->secure_port = secure_port;
#ifdef CWEB_ENABLE_DATABASE
cweb->db = NULL;
@@ -1278,10 +1283,20 @@ CWEB_Request *cweb_wait(CWEB *cweb)
bool cweb_match_domain(CWEB_Request *req, CWEB_String str)
{
int idx = http_find_header(req->req->headers, req->req->num_headers, HTTP_STR("host"));
if (idx < 0) return false;
uint16_t port = req->cweb->port;
uint16_t secure_port = req->cweb->secure_port;
return http_streq((HTTP_String) { str.ptr, str.len }, req->req->headers[idx].value);
HTTP_String str2 = { str.ptr, str.len };
if (port > 0
&& http_match_host(req->req, str2, port))
return true;
if (secure_port > 0
&& http_match_host(req->req, str2, secure_port))
return true;
return false;
}
bool cweb_match_endpoint(CWEB_Request *req, CWEB_String str)