]> git.ipfire.org Git - thirdparty/apache/httpd.git/commitdiff
core: Allow an optional expression to be specified for an effective
authorGraham Leggett <minfrin@apache.org>
Mon, 17 Jan 2022 16:10:51 +0000 (16:10 +0000)
committerGraham Leggett <minfrin@apache.org>
Mon, 17 Jan 2022 16:10:51 +0000 (16:10 +0000)
path in the DirectoryMatch and LocationMatch directives. This allows
modules like mod_dav to map URLs to URL spaces or to directories on
the filesystem.

git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1897156 13f79535-47bb-0310-9956-ffa450edef68

CHANGES
docs/log-message-tags/next-number
docs/manual/mod/core.xml
modules/dav/main/mod_dav.c
server/core.c

diff --git a/CHANGES b/CHANGES
index de3bbe22d627de0a0845fbb9fba6a568a456b8f3..cc04ee13e4368c2833efc5da037d9666568fca68 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,11 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache 2.5.1
 
+  *) core: Allow an optional expression to be specified for an effective
+     path in the DirectoryMatch and LocationMatch directives. This allows
+     modules like mod_dav to map URLs to URL spaces or to directories on
+     the filesystem. [Graham Leggett]
+
   *) http: Enforce that fully qualified uri-paths not to be forward-proxied
      have an http(s) scheme, and that the ones to be forward proxied have a
      hostname, per HTTP specifications.  [Ruediger Pluem, Yann Ylavic]
index 4ca47544e382538656859c8e045e58f8d4ce539e..427e445179fec18cd9642173f936caabbcbfb3e1 100644 (file)
@@ -1 +1 @@
-10367
+10369
index e22545e8cfe8f5cc9532ade0011e3ea6c17af8cb..1b981071464a90a2d079c63c35f2e6b2c0cc3003 100644 (file)
@@ -926,6 +926,20 @@ named file-system directory, sub-directories, and their contents.</description>
     the corresponding <directive type="section">Directory</directive> will
     be applied.</p>
 
+    <p>Some modules require the directory-path prefix in order to do their work,
+    and when a regular expression is provided the directory-path is no longer
+    available. From 2.5.1 onwards, an expression can be specified in addition
+    to the regular expression that resolves to the directory-path prefix. This
+    can allow complex mappings from the URL space to an effective directory.
+    This funcionality is identical to that provided by the
+    <directive>DirectoryMatch</directive> directive below.</p>
+
+    <highlight language="config">
+&lt;Directory ~ /home/%{env:MATCH_PARTITIONNAME}/dav/ ^/dav/(?&lt;PARTITIONNAME&gt;[^/]+)/&gt;
+    Dav on
+&lt;/Directory&gt;
+    </highlight>
+
    <p><strong>Note that the default access for
     <code>&lt;Directory "/"&gt;</code> is to permit all access.
     This means that Apache httpd will serve any file mapped from an URL. It is
@@ -959,7 +973,7 @@ named file-system directory, sub-directories, and their contents.</description>
 <name>DirectoryMatch</name>
 <description>Enclose directives that apply to
 the contents of file-system directories matching a regular expression.</description>
-<syntax>&lt;DirectoryMatch <var>regex</var>&gt;
+<syntax>&lt;DirectoryMatch [<var>expr</var>] <var>regex</var>&gt;
 ... &lt;/DirectoryMatch&gt;</syntax>
 <contextlist><context>server config</context><context>virtual host</context>
 </contextlist>
@@ -1005,6 +1019,18 @@ the contents of file-system directories matching a regular expression.</descript
     <highlight language="config">
 &lt;DirectoryMatch "^/var/www/combined/(?&lt;sitename&gt;[^/]+)"&gt;
     Require ldap-group cn=%{env:MATCH_SITENAME},ou=combined,o=Example
+&lt;/DirectoryMatch&gt;
+    </highlight>
+
+    <p>Some modules require the directory-path prefix in order to do their work,
+    and when a regular expression is provided the directory-path is no longer
+    available. From 2.5.1 onwards, an expression can be specified in addition
+    to the regular expression that resolves to the directory-path prefix. This
+    can allow complex mappings from the URL space to an effective directory.</p>
+
+    <highlight language="config">
+&lt;DirectoryMatch /home/%{env:MATCH_PARTITIONNAME}/dav/ ^/dav/(?&lt;PARTITIONNAME&gt;[^/]+)/&gt;
+    Dav on
 &lt;/DirectoryMatch&gt;
     </highlight>
 </usage>
@@ -3172,6 +3198,19 @@ URLs</description>
 &lt;/Location&gt;
     </highlight>
 
