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]+)\.(?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; }