I know a patch was sent to the list a few months ago to add URL rewriting support to httpd, but it doesn't seem to have made its way into the tree. It's unclear whether that's because URL rewriting support without using redirection is being considered featuritis, it fell through the cracks, or what, but I thought I'd propose an alternative that is simpler in terms of functionality and mimics the behavior of the "block return code uri" option.
Note that query strings in requests are ignored when using the new "pass rewrite uri" option in order to make the option match the "block return code uri" one. This means that a shim is needed when using query string variables from requests since the $QUERY_STRING macro is encoded when it gets expanded. I don't know what the best way to address this is, if anything, but three possibilities come to mind: 1) have something like a $RAW_QUERY_STRING macro, 2) always append the query string from the request, or 3) add an "[no] append query" option after the rewrite options. For what it's worth, Apache can be configured to either always ignore or always append the query string depending on (confusingly) the rewrite options and/or the value of the rewrite URL, and nginx always appends the request query string unless the rewrite URL ends in a "?". This diff needs the other diff I recently sent to the list to make the $DOCUMENT_URI macro work correctly when using FastCGI. For those interested, here's the thread containing the other patch that was sent a few months ago: http://marc.info/?t=144743058100004&r=1&w=2 Index: http.h =================================================================== RCS file: /cvs/src/usr.sbin/httpd/http.h,v retrieving revision 1.13 diff -u -p -r1.13 http.h --- http.h 11 Jun 2015 18:49:09 -0000 1.13 +++ http.h 14 Apr 2016 04:26:03 -0000 @@ -242,8 +242,9 @@ struct http_descriptor { int http_chunked; char *http_version; - /* Rewritten path remains NULL if not used */ + /* Rewritten path and query remain NULL if not used */ char *http_path_alias; + char *http_query_alias; /* A tree of headers and attached lists for repeated headers. */ struct kv *http_lastheader; Index: httpd.conf.5 =================================================================== RCS file: /cvs/src/usr.sbin/httpd/httpd.conf.5,v retrieving revision 1.68 diff -u -p -r1.68 httpd.conf.5 --- httpd.conf.5 19 Jul 2015 05:17:27 -0000 1.68 +++ httpd.conf.5 14 Apr 2016 04:26:03 -0000 @@ -319,6 +319,8 @@ The pattern may contain captures that ca .Ar uri of an enclosed .Ic block return +or +.Ic pass rewrite option. .It Oo Ic no Oc Ic log Op Ar option Set the specified logging options. @@ -373,10 +375,23 @@ Enable or disable logging to .Xr syslog 3 instead of the log files. .El -.It Ic pass +.It Ic pass Op Ic rewrite Ar uri Disable any previous .Ic block in a location. +If the optional +.Ar uri +argument is specified, +.Xr httpd 8 +will use +.Ar uri +instead of the matched location string when determining what +to load. +The +.Ar uri +may contain predefined macros as explained in the +.Ic block return +option. .It Ic root Ar option Configure the document root and options for the request path. Valid options are: Index: parse.y =================================================================== RCS file: /cvs/src/usr.sbin/httpd/parse.y,v retrieving revision 1.77 diff -u -p -r1.77 parse.y --- parse.y 22 Nov 2015 13:27:13 -0000 1.77 +++ parse.y 14 Apr 2016 04:26:03 -0000 @@ -134,7 +134,7 @@ typedef struct { %token LOCATION LOG LOGDIR MATCH MAXIMUM NO NODELAY ON PORT PREFORK PROTOCOLS %token REQUEST REQUESTS ROOT SACK SERVER SOCKET STRIP STYLE SYSLOG TCP TIMEOUT %token TLS TYPE TYPES HSTS MAXAGE SUBDOMAINS DEFAULT PRELOAD -%token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS +%token ERROR INCLUDE AUTHENTICATE WITH BLOCK DROP RETURN PASS REWRITE %token <v.string> STRING %token <v.number> NUMBER %type <v.port> port @@ -913,6 +913,8 @@ filter : block RETURN NUMBER optstring free($4); YYERROR; } + + free(srv_conf->return_uri); srv_conf->return_uri = $4; srv_conf->return_uri_len = strlen($4) + 1; } @@ -925,10 +927,14 @@ filter : block RETURN NUMBER optstring /* Forbidden */ srv_conf->return_code = 403; } - | PASS { - srv_conf->flags &= ~SRVFLAG_BLOCK; - srv_conf->flags |= SRVFLAG_NO_BLOCK; + | pass REWRITE optstring { + if ($3 != NULL) { + free(srv_conf->return_uri); + srv_conf->return_uri = $3; + srv_conf->return_uri_len = strlen($3) + 1; + } } + | pass ; block : BLOCK { @@ -937,6 +943,12 @@ block : BLOCK { } ; +pass : PASS { + srv_conf->flags &= ~SRVFLAG_BLOCK; + srv_conf->flags |= SRVFLAG_NO_BLOCK; + } + ; + optmatch : /* empty */ { $$ = 0; } | MATCH { $$ = 1; } ; @@ -1184,6 +1196,7 @@ lookup(char *s) { "request", REQUEST }, { "requests", REQUESTS }, { "return", RETURN }, + { "rewrite", REWRITE }, { "root", ROOT }, { "sack", SACK }, { "server", SERVER }, Index: server_fcgi.c =================================================================== RCS file: /cvs/src/usr.sbin/httpd/server_fcgi.c,v retrieving revision 1.67 diff -u -p -r1.67 server_fcgi.c --- server_fcgi.c 23 Nov 2015 20:56:15 -0000 1.67 +++ server_fcgi.c 14 Apr 2016 04:26:03 -0000 @@ -97,7 +97,7 @@ server_fcgi(struct httpd *env, struct cl int pathlen; int fd = -1, ret; const char *stripped, *p, *alias, *errstr = NULL; - char *str, *script = NULL; + char *queryalias, *str, *script = NULL; if (srv_conf->socket[0] == ':') { struct sockaddr_storage ss; @@ -194,6 +194,10 @@ server_fcgi(struct httpd *env, struct cl ? desc->http_path_alias : desc->http_path; + queryalias = desc->http_query_alias != NULL + ? desc->http_query_alias + : desc->http_query; + stripped = server_root_strip(alias, srv_conf->strip); if ((pathlen = asprintf(&script, "%s%s", srv_conf->root, stripped)) == -1) { @@ -242,8 +246,8 @@ server_fcgi(struct httpd *env, struct cl goto fail; } - if (desc->http_query) - if (fcgi_add_param(¶m, "QUERY_STRING", desc->http_query, + if (queryalias) + if (fcgi_add_param(¶m, "QUERY_STRING", queryalias, clt) == -1) { errstr = "failed to encode param"; goto fail; Index: server_http.c =================================================================== RCS file: /cvs/src/usr.sbin/httpd/server_http.c,v retrieving revision 1.106 diff -u -p -r1.106 server_http.c --- server_http.c 8 Mar 2016 09:33:15 -0000 1.106 +++ server_http.c 14 Apr 2016 04:26:03 -0000 @@ -1040,6 +1040,7 @@ server_response(struct httpd *httpd, str { char path[PATH_MAX]; char hostname[HOST_NAME_MAX+1]; + char buf[IBUF_READ_SIZE]; struct http_descriptor *desc = clt->clt_descreq; struct http_descriptor *resp = clt->clt_descresp; struct server *srv = clt->clt_srv; @@ -1157,7 +1158,28 @@ server_response(struct httpd *httpd, str server_abort_http(clt, srv_conf->return_code, srv_conf->return_uri); return (-1); - } else if (srv_conf->flags & SRVFLAG_AUTH && + } else if (srv_conf->return_uri) { + memset(buf, 0, sizeof(buf)); + + if (server_expand_http(clt, srv_conf->return_uri, buf, + sizeof(buf)) == NULL) + goto fail; + + free(desc->http_path_alias); + if ((desc->http_path_alias = strdup(buf)) == NULL) + goto fail; + + free(desc->http_query_alias); + desc->http_query_alias = strchr(desc->http_path_alias, '?'); + if (desc->http_query_alias != NULL) { + *desc->http_query_alias++ = '\0'; + if ((desc->http_query_alias = + strdup(desc->http_query_alias)) == NULL) + goto fail; + } + } + + if (srv_conf->flags & SRVFLAG_AUTH && server_http_authenticate(srv_conf, clt) == -1) { server_abort_http(clt, 401, srv_conf->auth_realm); return (-1);