+    <p>Some modules require the URL-path prefix in order to do their work, and when
+    a regular expression is provided the URL-path is no longer available. From
+    2.5.1 onwards, an expression can be specified in addition to the regular
+    expression that resolves to the URL-path prefix. This can allow complex
+    mappings from the URL space to an effective path. This funcionality is identical
+    to that provided by the <directive>LocationMatch</directive> directive below.</p>
+
+    <highlight language="config">
+&lt;Location ~ /dav/%{env:MATCH_PARTITIONNAME} ^/dav/(?&lt;PARTITIONNAME&gt;[^/]+)/&gt;
+    Dav on
+&lt;/Location&gt;
+    </highlight>
+
     <note><title>Note about / (slash)</title>
       <p>The slash character has special meaning depending on where in a
       URL it appears. People may be used to its behavior in the filesystem
@@ -3207,7 +3246,7 @@ URLs</description>
 <description>Applies the enclosed directives only to regular-expression
 matching URLs</description>
 <syntax>&lt;LocationMatch
-    <var>regex</var>&gt; ... &lt;/LocationMatch&gt;</syntax>
+    [<var>expr</var>] <var>regex</var>&gt; ... &lt;/LocationMatch&gt;</syntax>
 <contextlist><context>server config</context><context>virtual host</context>
 </contextlist>
 
@@ -3250,6 +3289,18 @@ matching URLs</description>
 &lt;/LocationMatch&gt;
     </highlight>
 
+    <p>Some modules require the URL-path prefix in order to do their work, and when
+    a regular expression is provided the URL-path is no longer available. From
+    2.5.1 onwards, an expression can be specified in addition to the regular
+    expression that resolves to the URL-path prefix. This can allow complex
+    mappings from the URL space to an effective path.</p>
+
+    <highlight language="config">
+&lt;LocationMatch /dav/%{env:MATCH_PARTITIONNAME} ^/dav/(?&lt;PARTITIONNAME&gt;[^/]+)/&gt;
+    Dav on
+&lt;/LocationMatch&gt;
+    </highlight>
+
      <note><title>Note about / (slash)</title>
       <p>The slash character has special meaning depending on where in a
       URL it appears. People may be used to its behavior in the filesystem
index 1a4b0663f072636be65c1df97986ceb6fb3de124..080fb26cf08d0ccf0653814462270bde2077a3bd 100644 (file)
@@ -83,6 +83,7 @@ typedef struct {
     const char *dir;
     int locktimeout;
     int allow_depthinfinity;
+    const ap_expr_info_t *dir_expr;
 
 } dav_dir_conf;
 
@@ -203,6 +204,7 @@ static void *dav_merge_dir_config(apr_pool_t *p, void *base, void *overrides)
     newconf->dir = DAV_INHERIT_VALUE(parent, child, dir);
     newconf->allow_depthinfinity = DAV_INHERIT_VALUE(parent, child,
                                                      allow_depthinfinity);
+    newconf->dir_expr = DAV_INHERIT_VALUE(parent, child, dir_expr);
 
     return newconf;
 }
@@ -282,6 +284,18 @@ static const char *dav_cmd_dav(cmd_parms *cmd, void *config, const char *arg1)
         }
     }
 
+    if (!conf->dir_expr) {
+        const char *expr_err = NULL;
+
+        conf->dir_expr = ap_expr_parse_cmd(cmd, conf->dir, AP_EXPR_FLAG_STRING_RESULT,
+                &expr_err, NULL);
+        if (expr_err) {
+            return apr_pstrcat(cmd->temp_pool,
+                    "Cannot parse Directory/Location expression '", conf->dir, "': ",
+                    expr_err, NULL);
+        }
+    }
+
     return NULL;
 }
 
@@ -723,6 +737,57 @@ static int dav_get_overwrite(request_rec *r)
     return -1;
 }
 
