From c51dccd833ccaec00df9420701c879ca6fb0e3b4 Mon Sep 17 00:00:00 2001 From: Graham Leggett Date: Mon, 17 Jan 2022 16:10:51 +0000 Subject: 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. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1897156 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES | 5 +++ docs/log-message-tags/next-number | 2 +- docs/manual/mod/core.xml | 55 ++++++++++++++++++++++- modules/dav/main/mod_dav.c | 95 ++++++++++++++++++++++++++++++++++++++- server/core.c | 51 ++++++++++++++------- 5 files changed, 187 insertions(+), 21 deletions(-) diff --git a/CHANGES b/CHANGES index de3bbe22d6..cc04ee13e4 100644 --- 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] diff --git a/docs/log-message-tags/next-number b/docs/log-message-tags/next-number index 4ca47544e3..427e445179 100644 --- a/docs/log-message-tags/next-number +++ b/docs/log-message-tags/next-number @@ -1 +1 @@ -10367 +10369 diff --git a/docs/manual/mod/core.xml b/docs/manual/mod/core.xml index e22545e8cf..1b98107146 100644 --- a/docs/manual/mod/core.xml +++ b/docs/manual/mod/core.xml @@ -926,6 +926,20 @@ named file-system directory, sub-directories, and their contents. the corresponding Directory will be applied.

+

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 + DirectoryMatch directive below.

+ + +<Directory ~ /home/%{env:MATCH_PARTITIONNAME}/dav/ ^/dav/(?<PARTITIONNAME>[^/]+)/> + Dav on +</Directory> + +

Note that the default access for <Directory "/"> 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. DirectoryMatch Enclose directives that apply to the contents of file-system directories matching a regular expression. -<DirectoryMatch regex> +<DirectoryMatch [expr] regex> ... </DirectoryMatch> server configvirtual host @@ -1005,6 +1019,18 @@ the contents of file-system directories matching a regular expression. <DirectoryMatch "^/var/www/combined/(?<sitename>[^/]+)"> Require ldap-group cn=%{env:MATCH_SITENAME},ou=combined,o=Example +</DirectoryMatch> + + +

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.

+ + +<DirectoryMatch /home/%{env:MATCH_PARTITIONNAME}/dav/ ^/dav/(?<PARTITIONNAME>[^/]+)/> + Dav on </DirectoryMatch> @@ -3172,6 +3198,19 @@ URLs </Location> +

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 LocationMatch directive below.

+ + +<Location ~ /dav/%{env:MATCH_PARTITIONNAME} ^/dav/(?<PARTITIONNAME>[^/]+)/> + Dav on +</Location> + + Note about / (slash)

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 Applies the enclosed directives only to regular-expression matching URLs <LocationMatch - regex> ... </LocationMatch> + [expr] regex> ... </LocationMatch> server configvirtual host @@ -3250,6 +3289,18 @@ matching URLs </LocationMatch> +

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.

+ + +<LocationMatch /dav/%{env:MATCH_PARTITIONNAME} ^/dav/(?<PARTITIONNAME>[^/]+)/> + Dav on +</LocationMatch> + + Note about / (slash)

The slash character has special meaning depending on where in a URL it appears. People may be used to its behavior in the filesystem diff --git a/modules/dav/main/mod_dav.c b/modules/dav/main/mod_dav.c index 1a4b0663f0..080fb26cf0 100644 --- a/modules/dav/main/mod_dav.c +++ b/modules/dav/main/mod_dav.c @@ -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, diff --git a/server/core.c b/server/core.c index 67c36f8134..634d4c6b5d 100644 --- a/server/core.c +++ b/server/core.c @@ -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 " block must specify a path"; - r = ap_pregcomp(cmd->pool, cmd->path, AP_REG_EXTENDED|USE_ICASE); + if (!*cmd->path) { + return " 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) { /* */ - 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) { /* */ - 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 " 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) { /* */ - 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; -- cgit v1.2.3