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);

Reply via email to