Hi,

I'd like to add an URI stripping option to httpd, which is similar to
apache/nginx's alias options:

root [strip number] directory
        Set the document root of the server.  The directory is a pathname
        within the chroot(2) root directory of httpd.  If not specified,
        it defaults to /htdocs.  If the strip option is set, number path
        components are stripped from the beginning of the request URI
        before looking up the stripped-down URI at directory.


for example:

location "/pub/OpenBSD/snapshots/amd64*" {
        root strip 4 "/OpenBSD.amd64"
        directory auto index
}  


For serving php:

location "/wiki/" {
        root strip 1 "/dokuwiki"
        directory index "doku.php"
        fastcgi socket "/tmp/doku.sock"
}
location "/wiki/*.php" {
        root strip 1 "/dokuwiki"
        fastcgi socket "/tmp/doku.sock"
}
location "/wiki/lib/*" {
        root strip 1 "/dokuwiki"
        directory no index
}


Comments? OKs?

Christopher


diff --git httpd.conf.5 httpd.conf.5
index 788d0a9..7776131 100644
--- httpd.conf.5
+++ httpd.conf.5
@@ -229,7 +229,7 @@ Enable or disable logging to
 .Xr syslog 3
 instead of the log files.
 .El
-.It Ic root Ar directory
+.It Ic root Oo Ic strip Ar number Oc Ar directory
 Set the document root of the server.
 The
 .Ar directory
@@ -239,6 +239,11 @@ root directory of
 .Nm httpd .
 If not specified, it defaults to
 .Pa /htdocs .
+If the strip option is set,
+.Ar number
+path components are stripped from the beginning of the request URI before
+looking up the stripped-down URI at
+.Ar directory .
 .It Ic ssl Ar option
 Set the SSL configuration for the server.
 These options are only used if SSL has been enabled via the listen directive.
