mirror of
https://git.sr.ht/~sircmpwn/gmnisrv
synced 2025-02-02 15:08:11 -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
|
the name of the organization responsible for the host and it will be
|
||||||
filled in as the X.509 /O name.
|
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
|
To configure *gmnisrv* to service requests, routing keys must be defined. The
|
||||||
introducing config sections named after each host to provide service for. The
|
name of the configuration section is used to determine what kinds of requests it
|
||||||
following keys apply:
|
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*
|
*root*
|
||||||
Configures the path on disk from which files shall be served for this
|
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*
|
*index*
|
||||||
Configures the name of the index file which shall be served in the event
|
Configures the name of the index file which shall be served in the event
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
#define GMNISRV_CONFIG
|
#define GMNISRV_CONFIG
|
||||||
#include <arpa/inet.h>
|
#include <arpa/inet.h>
|
||||||
#include <openssl/x509.h>
|
#include <openssl/x509.h>
|
||||||
|
#include <regex.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
|
||||||
struct gmnisrv_tls {
|
struct gmnisrv_tls {
|
||||||
@ -10,15 +11,33 @@ struct gmnisrv_tls {
|
|||||||
SSL_CTX *ssl_ctx;
|
SSL_CTX *ssl_ctx;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct gmnisrv_host {
|
enum gmnisrv_routing {
|
||||||
char *hostname;
|
ROUTE_PATH,
|
||||||
|
ROUTE_REGEX,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct gmnisrv_route {
|
||||||
|
enum gmnisrv_routing routing;
|
||||||
|
char *spec;
|
||||||
|
union {
|
||||||
|
char *path;
|
||||||
|
regex_t *regex;
|
||||||
|
};
|
||||||
|
|
||||||
char *root;
|
char *root;
|
||||||
char *index;
|
char *index;
|
||||||
bool autoindex;
|
bool autoindex;
|
||||||
|
|
||||||
|
struct gmnisrv_route *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct gmnisrv_host {
|
||||||
|
char *hostname;
|
||||||
X509 *x509;
|
X509 *x509;
|
||||||
EVP_PKEY *pkey;
|
EVP_PKEY *pkey;
|
||||||
|
|
||||||
|
struct gmnisrv_route *routes;
|
||||||
|
|
||||||
struct gmnisrv_host *next;
|
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;
|
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
|
static int
|
||||||
parse_listen(struct gmnisrv_config *conf, const char *value)
|
parse_listen(struct gmnisrv_config *conf, const char *value)
|
||||||
{
|
{
|
||||||
@ -133,7 +148,29 @@ conf_ini_handler(void *user, const char *section,
|
|||||||
return 0;
|
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) {
|
if (!host) {
|
||||||
host = calloc(1, sizeof(struct gmnisrv_host));
|
host = calloc(1, sizeof(struct gmnisrv_host));
|
||||||
assert(host);
|
assert(host);
|
||||||
@ -142,33 +179,52 @@ conf_ini_handler(void *user, const char *section,
|
|||||||
conf->hosts = host;
|
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 {
|
struct {
|
||||||
char *name;
|
char *name;
|
||||||
char **value;
|
char **value;
|
||||||
} host_strvars[] = {
|
} route_strvars[] = {
|
||||||
{ "root", &host->root },
|
{ "root", &route->root },
|
||||||
{ "index", &host->index },
|
{ "index", &route->index },
|
||||||
};
|
};
|
||||||
struct {
|
struct {
|
||||||
char *name;
|
char *name;
|
||||||
bool *value;
|
bool *value;
|
||||||
} host_bvars[] = {
|
} route_bvars[] = {
|
||||||
{ "autoindex", &host->autoindex },
|
{ "autoindex", &route->autoindex },
|
||||||
};
|
};
|
||||||
|
|
||||||
for (size_t i = 0; i < sizeof(host_strvars) / sizeof(host_strvars[0]); ++i) {
|
for (size_t i = 0; i < sizeof(route_strvars) / sizeof(route_strvars[0]); ++i) {
|
||||||
if (strcmp(host_strvars[i].name, name) != 0) {
|
if (strcmp(route_strvars[i].name, name) != 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
*host_strvars[i].value = strdup(value);
|
*route_strvars[i].value = strdup(value);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (size_t i = 0; i < sizeof(host_bvars) / sizeof(host_bvars[0]); ++i) {
|
for (size_t i = 0; i < sizeof(route_bvars) / sizeof(route_bvars[0]); ++i) {
|
||||||
if (strcmp(host_bvars[i].name, name) != 0) {
|
if (strcmp(route_bvars[i].name, name) != 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
*host_bvars[i].value =
|
*route_bvars[i].value =
|
||||||
strcasecmp(value, "yes") == 0 ||
|
strcasecmp(value, "yes") == 0 ||
|
||||||
strcasecmp(value, "true") == 0 ||
|
strcasecmp(value, "true") == 0 ||
|
||||||
strcasecmp(value, "on") == 0;
|
strcasecmp(value, "on") == 0;
|
||||||
@ -233,8 +289,15 @@ config_finish(struct gmnisrv_config *conf)
|
|||||||
while (host) {
|
while (host) {
|
||||||
struct gmnisrv_host *next = host->next;
|
struct gmnisrv_host *next = host->next;
|
||||||
free(host->hostname);
|
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);
|
free(host);
|
||||||
host = next;
|
host = next;
|
||||||
}
|
}
|
||||||
|
53
src/serve.c
53
src/serve.c
@ -117,15 +117,60 @@ internal_error:
|
|||||||
goto exit;
|
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
|
void
|
||||||
serve_request(struct gmnisrv_client *client)
|
serve_request(struct gmnisrv_client *client)
|
||||||
{
|
{
|
||||||
struct gmnisrv_host *host = client->host;
|
struct gmnisrv_host *host = client->host;
|
||||||
assert(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];
|
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)) {
|
if ((size_t)n >= sizeof(path)) {
|
||||||
client_submit_response(client, GEMINI_STATUS_PERMANENT_FAILURE,
|
client_submit_response(client, GEMINI_STATUS_PERMANENT_FAILURE,
|
||||||
"Request path exceeds PATH_MAX", NULL);
|
"Request path exceeds PATH_MAX", NULL);
|
||||||
@ -142,12 +187,12 @@ serve_request(struct gmnisrv_client *client)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (S_ISDIR(st.st_mode)) {
|
if (S_ISDIR(st.st_mode)) {
|
||||||
if (host->autoindex) {
|
if (route->autoindex) {
|
||||||
serve_autoindex(client, path);
|
serve_autoindex(client, path);
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
strncat(path,
|
strncat(path,
|
||||||
host->index ? host->index : "index.gmi",
|
route->index ? route->index : "index.gmi",
|
||||||
sizeof(path) - 1);
|
sizeof(path) - 1);
|
||||||
}
|
}
|
||||||
} else if (S_ISLNK(st.st_mode)) {
|
} else if (S_ISLNK(st.st_mode)) {
|
||||||
|
Loading…
Reference in New Issue
Block a user