Remove router
This commit is contained in:
@@ -4904,12 +4904,12 @@ int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseBuild
|
|||||||
case SOCKET_EVENT_DIED:
|
case SOCKET_EVENT_DIED:
|
||||||
{
|
{
|
||||||
Connection *conn = event.user_data;
|
Connection *conn = event.user_data;
|
||||||
HTTP_ASSERT(conn);
|
if (conn) {
|
||||||
|
http_engine_free(&conn->engine);
|
||||||
http_engine_free(&conn->engine);
|
conn->used = false;
|
||||||
conn->used = false;
|
conn->gen++;
|
||||||
conn->gen++;
|
server->num_conns--;
|
||||||
server->num_conns--;
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -5104,456 +5104,3 @@ void http_response_builder_done(HTTP_ResponseBuilder res)
|
|||||||
if (state == HTTP_ENGINE_STATE_SERVER_CLOSED)
|
if (state == HTTP_ENGINE_STATE_SERVER_CLOSED)
|
||||||
socket_pool_close(server->socket_pool, conn->sock);
|
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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -499,28 +499,4 @@ void http_response_builder_undo (HTTP_ResponseBuilder res);
|
|||||||
void http_response_builder_done (HTTP_ResponseBuilder res);
|
void http_response_builder_done (HTTP_ResponseBuilder res);
|
||||||
|
|
||||||
#endif // HTTP_SERVER_INCLUDED
|
#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
|
#endif // HTTP_AMALGAMATION
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ handle 3xx client redirections
|
|||||||
add discussion on string management
|
add discussion on string management
|
||||||
add discussion on error management
|
add discussion on error management
|
||||||
check that virtual hosts over HTTPS work
|
check that virtual hosts over HTTPS work
|
||||||
add examples for the router
|
|
||||||
|
|
||||||
add timers
|
add timers
|
||||||
add debug and release builds
|
add debug and release builds
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ header.append_file("src/engine.h")
|
|||||||
header.append_file("src/cert.h")
|
header.append_file("src/cert.h")
|
||||||
header.append_file("src/client.h")
|
header.append_file("src/client.h")
|
||||||
header.append_file("src/server.h")
|
header.append_file("src/server.h")
|
||||||
header.append_file("src/router.h")
|
|
||||||
header.append_text("#endif // HTTP_AMALGAMATION\n")
|
header.append_text("#endif // HTTP_AMALGAMATION\n")
|
||||||
header.save("chttp.h")
|
header.save("chttp.h")
|
||||||
|
|
||||||
@@ -59,5 +58,4 @@ source.append_file("src/socket.c")
|
|||||||
source.append_file("src/socket_pool.c")
|
source.append_file("src/socket_pool.c")
|
||||||
source.append_file("src/client.c")
|
source.append_file("src/client.c")
|
||||||
source.append_file("src/server.c")
|
source.append_file("src/server.c")
|
||||||
source.append_file("src/router.c")
|
|
||||||
source.save("chttp.c")
|
source.save("chttp.c")
|
||||||
|
|||||||
-447
@@ -1,447 +0,0 @@
|
|||||||
#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;
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
#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
|
|
||||||
+6
-6
@@ -105,12 +105,12 @@ int http_server_wait(HTTP_Server *server, HTTP_Request **req, HTTP_ResponseBuild
|
|||||||
case SOCKET_EVENT_DIED:
|
case SOCKET_EVENT_DIED:
|
||||||
{
|
{
|
||||||
Connection *conn = event.user_data;
|
Connection *conn = event.user_data;
|
||||||
HTTP_ASSERT(conn);
|
if (conn) {
|
||||||
|
http_engine_free(&conn->engine);
|
||||||
http_engine_free(&conn->engine);
|
conn->used = false;
|
||||||
conn->used = false;
|
conn->gen++;
|
||||||
conn->gen++;
|
server->num_conns--;
|
||||||
server->num_conns--;
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user