Implement cweb_match_domain by wrapping http_match_host
This commit is contained in:
+17
-453
@@ -365,6 +365,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>
|
||||
@@ -1667,6 +1668,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
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -5104,456 +5121,3 @@ 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;
|
||||
}
|
||||
|
||||
+3
-24
@@ -193,6 +193,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
|
||||
|
||||
@@ -499,28 +502,4 @@ 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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user