From 8baeb5a51c4dfa03956887ade2ef77295f17c95e Mon Sep 17 00:00:00 2001 From: Drew DeVault Date: Sun, 25 Oct 2020 21:46:01 -0400 Subject: Initial implementation of a routing table --- src/config.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++---------- src/serve.c | 53 ++++++++++++++++++++++++++++++++--- 2 files changed, 126 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/config.c b/src/config.c index 36060cf..693766e 100644 --- a/src/config.c +++ b/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; } diff --git a/src/serve.c b/src/serve.c index b798e7b..18c2993 100644 --- a/src/serve.c +++ b/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)) { -- cgit v1.2.3