mirror of
https://git.sr.ht/~sircmpwn/gmnisrv
synced 2024-11-03 06:07:17 -05:00
Initial implementation of a routing table
This commit is contained in:
parent
1fe107875b
commit
8baeb5a51c
@ -42,15 +42,46 @@ The following keys are accepted under the *[:tls]* section:
|
||||
the name of the organization responsible for the host and it will be
|
||||
filled in as the X.509 /O name.
|
||||
|
||||
## HOST KEYS
|
||||
## ROUTING KEYS
|
||||
|
||||
Hosts that *gmnisrv* is to serve shall be defined in *gmnisrv.ini* by
|
||||
introducing config sections named after each host to provide service for. The
|
||||
following keys apply:
|
||||
To configure *gmnisrv* to service requests, routing keys must be defined. The
|
||||
name of the configuration section is used to determine what kinds of requests it
|
||||
configures.
|
||||
|
||||
The format of the section name is the _hostname_ to be serviced, followed by a
|
||||
token which defines the routing strategy, and a string whose format is specific
|
||||
to each routing strategy. The token and match string may be omitted
|
||||
(i.e. [_hostname_] alone), which implies path routing against "/".
|
||||
|
||||
|] *:*
|
||||
:< Route by path prefix. The URL path is compared to "_string_/".
|
||||
| *=*
|
||||
: Exact match. The URL path must exactly match the string.
|
||||
| *~*
|
||||
: Regular expression routing. The string is a JavaScript-compatible regular
|
||||
expression which is tested against the URL path.
|
||||
|
||||
Some example section names and examples of matching paths:
|
||||
|
||||
|[ *[example.org:/foo]*
|
||||
:< /foo, /foo/bar, /foo/bar/baz
|
||||
| *[example.org=/foo.txt]*
|
||||
: /foo.txt
|
||||
| *[example.org~/[a-z]+\\.(png|jpg|webp)*
|
||||
: /foo.png, /bar.webp
|
||||
|
||||
Routes should be ordered from least to most specific. The matching algorithm
|
||||
attempts to match the URL against each route in reverse order, and chooses the
|
||||
first route which matches.
|
||||
|
||||
Within each routing section, the following keys are used to configure how
|
||||
*gmnisrv* will respond to matching requests:
|
||||
|
||||
*root*
|
||||
Configures the path on disk from which files shall be served for this
|
||||
host.
|
||||
host. If using path prefix matching, the prefix is trimmed, so if
|
||||
example.org/foo/bar.txt is requested and matches *[example.org:/foo]*,
|
||||
"bar.txt" will be appended to the root to form the file path.
|
||||
|
||||
*index*
|
||||
Configures the name of the index file which shall be served in the event
|
||||
|
@ -2,6 +2,7 @@
|
||||
#define GMNISRV_CONFIG
|
||||
#include <arpa/inet.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <regex.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
struct gmnisrv_tls {
|
||||
@ -10,15 +11,33 @@ struct gmnisrv_tls {
|
||||
SSL_CTX *ssl_ctx;
|
||||
};
|
||||
|
||||
struct gmnisrv_host {
|
||||
char *hostname;
|
||||
enum gmnisrv_routing {
|
||||
ROUTE_PATH,
|
||||
ROUTE_REGEX,
|
||||
};
|
||||
|
||||
struct gmnisrv_route {
|
||||
enum gmnisrv_routing routing;
|
||||
char *spec;
|
||||
union {
|
||||
char *path;
|
||||
regex_t *regex;
|
||||
};
|
||||
|
||||
char *root;
|
||||
char *index;
|
||||
bool autoindex;
|
||||
|
||||
struct gmnisrv_route *next;
|
||||
};
|
||||
|
||||
struct gmnisrv_host {
|
||||
char *hostname;
|
||||
X509 *x509;
|
||||
EVP_PKEY *pkey;
|
||||
|
||||
struct gmnisrv_route *routes;
|
||||
|
||||
struct gmnisrv_host *next;
|
||||
};
|
||||
|
||||
|
91
src/config.c
91
src/config.c
@ -21,6 +21,21 @@ gmnisrv_config_get_host(struct gmnisrv_config *conf, const char *hostname)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct gmnisrv_route *
|
||||
gmnisrv_host_get_route(struct gmnisrv_host *host,
|
||||
enum gmnisrv_routing routing, const char *spec)
|
||||
{
|
||||
struct gmnisrv_route *route = host->routes;
|
||||
while (route) {
|
||||
if (route->routing == routing
|
||||
&& strcmp(route->spec, spec) == 0) {
|
||||
return route;
|
||||
}
|
||||
route = route->next;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int
|
||||
parse_listen(struct gmnisrv_config *conf, const char *value)
|
||||
{
|
||||
@ -133,7 +148,29 @@ conf_ini_handler(void *user, const char *section,
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct gmnisrv_host *host = gmnisrv_config_get_host(conf, section);
|
||||
const char *spec;
|
||||
char hostname[1024 + 1];
|
||||
enum gmnisrv_routing routing;
|
||||
size_t hostln = strcspn(section, ":~");
|
||||
switch (section[hostln]) {
|
||||
case '\0':
|
||||
routing = ROUTE_PATH;
|
||||
spec = "/";
|
||||
break;
|
||||
case ':':
|
||||
routing = ROUTE_PATH;
|
||||
spec = §ion[hostln + 1];
|
||||
break;
|
||||
case '~':
|
||||
routing = ROUTE_REGEX;
|
||||
spec = §ion[hostln + 1];
|
||||
break;
|
||||
}
|
||||
assert(hostln < sizeof(hostname));
|
||||
strncpy(hostname, section, hostln);
|
||||
hostname[hostln] = '\0';
|
||||
|
||||
struct gmnisrv_host *host = gmnisrv_config_get_host(conf, hostname);
|
||||
if (!host) {
|
||||
host = calloc(1, sizeof(struct gmnisrv_host));
|
||||
assert(host);
|
||||
@ -142,33 +179,52 @@ conf_ini_handler(void *user, const char *section,
|
||||
conf->hosts = host;
|
||||
}
|
||||
|
||||
struct gmnisrv_route *route =
|
||||
gmnisrv_host_get_route(host, routing, spec);
|
||||
if (!route) {
|
||||
route = calloc(1, sizeof(struct gmnisrv_route));
|
||||
assert(route);
|
||||
route->spec = strdup(spec);
|
||||
route->routing = routing;
|
||||
route->next = host->routes;
|
||||
host->routes = route;
|
||||
|
||||
switch (route->routing) {
|
||||
case ROUTE_PATH:
|
||||
route->path = strdup(spec);
|
||||
break;
|
||||
case ROUTE_REGEX:
|
||||
assert(0); // TODO
|
||||
}
|
||||
}
|
||||
|
||||
struct {
|
||||
char *name;
|
||||
char **value;
|
||||
} host_strvars[] = {
|
||||
{ "root", &host->root },
|
||||
{ "index", &host->index },
|
||||
} route_strvars[] = {
|
||||
{ "root", &route->root },
|
||||
{ "index", &route->index },
|
||||
};
|
||||
struct {
|
||||
char *name;
|
||||
bool *value;
|
||||
} host_bvars[] = {
|
||||
{ "autoindex", &host->autoindex },
|
||||
} route_bvars[] = {
|
||||
{ "autoindex", &route->autoindex },
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof(host_strvars) / sizeof(host_strvars[0]); ++i) {
|
||||
if (strcmp(host_strvars[i].name, name) != 0) {
|
||||
for (size_t i = 0; i < sizeof(route_strvars) / sizeof(route_strvars[0]); ++i) {
|
||||
if (strcmp(route_strvars[i].name, name) != 0) {
|
||||
continue;
|
||||
}
|
||||
*host_strvars[i].value = strdup(value);
|
||||
*route_strvars[i].value = strdup(value);
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sizeof(host_bvars) / sizeof(host_bvars[0]); ++i) {
|
||||
if (strcmp(host_bvars[i].name, name) != 0) {
|
||||
for (size_t i = 0; i < sizeof(route_bvars) / sizeof(route_bvars[0]); ++i) {
|
||||
if (strcmp(route_bvars[i].name, name) != 0) {
|
||||
continue;
|
||||
}
|
||||
*host_bvars[i].value =
|
||||
*route_bvars[i].value =
|
||||
strcasecmp(value, "yes") == 0 ||
|
||||
strcasecmp(value, "true") == 0 ||
|
||||
strcasecmp(value, "on") == 0;
|
||||
@ -233,8 +289,15 @@ config_finish(struct gmnisrv_config *conf)
|
||||
while (host) {
|
||||
struct gmnisrv_host *next = host->next;
|
||||
free(host->hostname);
|
||||
free(host->root);
|
||||
free(host->index);
|
||||
|
||||
struct gmnisrv_route *route = host->routes;
|
||||
while (route) {
|
||||
struct gmnisrv_route *rnext = route->next;
|
||||
free(route->root);
|
||||
free(route->index);
|
||||
free(route);
|
||||
route = rnext;
|
||||
}
|
||||
free(host);
|
||||
host = next;
|
||||
}
|
||||
|
53
src/serve.c
53
src/serve.c
@ -117,15 +117,60 @@ internal_error:
|
||||
goto exit;
|
||||
}
|
||||
|
||||
static bool
|
||||
route_match(struct gmnisrv_route *route, const char *path, const char **revised)
|
||||
{
|
||||
switch (route->routing) {
|
||||
case ROUTE_PATH:;
|
||||
size_t l = strlen(route->path);
|
||||
if (strncmp(path, route->path, l) != 0) {
|
||||
return false;
|
||||
}
|
||||
if (route->path[l-1] != '/' && path[l] != '\0' && path[l] != '/') {
|
||||
// Prevents path == "/foobar" from matching
|
||||
// route == "/foo":
|
||||
return false;
|
||||
}
|
||||
if (route->path[l-1] == '/') {
|
||||
*revised = &path[l-1];
|
||||
} else {
|
||||
*revised = &path[l];
|
||||
}
|
||||
return true;
|
||||
case ROUTE_REGEX:
|
||||
assert(0); // TODO
|
||||
}
|
||||
|
||||
assert(0); // Invariant
|
||||
}
|
||||
|
||||
void
|
||||
serve_request(struct gmnisrv_client *client)
|
||||
{
|
||||
struct gmnisrv_host *host = client->host;
|
||||
assert(host);
|
||||
assert(host->root); // TODO: reverse proxy support
|
||||
struct gmnisrv_route *route = host->routes;
|
||||
assert(route);
|
||||
|
||||
const char *url_path;
|
||||
while (route) {
|
||||
if (route_match(route, client->path, &url_path)) {
|
||||
break;
|
||||
}
|
||||
|
||||
route = route->next;
|
||||
}
|
||||
|
||||
if (!route) {
|
||||
client_submit_response(client,
|
||||
GEMINI_STATUS_NOT_FOUND, "Not found", NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
assert(route->root); // TODO: reverse proxy support
|
||||
|
||||
char path[PATH_MAX + 1];
|
||||
int n = snprintf(path, sizeof(path), "%s%s", host->root, client->path);
|
||||
int n = snprintf(path, sizeof(path), "%s%s", route->root, url_path);
|
||||
if ((size_t)n >= sizeof(path)) {
|
||||
client_submit_response(client, GEMINI_STATUS_PERMANENT_FAILURE,
|
||||
"Request path exceeds PATH_MAX", NULL);
|
||||
@ -142,12 +187,12 @@ serve_request(struct gmnisrv_client *client)
|
||||
}
|
||||
|
||||
if (S_ISDIR(st.st_mode)) {
|
||||
if (host->autoindex) {
|
||||
if (route->autoindex) {
|
||||
serve_autoindex(client, path);
|
||||
return;
|
||||
} else {
|
||||
strncat(path,
|
||||
host->index ? host->index : "index.gmi",
|
||||
route->index ? route->index : "index.gmi",
|
||||
sizeof(path) - 1);
|
||||
}
|
||||
} else if (S_ISLNK(st.st_mode)) {
|
||||
|
Loading…
Reference in New Issue
Block a user