+static int uripath_is_canonical(const char *uripath)
+{
+    const char *dot_pos, *ptr = uripath;
+    apr_size_t i, len;
+    unsigned pattern = 0;
+
+    /* URIPATH is canonical if it has:
+     *  - no '.' segments
+     *  - no closing '/'
+     *  - no '//'
+     */
+
+    if (ptr[0] == '.'
+            && (ptr[1] == '/' || ptr[1] == '\0'
+                    || (ptr[1] == '.' && (ptr[2] == '/' || ptr[2] == '\0')))) {
+        return 0;
+    }
+
+    /* valid special cases */
+    len = strlen(ptr);
+    if (len < 2) {
+        return 1;
+    }
+
+    /* invalid endings */
+    if (ptr[len - 1] == '/' || (ptr[len - 1] == '.' && ptr[len - 2] == '/')) {
+        return 0;
+    }
+
+    /* '.' are rare. So, search for them globally. There will often be no
+     * more than one hit.  Also note that we already checked for invalid
+     * starts and endings, i.e. we only need to check for "/./"
+     */
+    for (dot_pos = memchr(ptr, '.', len); dot_pos;
+            dot_pos = strchr(dot_pos + 1, '.')) {
+        if (dot_pos > ptr && dot_pos[-1] == '/' && dot_pos[1] == '/') {
+            return 0;
+        }
+    }
+
+    /* Now validate the rest of the path. */
+    for (i = 0; i < len - 1; ++i) {
+        pattern = ((pattern & 0xff) << 8) + (unsigned char) ptr[i];
+        if (pattern == 0x101 * (unsigned char) ('/')) {
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
 /* resolve a request URI to a resource descriptor.
  *
  * If label_allowed != 0, then allow the request target to be altered by
@@ -737,6 +802,7 @@ DAV_DECLARE(dav_error *) dav_get_resource(request_rec *r, int label_allowed,
 {
     dav_dir_conf *conf;
     const char *label = NULL;
+    const char *dir;
     dav_error *err;
 
     /* if the request target can be overridden, get any target selector */
@@ -753,9 +819,34 @@ DAV_DECLARE(dav_error *) dav_get_resource(request_rec *r, int label_allowed,
                              ap_escape_html(r->pool, r->uri)));
     }
 
+    if (conf->dir_expr) {
+        const char *err = NULL;
+
+        dir = ap_expr_str_exec(r, conf->dir_expr, &err);
+        if (err) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10367)
+                    "Director/Location expression '%s' could not be parsed: %s", conf->dir, err);
+            return dav_new_error(r->pool, HTTP_INTERNAL_SERVER_ERROR, 0, 0,
+                                 apr_psprintf(r->pool,
+                                 "Directory/Location expression could not be parsed: %s", err));
+        }
+
+        /* safety check - is our path canonical? */
+        if (!uripath_is_canonical(dir)) {
+            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10368)
+                    "Directory/Location is not canonical ('.', '..' and '//' not allowed): %s", dir);
+            return dav_new_error(r->pool, HTTP_BAD_REQUEST, 0, 0,
+                    apr_psprintf(r->pool, "Directory/Location is not canonical for: %s",
+                            ap_escape_html(r->pool, r->uri)));
+        }
+
+    }
+    else {
+        dir = conf->dir;
+    }
+
     /* resolve the resource */
