1
0
forked from aniani/gmnisrv

Implement URL rewrites with regex capture groups

This commit is contained in:
Drew DeVault 2020-11-01 11:19:51 -05:00
parent 953039e0b1
commit dc6e4e80c0
6 changed files with 151 additions and 28 deletions

View File

@ -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, *[example.org:/foo]* with the root set to /srv/gemini,
/srv/gemini/foo/bar.txt will be served. /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* *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
that a request for this host does not include the filename part. that a request for this host does not include the filename part.

View File

@ -26,6 +26,7 @@ struct gmnisrv_route {
char *root; char *root;
char *index; char *index;
char *rewrite;
bool autoindex; bool autoindex;
bool cgi; bool cgi;

View File

@ -55,16 +55,6 @@ int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
#define INI_ALLOW_BOM 1 #define INI_ALLOW_BOM 1
#endif #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). */ /* Nonzero to use stack, zero to use heap (malloc/free). */
#ifndef INI_USE_STACK #ifndef INI_USE_STACK
#define INI_USE_STACK 1 #define INI_USE_STACK 1

View File

@ -213,6 +213,7 @@ conf_ini_handler(void *user, const char *section,
char *name; char *name;
char **value; char **value;
} route_strvars[] = { } route_strvars[] = {
{ "rewrite", &route->rewrite },
{ "root", &route->root }, { "root", &route->root },
{ "index", &route->index }, { "index", &route->index },
}; };
@ -228,6 +229,11 @@ conf_ini_handler(void *user, const char *section,
if (strcmp(route_strvars[i].name, name) != 0) { if (strcmp(route_strvars[i].name, name) != 0) {
continue; 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); *route_strvars[i].value = strdup(value);
return 1; return 1;
} }
@ -315,9 +321,10 @@ config_finish(struct gmnisrv_config *conf)
} }
struct gmnisrv_route *rnext = route->next; struct gmnisrv_route *rnext = route->next;
free(route->spec);
free(route->root);
free(route->index); free(route->index);
free(route->rewrite);
free(route->root);
free(route->spec);
free(route); free(route);
route = rnext; route = rnext;
} }

View File

@ -45,18 +45,9 @@ static char* lskip(const char* s)
be prefixed by a whitespace character to register as a comment. */ be prefixed by a whitespace character to register as a comment. */
static char* find_chars_or_comment(const char* s, const char* chars) 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))) { while (*s && (!chars || !strchr(chars, *s))) {
s++; s++;
} }
#endif
return (char*)s; return (char*)s;
} }
@ -123,7 +114,7 @@ int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
#endif #endif
else if (*start == '[') { else if (*start == '[') {
/* A "[section]" line */ /* A "[section]" line */
end = find_chars_or_comment(start + 1, "]"); end = strrchr(start + 1, ']');
if (*end == ']') { if (*end == ']') {
*end = '\0'; *end = '\0';
strncpy0(section, start + 1, sizeof(section)); strncpy0(section, start + 1, sizeof(section));
@ -141,11 +132,6 @@ int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler,
*end = '\0'; *end = '\0';
name = rstrip(start); name = rstrip(start);
value = lskip(end + 1); value = lskip(end + 1);
#if INI_ALLOW_INLINE_COMMENTS
end = find_chars_or_comment(value, NULL);
if (*end)
*end = '\0';
#endif
rstrip(value); rstrip(value);
/* Valid name[=:]value pair found, call handler */ /* Valid name[=:]value pair found, call handler */

View File

@ -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 static bool
route_match(struct gmnisrv_route *route, const char *path, char **revised) 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); free(capture);
return false; return false;
} }
*revised = strdup(path); *revised = process_rewrites(route, path, capture, ncapture);
free(capture);
return true; return true;
} }