summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDrew DeVault <sir@cmpwn.com>2020-11-01 11:19:51 -0500
committerDrew DeVault <sir@cmpwn.com>2020-11-01 11:19:51 -0500
commitdc6e4e80c0b0a3950594e32db7cff1b2db24d75c (patch)
treeddbec0d5be083daa32f05acdcce25a31489ec999
parent953039e0b15df9c119b70eb18f36e580a4c4e7d4 (diff)
downloadgmnisrv-dc6e4e80c0b0a3950594e32db7cff1b2db24d75c.tar.gz
gmnisrv-dc6e4e80c0b0a3950594e32db7cff1b2db24d75c.tar.xz
gmnisrv-dc6e4e80c0b0a3950594e32db7cff1b2db24d75c.zip
Implement URL rewrites with regex capture groups
-rw-r--r--doc/gmnisrvini.scd18
-rw-r--r--include/config.h1
-rw-r--r--include/ini.h10
-rw-r--r--src/config.c11
-rw-r--r--src/ini.c16
-rw-r--r--src/serve.c123
6 files changed, 151 insertions, 28 deletions
diff --git a/doc/gmnisrvini.scd b/doc/gmnisrvini.scd
index 5b936e5..78c80be 100644
--- a/doc/gmnisrvini.scd
+++ b/doc/gmnisrvini.scd
@@ -91,6 +91,24 @@ Within each routing section, the following keys are used to configure how
*[example.org:/foo]* with the root set to /srv/gemini,
/srv/gemini/foo/bar.txt will be served.
+*rewrite*
+ If regular expression routing is used, the rewrite directive may be used
+ to rewrite the URL path component before proceeding. The URL will be set
+ to the value of the rewrite expression. If *\\N* appears in the rewrite
+ value, where *N* is a number, that capture group will be substituted for
+ *\\N*. If *\\{name}* appears, where *name* is a named capture group, it
+ will be substituted.
+
+ Example:
+
+ ```
+ [localhost~^/([a-zA-Z]+)\.(?<extension>png|jpg)$]
+ root=./root
+ rewrite=/images/\1.\{extension}
+ ```
+
+ This will rewrite a request for /example.png to /images/example.png.
+
*index*
Configures the name of the index file which shall be served in the event
that a request for this host does not include the filename part.
diff --git a/include/config.h b/include/config.h
index cf9f2a3..fc7e9fb 100644
--- a/include/config.h
+++ b/include/config.h
@@ -26,6 +26,7 @@ struct gmnisrv_route {
char *root;
char *index;
+ char *rewrite;
bool autoindex;
bool cgi;
diff --git a/include/ini.h b/include/ini.h
index 3757b83..81556cd 100644
--- a/include/ini.h
+++ b/include/ini.h
@@ -55,16 +55,6 @@ int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
#define INI_ALLOW_BOM 1
#endif
-/* Nonzero to allow inline comments (with valid inline comment characters
- specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match
- Python 3.2+ configparser behaviour. */
-#ifndef INI_ALLOW_INLINE_COMMENTS
-#define INI_ALLOW_INLINE_COMMENTS 1
-#endif
-#ifndef INI_INLINE_COMMENT_PREFIXES
-#define INI_INLINE_COMMENT_PREFIXES ";"
-#endif
-
/* Nonzero to use stack, zero to use heap (malloc/free). */
#ifndef INI_USE_STACK
#define INI_USE_STACK 1
diff --git a/src/config.c b/src/config.c
index ec767da..662d02e 100644
--- a/src/config.c
+++ b/src/config.c
@@ -213,6 +213,7 @@ conf_ini_handler(void *user, const char *section,
char *name;
char **value;
} route_strvars[] = {
+ { "rewrite", &route->rewrite },
{ "root", &route->root },
{ "index", &route->index },
};
@@ -228,6 +229,11 @@ conf_ini_handler(void *user, const char *section,
if (strcmp(route_strvars[i].name, name) != 0) {
continue;
}
+ if (strcmp(route_strvars[i].name, "rewrite") == 0
+ && routing != ROUTE_REGEX) {
+ fprintf(stderr, "rewrite directives are only valid for regex routes\n");
+ return 0;
+ }
*route_strvars[i].value = strdup(value);
return 1;
}
@@ -315,9 +321,10 @@ config_finish(struct gmnisrv_config *conf)
}
struct gmnisrv_route *rnext = route->next;
- free(route->spec);
- free(route->root);
free(route->index);
+ free(route->rewrite);
+ free(route->root);
+ free(route->spec);
free(route);
route = rnext;
}
diff --git a/src/ini.c b/src/ini.c
index 88ff0d1..a001101 100644
--- a/src/ini.c
+++ b/src/ini.c
@@ -45,18 +45,9 @@ static char* lskip(const char* s)
be prefixed by a whitespace character to register as a comment. */
static char* find_chars_or_comment(const char* s, const char* chars)
{
-#if INI_ALLOW_INLINE_COMMENTS
- int was_space = 0;
- while (*s && (!chars || !strchr(chars, *s)) &&
- !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) {
- was_space = isspace((unsigned char)(*s));
- s++;
- }
-#else
while (*s && (!chars || !strchr(chars, *s))) {
s++;
}
-#endif
return (char*)s;
}
@@ -123,7 +114,7 @@ int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
#endif
else if (*start == '[') {
/* A "[section]" line */
- end = find_chars_or_comment(start + 1, "]");
+ end = strrchr(start + 1, ']');
if (*end == ']') {
*end = '\0';
strncpy0(section, start + 1, sizeof(section));
@@ -141,11 +132,6 @@ int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
*end = '\0';
name = rstrip(start);
value = lskip(end + 1);
-#if INI_ALLOW_INLINE_COMMENTS
- end = find_chars_or_comment(value, NULL);
- if (*end)
- *end = '\0';
-#endif
rstrip(value);
/* Valid name[=:]value pair found, call handler */
diff --git a/src/serve.c b/src/serve.c
index 6d9d87d..74bb979 100644
--- a/src/serve.c
+++ b/src/serve.c
@@ -197,6 +197,126 @@ serve_cgi(struct gmnisrv_client *client, const char *path,
}
}
+static char *
+ensure_buf(char *buf, size_t *sz, size_t desired)
+{
+ while (*sz < desired) {
+ *sz = *sz * 2;
+ char *new = realloc(buf, *sz);
+ assert(new);
+ buf = new;
+ }
+ return buf;
+}
+
+static int
+get_group(struct gmnisrv_route *route, const char *name, int ncapture)
+{
+ const char *groupname = lre_get_groupnames(route->regex);
+ for (int i = 0; i < ncapture; groupname += strlen(groupname) + 1, ++i) {
+ if (strcmp(groupname, name) == 0) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static char *
+process_rewrites(struct gmnisrv_route *route, const char *path,
+ uint8_t **capture, int ncapture)
+{
+ if (!route->rewrite) {
+ return strdup(path);
+ }
+
+ size_t new_sz = strlen(path) * 2;
+ size_t new_ln = 0;
+ char *new = malloc(new_sz);
+ *new = '\0';
+
+ char *temp = strdup(route->rewrite);
+ char *rewrite = temp;
+ char *next;
+ do {
+ next = strchr(rewrite, '\\');
+
+ size_t len;
+ if (next) {
+ len = next - rewrite;
+ *next = '\0';
+ } else {
+ len = strlen(rewrite);
+ }
+
+ ensure_buf(new, &new_sz, new_ln + len);
+ strcat(new, rewrite);
+ new_ln += len;
+
+ if (!next) {
+ break;
+ }
+
+ int group;
+ char *endptr;
+ switch (next[1]) {
+ case '\0':
+ server_error("Misconfigured rewrite rule for route %s: expected capture group identifier",
+ route->spec);
+ return strdup(path);
+ case '{':;
+ char *rbrace = strchr(&next[1], '}');
+ if (!rbrace) {
+ server_error("Misconfigured rewrite rule for route %s: expected capture group terminator '}'", route->spec);
+ return strdup(path);
+ }
+ *rbrace = '\0';
+ group = get_group(route, &next[2], ncapture);
+ if (group == -1) {
+ server_error("Misconfigured rewrite rule for route %s: unknown capture group '%s'", route->spec, &next[2]);
+ return strdup(path);
+ }
+ ++group;
+ endptr = &rbrace[1];
+ goto subgroup;
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':;
+ group = (int)strtoul(&next[1], &endptr, 10);
+ if (group >= ncapture) {
+ server_error("Misconfigured rewrite rule for route %s: unknown capture group %d\n", group);
+ return strdup(path);
+ }
+subgroup:;
+ fprintf(stderr, "replace group %d\n", group);
+ char *start = (char *)capture[group * 2],
+ *end = (char *)capture[group * 2 + 1];
+
+ char c = *end;
+ *end = '\0';
+ len = strlen(start);
+ ensure_buf(new, &new_sz, new_ln + len);
+ strcat(new, start);
+ new_ln += len;
+
+ fprintf(stderr, "+%s = %s\n", start, new);
+ rewrite = endptr;
+ *end = c;
+ break;
+ }
+ } while (next);
+
+ free(temp);
+ fprintf(stderr, "rewritten: %s\n", new);
+ return new;
+}
+
static bool
route_match(struct gmnisrv_route *route, const char *path, char **revised)
{
@@ -227,7 +347,8 @@ route_match(struct gmnisrv_route *route, const char *path, char **revised)
free(capture);
return false;
}
- *revised = strdup(path);
+ *revised = process_rewrites(route, path, capture, ncapture);
+ free(capture);
return true;
}