On Tue, May 29, 2018 at 06:48:31PM +0200, Reyk Floeter wrote: > it's about time. > > server "default" { > listen on * port 80 > location match "/de/(.*)" { > request rewrite "/ch/%1" > } > } > > Tests? OK? >
I didn't handle the query in my previous diff. For example, a very typical rewrite to handle a slug: location match "/page/(%d+)/.*" { request rewrite "/static/index.php?id=%1" } The new diff below became very large, I will split it into 3 and resend them. Reyk Index: usr.sbin/httpd/config.c =================================================================== RCS file: /cvs/src/usr.sbin/httpd/config.c,v retrieving revision 1.54 diff -u -p -u -p -r1.54 config.c --- usr.sbin/httpd/config.c 19 May 2018 13:56:56 -0000 1.54 +++ usr.sbin/httpd/config.c 29 May 2018 21:41:55 -0000 @@ -476,6 +476,13 @@ config_getserver_config(struct httpd *en &parent->default_type, sizeof(struct media_type)); } + f = SRVFLAG_PATH_REWRITE|SRVFLAG_NO_PATH_REWRITE; + if ((srv_conf->flags & f) == 0) { + srv_conf->flags |= parent->flags & f; + (void)strlcpy(srv_conf->path, parent->path, + sizeof(srv_conf->path)); + } + f = SRVFLAG_SERVER_HSTS; srv_conf->flags |= parent->flags & f; srv_conf->hsts_max_age = parent->hsts_max_age; Index: usr.sbin/httpd/httpd.conf.5 =================================================================== RCS file: /cvs/src/usr.sbin/httpd/httpd.conf.5,v retrieving revision 1.95 diff -u -p -u -p -r1.95 httpd.conf.5 --- usr.sbin/httpd/httpd.conf.5 23 May 2018 19:02:50 -0000 1.95 +++ usr.sbin/httpd/httpd.conf.5 29 May 2018 21:41:55 -0000 @@ -198,6 +198,8 @@ argument can be used with return codes i .Sq Location: header for redirection to a specified URI. .Pp +It is possible to rewrite the request to redirect it to a different +external location. The .Ar uri may contain predefined macros that will be expanded at runtime: @@ -206,7 +208,7 @@ may contain predefined macros that will .It Ic $DOCUMENT_URI The request path. .It Ic $QUERY_STRING -The optional query string of the request. +The URL encoded query string of the request. .It Ic $REMOTE_ADDR The IP address of the connected client. .It Ic $REMOTE_PORT @@ -218,7 +220,7 @@ The request path and optional query stri .It Ic $SERVER_ADDR The configured IP address of the server. .It Ic $SERVER_PORT -The configured TCP server port of the server. +The configured TCP port of the server. .It Ic $SERVER_NAME The name of the server. .It Ic $HTTP_HOST @@ -396,10 +398,10 @@ the using pattern matching instead of shell globbing rules, see .Xr patterns 7 . -The pattern may contain captures that can be used in the -.Ar uri -of an enclosed +The pattern may contain captures that can be used in an enclosed .Ic block return +or +.Ic request rewrite option. .It Oo Ic no Oc Ic log Op Ar option Set the specified logging options. @@ -458,12 +460,31 @@ instead of the log files. Disable any previous .Ic block in a location. -.It Ic root Ar option -Configure the document root and options for the request path. +.It Ic request Ar option +Configure the options for the request path. Valid options are: .Bl -tag -width Ds -.It Ar directory -Set the document root of the server. +.It Oo Ic no Oc Ic rewrite Ar path +Enable or disable rewriting of the request. +Unlike the redirection with +.Ic block return , +this will change the request path internally before +.Nm httpd +makes a final decision about the matching location. +The +.Ar path +argument may contain predefined macros that will be expanded at runtime. +See the +.Ic block return +option for the list of supported macros. +.It Ic strip Ar number +Strip +.Ar number +path components from the beginning of the request path before looking +up the stripped-down path at the document root. +.El +.It Ic root Ar directory +Configure the document root of the server. The .Ar directory is a pathname within the @@ -472,12 +493,6 @@ root directory of .Nm httpd . If not specified, it defaults to .Pa /htdocs . -.It Ic strip Ar number -Strip -.Ar number -path components from the beginning of the request path before looking -up the stripped-down path at the document root. -.El .It Ic tcp Ar option Enable or disable the specified TCP/IP options; see .Xr tcp 4 @@ -715,6 +730,17 @@ server "example.com" { server "www.example.com" { listen on 10.0.0.1 port 80 +} +.Ed +The request can also be rewritten with the +.Ic request rewrite +directive: +.Bd -literal -offset indent +server "example.com" { + listen on * port 80 + location match "/old/(.*)" { + request rewrite "/new/%1" + } } .Ed .Sh SEE ALSO Index: usr.sbin/httpd/httpd.h =================================================================== RCS file: /cvs/src/usr.sbin/httpd/httpd.h,v retrieving revision 1.137 diff -u -p -u -p -r1.137 httpd.h --- usr.sbin/httpd/httpd.h 19 May 2018 13:56:56 -0000 1.137 +++ usr.sbin/httpd/httpd.h 29 May 2018 21:41:55 -0000 @@ -398,13 +398,15 @@ SPLAY_HEAD(client_tree, client); #define SRVFLAG_SERVER_MATCH 0x00200000 #define SRVFLAG_SERVER_HSTS 0x00400000 #define SRVFLAG_DEFAULT_TYPE 0x00800000 +#define SRVFLAG_PATH_REWRITE 0x01000000 +#define SRVFLAG_NO_PATH_REWRITE 0x02000000 #define SRVFLAG_BITS \ "\10\01INDEX\02NO_INDEX\03AUTO_INDEX\04NO_AUTO_INDEX" \ "\05ROOT\06LOCATION\07FCGI\10NO_FCGI\11LOG\12NO_LOG\13SOCKET" \ "\14SYSLOG\15NO_SYSLOG\16TLS\17ACCESS_LOG\20ERROR_LOG" \ "\21AUTH\22NO_AUTH\23BLOCK\24NO_BLOCK\25LOCATION_MATCH" \ - "\26SERVER_MATCH\27SERVER_HSTS\30DEFAULT_TYPE" + "\26SERVER_MATCH\27SERVER_HSTS\30DEFAULT_TYPE\31PATH\32NO_PATH" #define TCPFLAG_NODELAY 0x01 #define TCPFLAG_NNODELAY 0x02 @@ -470,8 +472,9 @@ struct server_config { uint32_t parent_id; char name[HOST_NAME_MAX+1]; char location[HTTPD_LOCATION_MAX]; - char index[PATH_MAX]; char root[PATH_MAX]; + char path[PATH_MAX]; + char index[PATH_MAX]; char socket[PATH_MAX]; char accesslog[PATH_MAX]; char errorlog[PATH_MAX]; Index: usr.sbin/httpd/parse.y =================================================================== RCS file: /cvs/src/usr.sbin/httpd/parse.y,v retrieving revision 1.99 diff -u -p -u -p -r1.99 parse.y --- usr.sbin/httpd/parse.y 23 May 2018 19:11:48 -0000 1.99 +++ usr.sbin/httpd/parse.y 29 May 2018 21:41:56 -0000 @@ -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 PASS REWRITE %token CA CLIENT CRL OPTIONAL %token <v.string> STRING %token <v.number> NUMBER @@ -486,6 +486,7 @@ serveroptsl : LISTEN ON STRING opttls po YYERROR; } } + | request | root | directory | logformat @@ -804,7 +805,33 @@ rootflags : STRING { free($1); srv->srv_conf.flags |= SRVFLAG_ROOT; } - | STRIP NUMBER { + ; + +request : REQUEST requestflags + | REQUEST '{' optnl requestflags_l '}' + ; + +requestflags_l : requestflags optcommanl requestflags_l + | requestflags optnl + ; + +requestflags : REWRITE STRING { + if (strlcpy(srv->srv_conf.path, $2, + sizeof(srv->srv_conf.path)) >= + sizeof(srv->srv_conf.path)) { + yyerror("request path too long"); + free($2); + YYERROR; + } + free($2); + srv->srv_conf.flags |= SRVFLAG_PATH_REWRITE; + srv->srv_conf.flags &= ~SRVFLAG_NO_PATH_REWRITE; + } + | NO REWRITE { + srv->srv_conf.flags |= SRVFLAG_NO_PATH_REWRITE; + srv->srv_conf.flags &= ~SRVFLAG_PATH_REWRITE; + } + | STRIP NUMBER { if ($2 < 0 || $2 > INT_MAX) { yyerror("invalid strip number"); YYERROR; @@ -1261,6 +1288,7 @@ lookup(char *s) { "request", REQUEST }, { "requests", REQUESTS }, { "return", RETURN }, + { "rewrite", REWRITE }, { "root", ROOT }, { "sack", SACK }, { "server", SERVER }, Index: usr.sbin/httpd/server_http.c =================================================================== RCS file: /cvs/src/usr.sbin/httpd/server_http.c,v retrieving revision 1.119 diff -u -p -u -p -r1.119 server_http.c --- usr.sbin/httpd/server_http.c 6 Apr 2018 13:02:07 -0000 1.119 +++ usr.sbin/httpd/server_http.c 29 May 2018 21:41:56 -0000 @@ -1,7 +1,7 @@ /* $OpenBSD: server_http.c,v 1.119 2018/04/06 13:02:07 florian Exp $ */ /* - * Copyright (c) 2006 - 2017 Reyk Floeter <r...@openbsd.org> + * Copyright (c) 2006 - 2018 Reyk Floeter <r...@openbsd.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -1023,7 +1023,7 @@ server_expand_http(struct client *clt, c { struct http_descriptor *desc = clt->clt_descreq; struct server_config *srv_conf = clt->clt_srv_conf; - char ibuf[128], *str, *path, *query; + char ibuf[128], *str, *path; const char *errstr = NULL, *p; size_t size; int n, ret; @@ -1067,10 +1067,8 @@ server_expand_http(struct client *clt, c if (desc->http_query == NULL) { ret = expand_string(buf, len, "$QUERY_STRING", ""); } else { - if ((query = url_encode(desc->http_query)) == NULL) - return (NULL); - ret = expand_string(buf, len, "$QUERY_STRING", query); - free(query); + ret = expand_string(buf, len, "$QUERY_STRING", + desc->http_query); } if (ret != 0) return (NULL); @@ -1119,13 +1117,8 @@ server_expand_http(struct client *clt, c if (desc->http_query == NULL) { str = path; } else { - if ((query = url_encode(desc->http_query)) == NULL) { - free(path); - return (NULL); - } - ret = asprintf(&str, "%s?%s", path, query); + ret = asprintf(&str, "%s?%s", path, desc->http_query); free(path); - free(query); if (ret == -1) return (NULL); } @@ -1177,13 +1170,16 @@ server_response(struct httpd *httpd, str struct kv *kv, key, *host; struct str_find sm; int portval = -1, ret; - char *hostval; + char *hostval, *query; const char *errstr = NULL; - /* Canonicalize the request path */ + /* Decode the URL */ if (desc->http_path == NULL || - url_decode(desc->http_path) == NULL || - canonicalize_path(desc->http_path, path, sizeof(path)) == NULL) + url_decode(desc->http_path) == NULL) + goto fail; + + /* Canonicalize the request path */ + if (canonicalize_path(desc->http_path, path, sizeof(path)) == NULL) goto fail; free(desc->http_path); if ((desc->http_path = strdup(path)) == NULL) @@ -1287,6 +1283,42 @@ server_response(struct httpd *httpd, str /* Now search for the location */ srv_conf = server_getlocation(clt, desc->http_path); + /* Optional rewrite */ + if (srv_conf->flags & SRVFLAG_PATH_REWRITE) { + /* Expand macros */ + if (server_expand_http(clt, srv_conf->path, + path, sizeof(path)) == NULL) + goto fail; + + /* + * Reset and update the query. The updated query must already + * be URL encoded - either specified by the user or by using the + * original $QUERY_STRING. + */ + free(desc->http_query); + desc->http_query = NULL; + if ((query = strchr(path, '?')) != NULL) { + *query++ = '\0'; + if ((desc->http_query = strdup(query)) == NULL) + goto fail; + } + + /* Canonicalize the updated request path */ + if (canonicalize_path(path, + path, sizeof(path)) == NULL) + goto fail; + + log_debug("%s: rewrote %s -> %s?%s", __func__, + desc->http_path, path, desc->http_query); + + free(desc->http_path); + if ((desc->http_path = strdup(path)) == NULL) + goto fail; + + /* Now search for the updated location */ + srv_conf = server_getlocation(clt, desc->http_path); + } + if (srv_conf->flags & SRVFLAG_BLOCK) { server_abort_http(clt, srv_conf->return_code, srv_conf->return_uri); @@ -1591,7 +1623,6 @@ server_log_http(struct client *clt, unsi int ret = -1; char *user = NULL; char *path = NULL; - char *query = NULL; char *version = NULL; char *referrer_v = NULL; char *agent_v = NULL; @@ -1635,9 +1666,6 @@ server_log_http(struct client *clt, unsi if (desc->http_path && (path = url_encode(desc->http_path)) == NULL) goto done; - if (desc->http_query && - (query = url_encode(desc->http_query)) == NULL) - goto done; ret = evbuffer_add_printf(clt->clt_log, "%s %s - %s [%s] \"%s %s%s%s%s%s\" %03d %zu\n", @@ -1646,7 +1674,7 @@ server_log_http(struct client *clt, unsi server_httpmethod_byid(desc->http_method), desc->http_path == NULL ? "" : path, desc->http_query == NULL ? "" : "?", - desc->http_query == NULL ? "" : query, + desc->http_query == NULL ? "" : desc->http_query, desc->http_version == NULL ? "" : " ", desc->http_version == NULL ? "" : version, code, len); @@ -1679,9 +1707,6 @@ server_log_http(struct client *clt, unsi if (desc->http_path && (path = url_encode(desc->http_path)) == NULL) goto done; - if (desc->http_query && - (query = url_encode(desc->http_query)) == NULL) - goto done; if (referrer && (referrer_v = url_encode(referrer->kv_value)) == NULL) goto done; @@ -1694,7 +1719,7 @@ server_log_http(struct client *clt, unsi server_httpmethod_byid(desc->http_method), desc->http_path == NULL ? "" : path, desc->http_query == NULL ? "" : "?", - desc->http_query == NULL ? "" : query, + desc->http_query == NULL ? "" : desc->http_query, desc->http_version == NULL ? "" : " ", desc->http_version == NULL ? "" : version, code, len, @@ -1718,7 +1743,6 @@ server_log_http(struct client *clt, unsi done: free(user); free(path); - free(query); free(version); free(referrer_v); free(agent_v);