My website generator is a little stupid at times. It generates files with .html suffixes, but urls without them.
I worked around this with some redirects, but it never felt quite right doing an extra round trip. Therefore, I added internal redirects, processing the rewrite before responding to an http request. This introduces new syntax to the config file, allowing you to do: location match "^(/foo/bar/[%w]+)$" { rewrite-to "/baz/%1.html" } Because we don't know what the paths should be relative to, all paths rewritten must be absolute. It seems like someone else may find it useful[1], so I'm submitting it. I've been running a slightly older version of this on https://myrlang.org for the last day or two, and it's been uneventful. The difference is that the syntax used to piggy back off the "block" action => 'block internal return 302 "path"'. This doesn't currently support chained rewrites. I think that it wouldn't be hard to add if it's needed. Ok? [1] https://github.com/reyk/httpd/issues/27 ========================================== diff --git usr.sbin/httpd/config.c usr.sbin/httpd/config.c index 3c31c3d4cd3..7d2982af7b9 100644 --- usr.sbin/httpd/config.c +++ usr.sbin/httpd/config.c @@ -448,7 +448,7 @@ config_getserver_config(struct httpd *env, struct server *srv, sizeof(srv_conf->errorlog)); } - f = SRVFLAG_BLOCK|SRVFLAG_NO_BLOCK; + f = SRVFLAG_BLOCK|SRVFLAG_NO_BLOCK|SRVFLAG_REWRITE; if ((srv_conf->flags & f) == 0) { free(srv_conf->return_uri); srv_conf->flags |= parent->flags & f; diff --git usr.sbin/httpd/httpd.conf.5 usr.sbin/httpd/httpd.conf.5 index a3c97629de3..3a00a750537 100644 --- usr.sbin/httpd/httpd.conf.5 +++ usr.sbin/httpd/httpd.conf.5 @@ -454,6 +454,14 @@ instead of the log files. Disable any previous .Ic block in a location. +.It Ic rewrite-to Ar path +The current request path is rewritten to +.Ar path . +using the same macro expansions as +.Cm block return +rules. After macros are substituted, the resulting paths must be +absolute, beginning with a slash. Rewriting is not done +recursively. .It Ic root Ar option Configure the document root and options for the request path. Valid options are: diff --git usr.sbin/httpd/httpd.h usr.sbin/httpd/httpd.h index 05cbb8e3550..477115ec92d 100644 --- usr.sbin/httpd/httpd.h +++ usr.sbin/httpd/httpd.h @@ -394,6 +394,7 @@ SPLAY_HEAD(client_tree, client); #define SRVFLAG_SERVER_MATCH 0x00200000 #define SRVFLAG_SERVER_HSTS 0x00400000 #define SRVFLAG_DEFAULT_TYPE 0x00800000 +#define SRVFLAG_REWRITE 0x01000000 #define SRVFLAG_BITS \ "\10\01INDEX\02NO_INDEX\03AUTO_INDEX\04NO_AUTO_INDEX" \ diff --git usr.sbin/httpd/parse.y usr.sbin/httpd/parse.y index fcf1938c42d..4072ee5b532 100644 --- usr.sbin/httpd/parse.y +++ usr.sbin/httpd/parse.y @@ -134,7 +134,7 @@ typedef struct { %token LISTEN LOCATION LOG LOGDIR MATCH MAXIMUM NO NODELAY OCSP ON PORT PREFORK %token PROTOCOLS REQUESTS ROOT SACK SERVER SOCKET STRIP STYLE SYSLOG TCP TICKET %token TIMEOUT TLS TYPE TYPES HSTS MAXAGE SUBDOMAINS DEFAULT PRELOAD REQUEST -%token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS +%token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN REWRITE PASS %token <v.string> STRING %token <v.number> NUMBER %type <v.port> port @@ -986,6 +986,11 @@ filter : block RETURN NUMBER optstring { srv_conf->return_uri_len = strlen($4) + 1; } } + | REWRITE STRING { + srv_conf->flags |= SRVFLAG_REWRITE; + srv_conf->return_uri = $2; + srv_conf->return_uri_len = strlen($2) + 1; + } | block DROP { /* No return code, silently drop the connection */ srv_conf->return_code = 0; @@ -1255,6 +1260,7 @@ lookup(char *s) { "request", REQUEST }, { "requests", REQUESTS }, { "return", RETURN }, + { "rewrite-to", REWRITE }, { "root", ROOT }, { "sack", SACK }, { "server", SERVER }, diff --git usr.sbin/httpd/server_http.c usr.sbin/httpd/server_http.c index e64de0d2f9c..c9ea4771037 100644 --- usr.sbin/httpd/server_http.c +++ usr.sbin/httpd/server_http.c @@ -1162,10 +1162,34 @@ server_expand_http(struct client *clt, const char *val, char *buf, return (buf); } +static int +server_set_path(struct http_descriptor *desc, char *input) +{ + char path[PATH_MAX]; + + if (input == NULL || url_decode(input) == NULL) + return -1; + if (canonicalize_path(input, path, sizeof(path)) == NULL) + return (-1); + free(desc->http_path); + if ((desc->http_path = strdup(path)) == NULL) + return(-1); + return (0); +} + +static int +server_rewrite(struct client *clt, struct http_descriptor *desc, char *input) +{ + char path[PATH_MAX]; + + if (server_expand_http(clt, input, path, sizeof(path)) == NULL) + return -1; + return (server_set_path(desc, path)); +} + int server_response(struct httpd *httpd, struct client *clt) { - char path[PATH_MAX]; char hostname[HOST_NAME_MAX+1]; struct http_descriptor *desc = clt->clt_descreq; struct http_descriptor *resp = clt->clt_descresp; @@ -1178,12 +1202,7 @@ server_response(struct httpd *httpd, struct client *clt) const char *errstr = NULL; /* Canonicalize the request path */ - if (desc->http_path == NULL || - url_decode(desc->http_path) == NULL || - canonicalize_path(desc->http_path, path, sizeof(path)) == NULL) - goto fail; - free(desc->http_path); - if ((desc->http_path = strdup(path)) == NULL) + if (server_set_path(desc, desc->http_path) == -1) goto fail; key.kv_key = "Host"; @@ -1284,6 +1303,10 @@ server_response(struct httpd *httpd, struct client *clt) /* Now search for the location */ srv_conf = server_getlocation(clt, desc->http_path); + /* If we have an internal redirection, rewrite the URL */ + if (srv_conf->flags & SRVFLAG_REWRITE) + if (server_rewrite(clt, desc, srv_conf->return_uri) == -1) + goto fail; if (srv_conf->flags & SRVFLAG_BLOCK) { server_abort_http(clt, srv_conf->return_code, srv_conf->return_uri);