-    err = (*conf->provider->repos->get_resource)(r, conf->dir,
-                                                 label, use_checked_in,
+    err = (*conf->provider->repos->get_resource)(r, dir, label, use_checked_in,
                                                  res_p);
     if (err != NULL) {
         err = dav_push_error(r->pool, err->status, 0,
index 67c36f8134d26c0d0223bce6628c1138fdf11fd8..634d4c6b5db65eefe5e63b29f5a92cd3c4ae8cf1 100644 (file)
@@ -2506,6 +2506,7 @@ static const char *dirsection(cmd_parms *cmd, void *mconfig, const char *arg)
     char *old_path = cmd->path;
     core_dir_config *conf;
     ap_conf_vector_t *new_dir_conf = ap_create_per_dir_config(cmd->pool);
+    const char *regex;
     ap_regex_t *r = NULL;
     const command_rec *thiscmd = cmd->cmd;
 
@@ -2529,15 +2530,20 @@ static const char *dirsection(cmd_parms *cmd, void *mconfig, const char *arg)
 
     if (!strcmp(cmd->path, "~")) {
         cmd->path = ap_getword_conf(cmd->pool, &arg);
-        if (!cmd->path)
-            return "<Directory ~ > block must specify a path";
-        r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED|USE_ICASE);
+        if (!*cmd->path) {
+            return "<Directory ~ > block must specify a regex";
+        }
+        regex = ap_getword_conf(cmd->pool, &arg);
+        r = ap_pregcomp(cmd->pool, *regex ? regex : cmd->path,
+                AP_REG_EXTENDED | USE_ICASE);
         if (!r) {
             return "Regex could not be compiled";
         }
     }
     else if (thiscmd->cmd_data) { /* <DirectoryMatch> */
-        r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED|USE_ICASE);
+        regex = ap_getword_conf(cmd->pool, &arg);
+        r = ap_pregcomp(cmd->pool, *regex ? regex : cmd->path,
+                AP_REG_EXTENDED | USE_ICASE);
         if (!r) {
             return "Regex could not be compiled";
         }
@@ -2599,8 +2605,8 @@ static const char *dirsection(cmd_parms *cmd, void *mconfig, const char *arg)
     ap_add_per_dir_conf(cmd->server, new_dir_conf);
 
     if (*arg != '\0') {
-        return apr_pstrcat(cmd->pool, "Multiple ", thiscmd->name,
-                           "> arguments not (yet) supported.", NULL);
+        return apr_pstrcat(cmd->pool, "Additional ", thiscmd->name,
+                           "> arguments not (yet) supported: ", arg, NULL);
     }
 
     cmd->path = old_path;
@@ -2616,6 +2622,7 @@ static const char *urlsection(cmd_parms *cmd, void *mconfig, const char *arg)
     int old_overrides = cmd->override;
     char *old_path = cmd->path;
     core_dir_config *conf;
+    const char *regex;
     ap_regex_t *r = NULL;
     const command_rec *thiscmd = cmd->cmd;
     ap_conf_vector_t *new_url_conf = ap_create_per_dir_config(cmd->pool);
@@ -2638,14 +2645,21 @@ static const char *urlsection(cmd_parms *cmd, void *mconfig, const char *arg)
     cmd->override = OR_ALL|ACCESS_CONF;
 
     if (thiscmd->cmd_data) { /* <LocationMatch> */
-        r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED);
+        regex = ap_getword_conf(cmd->pool, &arg);
+        r = ap_pregcomp(cmd->pool, *regex ? regex : cmd->path,
+                AP_REG_EXTENDED);
         if (!r) {
             return "Regex could not be compiled";
         }
     }
     else if (!strcmp(cmd->path, "~")) {
         cmd->path = ap_getword_conf(cmd->pool, &arg);
-        r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED);
+        if (!*cmd->path) {
+            return "<Location ~ > block must specify a regex";
+        }
+        regex = ap_getword_conf(cmd->pool, &arg);
+        r = ap_pregcomp(cmd->pool, *regex ? regex : cmd->path,
+                AP_REG_EXTENDED);
         if (!r) {
             return "Regex could not be compiled";
         }
@@ -2671,8 +2685,8 @@ static const char *urlsection(cmd_parms *cmd, void *mconfig, const char *arg)
     ap_add_per_url_conf(cmd->server, new_url_conf);
 
     if (*arg != '\0') {
-        return apr_pstrcat(cmd->pool, "Multiple ", thiscmd->name,
-                           "> arguments not (yet) supported.", NULL);
+        return apr_pstrcat(cmd->pool, "Additional ", thiscmd->name,
+                           "> arguments not (yet) supported: ", arg, NULL);
     }
 
     cmd->path = old_path;
@@ -2688,6 +2702,7 @@ static const char *filesection(cmd_parms *cmd, void *mconfig, const char *arg)
     int old_overrides = cmd->override;
     char *old_path = cmd->path;
     core_dir_config *conf;
+    const char *regex;
     ap_regex_t *r = NULL;
     const command_rec *thiscmd = cmd->cmd;
     ap_conf_vector_t *new_file_conf = ap_create_per_dir_config(cmd->pool);
@@ -2715,14 +2730,18 @@ static const char *filesection(cmd_parms *cmd, void *mconfig, const char *arg)
     }
 
     if (thiscmd->cmd_data) { /* <FilesMatch> */
-        r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED|USE_ICASE);
+        regex = ap_getword_conf(cmd->pool, &arg);
+        r = ap_pregcomp(cmd->pool, *regex ? regex : cmd->path,
+                AP_REG_EXTENDED | USE_ICASE);
         if (!r) {
             return "Regex could not be compiled";
         }
     }
     else if (!strcmp(cmd->path, "~")) {
         cmd->path = ap_getword_conf(cmd->pool, &arg);
-        r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED|USE_ICASE);
+        regex = ap_getword_conf(cmd->pool, &arg);
+        r = ap_pregcomp(cmd->pool, *regex ? regex : cmd->path,
+                AP_REG_EXTENDED | USE_ICASE);
         if (!r) {
             return "Regex could not be compiled";
         }
@@ -2758,8 +2777,8 @@ static const char *filesection(cmd_parms *cmd, void *mconfig, const char *arg)
     ap_add_file_conf(cmd->pool, (core_dir_config *)mconfig, new_file_conf);
 
     if (*arg != '\0') {
-        return apr_pstrcat(cmd->pool, "Multiple ", thiscmd->name,
-                           "> arguments not (yet) supported.", NULL);
+        return apr_pstrcat(cmd->pool, "Additional ", thiscmd->name,
+                           "> arguments not (yet) supported: ", arg, NULL);
     }
 
     cmd->path = old_path;
@@ -2845,8 +2864,8 @@ static const char *ifsection(cmd_parms *cmd, void *mconfig, const char *arg)
         return errmsg;
 
     if (*arg != '\0') {
-        return apr_pstrcat(cmd->pool, "Multiple ", thiscmd->name,
-                           "> arguments not supported.", NULL);
+        return apr_pstrcat(cmd->pool, "Additional ", thiscmd->name,
+                           "> arguments not supported: ", arg, NULL);
     }
 
     cmd->path = old_path;