forked from aniani/gmnisrv
Implement URL rewrites with regex capture groups
This commit is contained in:
parent
953039e0b1
commit
dc6e4e80c0
@ -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.
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
11
src/config.c
11
src/config.c
@ -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;
|
||||||
}
|
}
|
||||||
|
16
src/ini.c
16
src/ini.c
@ -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 */
|
||||||
|
123
src/serve.c
123
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
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user