diff --git httpd.h httpd.h
index 04b1f05..45505a1 100644
--- httpd.h
+++ httpd.h
@@ -383,6 +383,7 @@ struct server_config {
        char                    *ssl_key_file;
 
        u_int16_t                flags;
+       u_int8_t                 strip;
        u_int8_t                 tcpflags;
        int                      tcpbufsiz;
        int                      tcpbacklog;
diff --git parse.y parse.y
index 44cf90c..70e1cf7 100644
--- parse.y
+++ parse.y
@@ -128,12 +128,13 @@ typedef struct {
 %token ACCESS AUTO BACKLOG BODY BUFFER CERTIFICATE CHROOT CIPHERS COMMON
 %token COMBINED CONNECTION DIRECTORY ERR FCGI INDEX IP KEY LISTEN LOCATION
 %token LOG MAXIMUM NO NODELAY ON PORT PREFORK REQUEST REQUESTS ROOT SACK
-%token SERVER SOCKET SSL STYLE SYSLOG TCP TIMEOUT TYPES
+%token SERVER SOCKET SSL STRIP STYLE SYSLOG TCP TIMEOUT TYPES
 %token ERROR INCLUDE
 %token <v.string>      STRING
 %token  <v.number>     NUMBER
 %type  <v.port>        port
 %type  <v.number>      optssl
+%type  <v.number>      optstrip
 %type  <v.tv>          timeout
 %type  <v.string>      numberstring
 
@@ -176,6 +177,10 @@ optssl             : /*empty*/     { $$ = 0; }
                | SSL           { $$ = 1; }
                ;
 
+optstrip       : /*empty*/     { $$ = 0; }
+               | STRIP NUMBER  { $$ = $2; }
+               ;
+
 main           : PREFORK NUMBER        {
                        if (loadcfg)
                                break;
@@ -333,16 +338,21 @@ serveroptsl       : LISTEN ON STRING optssl port {
                                YYERROR;
                        }
                } ssl
-               | ROOT STRING           {
-                       if (strlcpy(srv->srv_conf.root, $2,
+               | ROOT optstrip STRING          {
+                       if (strlcpy(srv->srv_conf.root, $3,
                            sizeof(srv->srv_conf.root)) >=
                            sizeof(srv->srv_conf.root)) {
                                yyerror("document root too long");
-                               free($2);
+                               free($3);
                                YYERROR;
                        }
-                       free($2);
+                       free($3);
                        srv->srv_conf.flags |= SRVFLAG_ROOT;
+                       if ($2 < 0 || $2 > UINT8_MAX) {
+                               yyerror("invalid strip number");
+                               YYERROR;
+                       }
+                       srv->srv_conf.strip = $2;
                }
                | DIRECTORY dirflags
                | DIRECTORY '{' dirflags_l '}'
@@ -848,6 +858,7 @@ lookup(char *s)
                { "server",             SERVER },
                { "socket",             SOCKET },
                { "ssl",                SSL },
+               { "strip",              STRIP },
                { "style",              STYLE },
                { "syslog",             SYSLOG },
                { "tcp",                TCP },
diff --git server_fcgi.c server_fcgi.c
index fe97be0..1d591b8 100644
--- server_fcgi.c
+++ server_fcgi.c
@@ -101,9 +101,12 @@ server_fcgi(struct httpd *env, struct client *clt)
        struct fcgi_begin_request_body  *begin;
        char                             hbuf[MAXHOSTNAMELEN];
        size_t                           scriptlen;
-       int                              pathlen;
+       int                              i, pathlen;
        int                              fd = -1, ret;
-       const char                      *errstr = NULL;
+       const char                      *stripped, *errstr = NULL;
+       const char                      *alias = desc->http_path_alias != NULL
+                                            ? desc->http_path_alias
+                                            : desc->http_path;
        char                            *str, *p, *script = NULL;
 
        if (srv_conf->socket[0] == ':') {
@@ -192,9 +195,14 @@ server_fcgi(struct httpd *env, struct client *clt)
        h->type = FCGI_PARAMS;
        h->content_len = param.total_len = 0;
 
-       if ((pathlen = asprintf(&script, "%s%s", srv_conf->root,
-           desc->http_path_alias != NULL ?
-           desc->http_path_alias : desc->http_path)) == -1) {
+       /* First strip leading directories. Leading '/' is ignored. */
+       stripped = alias;
+       i = srv_conf->strip;
+       while (i > 0 && *stripped != '\0') if (*++stripped == '/') i--;
+
+       /* Then prepend root. */
+       if ((pathlen = asprintf(&script, "%s%s", srv_conf->root, stripped))
+           == -1) {
                errstr = "failed to get script name";
                goto fail;
        }
@@ -214,9 +222,16 @@ server_fcgi(struct httpd *env, struct client *clt)
                }
                script[scriptlen] = '\0';
        }
-
-       if (fcgi_add_param(&param, "SCRIPT_NAME",
-           script + strlen(srv_conf->root), clt) == -1) {
+       /*
+        * calculate length of http SCRIPT_NAME:
+        * add stripped prefix length, remove local root length
+        */
+       scriptlen += (stripped - alias) - strlen(srv_conf->root);
+       if ((str = strndup(alias, scriptlen)) == NULL)
+               goto fail;
+       ret = fcgi_add_param(&param, "SCRIPT_NAME", str, clt);
+       free(str);
+       if (ret == -1) {
                errstr = "failed to encode param";
                goto fail;
        }
diff --git server_file.c server_file.c
index e8a487e..987f2b5 100644
--- server_file.c
+++ server_file.c
@@ -153,17 +153,24 @@ server_file(struct httpd *env, struct client *clt)
        struct http_descriptor  *desc = clt->clt_descreq;
        struct server_config    *srv_conf = clt->clt_srv_conf;
        char                     path[MAXPATHLEN];
-       const char              *errstr = NULL;
-       int                      ret = 500;
+       const char              *stripped, *errstr = NULL;
+       int                      i, ret = 500;
 
        if (srv_conf->flags & SRVFLAG_FCGI)
                return (server_fcgi(env, clt));
 
-       /* Request path is already canonicalized */
+       /*
+        * Request path is already canonicalized.
+        * First strip leading directories. Leading '/' is ignored.
+        */
+       stripped = desc->http_path_alias != NULL ?
+           desc->http_path_alias : desc->http_path;
+       i = srv_conf->strip;
+       while (i > 0 && *stripped != '\0') if (*++stripped == '/') i--;
+
+       /* Then prepend root. */
        if ((size_t)snprintf(path, sizeof(path), "%s%s",
-           srv_conf->root,
-           desc->http_path_alias != NULL ?
-           desc->http_path_alias : desc->http_path) >= sizeof(path)) {
+           srv_conf->root, stripped) >= sizeof(path)) {
                errstr = desc->http_path;
                goto abort;
        }
@@ -279,7 +286,7 @@ server_file_index(struct httpd *env, struct client *clt, 
struct stat *st)
        int                       code = 500;
        struct evbuffer          *evb = NULL;
        struct media_type        *media;
-       const char               *style;
+       const char               *stripped, *style;
        struct tm                 tm;
        time_t                    t, dir_mtime;
 
@@ -288,9 +295,17 @@ server_file_index(struct httpd *env, struct client *clt, 
struct stat *st)
                goto abort;
        }
 
-       /* Request path is already canonicalized */
+       /*
+        * Request path is already canonicalized.
+        * First strip leading directories. Leading '/' is ignored.
+        */
+       stripped = desc->http_path;
+       i = srv_conf->strip;
+       while (i > 0 && *stripped != '\0') if (*++stripped == '/') i--;
+
+       /* Then prepend root. */
        if ((size_t)snprintf(path, sizeof(path), "%s%s",
-           srv_conf->root, desc->http_path) >= sizeof(path))
+           srv_conf->root, stripped) >= sizeof(path))
                goto abort;
 
        /* Now open the file, should be readable or we have another problem */


-- 
http://gmerlin.de
OpenPGP: http://gmerlin.de/christopher.pub
F190 D013 8F01 AA53 E080  3F3C F17F B0A1 D44E 4FEE

Reply via email to