From 43e022f007cf8cde3b1cbe577de74464e10f2f55 Mon Sep 17 00:00:00 2001 From: Graham Leggett Date: Mon, 30 Dec 2013 19:50:52 +0000 Subject: [PATCH] core: Support named groups and backreferences within the LocationMatch, DirectoryMatch, FilesMatch and ProxyMatch directives. git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1554300 13f79535-47bb-0310-9956-ffa450edef68 --- CHANGES | 3 + include/ap_mmn.h | 3 +- include/ap_regex.h | 9 +++ include/http_core.h | 4 ++ modules/proxy/mod_proxy.c | 60 +++++++++++++++---- modules/proxy/mod_proxy.h | 4 ++ server/core.c | 16 +++++ server/request.c | 122 ++++++++++++++++++++++++++++++++++---- server/util_pcre.c | 37 +++++++++++- 9 files changed, 235 insertions(+), 23 deletions(-) diff --git a/CHANGES b/CHANGES index e8e2569a83..93431803b5 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ -*- coding: utf-8 -*- Changes with Apache 2.5.0 + *) core: Support named groups and backreferences within the LocationMatch, + DirectoryMatch, FilesMatch and ProxyMatch directives. [Graham Leggett] + *) mod_authz_user: Support the expression parser within the require directives. [Graham Leggett] diff --git a/include/ap_mmn.h b/include/ap_mmn.h index f52604b694..ec1a189b5d 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -443,6 +443,7 @@ * 20130924.1 (2.5.0-dev) Add ap_proxy_connection_reusable() * 20131112.0 (2.5.0-dev) Add parse_errorlog_arg to ap_errorlog_provider * 20131112.1 (2.5.0-dev) Add suspend_connection and resume_connection hooks + * 20131112.2 (2.5.0-dev) Add ap_regname */ #define MODULE_MAGIC_COOKIE 0x41503235UL /* "AP25" */ @@ -450,7 +451,7 @@ #ifndef MODULE_MAGIC_NUMBER_MAJOR #define MODULE_MAGIC_NUMBER_MAJOR 20131112 #endif -#define MODULE_MAGIC_NUMBER_MINOR 1 /* 0...n */ +#define MODULE_MAGIC_NUMBER_MINOR 2 /* 0...n */ /** * Determine if the server's current MODULE_MAGIC_NUMBER is at least a diff --git a/include/ap_regex.h b/include/ap_regex.h index 5122154d90..8f0aedd322 100644 --- a/include/ap_regex.h +++ b/include/ap_regex.h @@ -149,6 +149,15 @@ AP_DECLARE(int) ap_regexec_len(const ap_regex_t *preg, const char *buff, AP_DECLARE(apr_size_t) ap_regerror(int errcode, const ap_regex_t *preg, char *errbuf, apr_size_t errbuf_size); +/** + * Return an array of named regex backreferences + * @param preg The precompiled regex + * @param names The array to which the names will be added + * @param upper If non zero, uppercase the names + */ +AP_DECLARE(int) ap_regname(const ap_regex_t *preg, + apr_array_header_t *names, int upper); + /** Destroy a pre-compiled regex. * @param preg The pre-compiled regex to free. */ diff --git a/include/http_core.h b/include/http_core.h index 77f92c88ee..371e54d4bf 100644 --- a/include/http_core.h +++ b/include/http_core.h @@ -619,6 +619,10 @@ typedef struct { unsigned int allow_encoded_slashes_set : 1; unsigned int decode_encoded_slashes_set : 1; + + /** Named back references */ + apr_array_header_t *refs; + } core_dir_config; /* macro to implement off by default behaviour */ diff --git a/modules/proxy/mod_proxy.c b/modules/proxy/mod_proxy.c index db033a12f4..c5f4da78fe 100644 --- a/modules/proxy/mod_proxy.c +++ b/modules/proxy/mod_proxy.c @@ -744,22 +744,52 @@ static int proxy_walk(request_rec *r) */ const char *proxyname = r->filename + 6; int j; + apr_pool_t *rxpool = NULL; for (j = 0; j < num_sec; ++j) { entry_config = sec_proxy[j]; entry_proxy = ap_get_module_config(entry_config, &proxy_module); - /* XXX: What about case insensitive matching ??? - * Compare regex, fnmatch or string as appropriate - * If the entry doesn't relate, then continue - */ - if (entry_proxy->r - ? ap_regexec(entry_proxy->r, proxyname, 0, NULL, 0) - : (entry_proxy->p_is_fnmatch - ? apr_fnmatch(entry_proxy->p, proxyname, 0) - : strncmp(proxyname, entry_proxy->p, - strlen(entry_proxy->p)))) { + int nmatch = 0; + int i; + ap_regmatch_t *pmatch = NULL; + + if (entry_proxy->r) { + + if (entry_proxy->refs && entry_proxy->refs->nelts) { + if (!rxpool) { + apr_pool_create(&rxpool, r->pool); + } + nmatch = entry_proxy->refs->nelts; + pmatch = apr_palloc(rxpool, nmatch*sizeof(ap_regmatch_t)); + } + + if (ap_regexec(entry_proxy->r, proxyname, nmatch, pmatch, 0)) { + continue; + } + + for (i = 0; i < nmatch; i++) { + if (pmatch[i].rm_so >= 0 && pmatch[i].rm_eo >= 0 && + ((const char **)entry_proxy->refs->elts)[i]) { + apr_table_setn(r->subprocess_env, + ((const char **)entry_proxy->refs->elts)[i], + apr_pstrndup(r->pool, + proxyname + pmatch[i].rm_so, + pmatch[i].rm_eo - pmatch[i].rm_so)); + } + } + } + + else if ( + /* XXX: What about case insensitive matching ??? + * Compare regex, fnmatch or string as appropriate + * If the entry doesn't relate, then continue + */ + entry_proxy->p_is_fnmatch ? apr_fnmatch(entry_proxy->p, + proxyname, 0) : + strncmp(proxyname, entry_proxy->p, + strlen(entry_proxy->p))) { continue; } per_dir_defaults = ap_merge_per_dir_configs(r->pool, per_dir_defaults, @@ -768,6 +798,10 @@ static int proxy_walk(request_rec *r) r->per_dir_config = per_dir_defaults; + if (rxpool) { + apr_pool_destroy(rxpool); + } + return OK; } @@ -1314,6 +1348,7 @@ static void *merge_proxy_dir_config(apr_pool_t *p, void *basev, void *addv) new->p = add->p; new->p_is_fnmatch = add->p_is_fnmatch; new->r = add->r; + new->refs = add->refs; /* Put these in the dir config so they work inside */ new->raliases = apr_array_append(p, base->raliases, add->raliases); @@ -2238,6 +2273,11 @@ static const char *proxysection(cmd_parms *cmd, void *mconfig, const char *arg) conf->p = cmd->path; conf->p_is_fnmatch = apr_fnmatch_test(conf->p); + if (r) { + conf->refs = apr_array_make(cmd->pool, 8, sizeof(char *)); + ap_regname(r, conf->refs, 1); + } + ap_add_per_proxy_conf(cmd->server, new_dir_conf); if (*arg != '\0') { diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h index 25b19b4947..cc7fb02d20 100644 --- a/modules/proxy/mod_proxy.h +++ b/modules/proxy/mod_proxy.h @@ -219,6 +219,10 @@ typedef struct { unsigned int error_override_set:1; unsigned int alias_set:1; unsigned int add_forwarded_headers:1; + + /** Named back references */ + apr_array_header_t *refs; + } proxy_dir_conf; /* if we interpolate env vars per-request, we'll need a per-request diff --git a/server/core.c b/server/core.c index 72abb65831..bb528832f3 100644 --- a/server/core.c +++ b/server/core.c @@ -215,6 +215,7 @@ static void *merge_core_dir_configs(apr_pool_t *a, void *basev, void *newv) conf->d_is_fnmatch = new->d_is_fnmatch; conf->d_components = new->d_components; conf->r = new->r; + conf->refs = new->refs; conf->condition = new->condition; if (new->opts & OPT_UNSET) { @@ -2214,6 +2215,11 @@ static const char *dirsection(cmd_parms *cmd, void *mconfig, const char *arg) conf->d = cmd->path; conf->d_is_fnmatch = (apr_fnmatch_test(conf->d) != 0); + if (r) { + conf->refs = apr_array_make(cmd->pool, 8, sizeof(char *)); + ap_regname(r, conf->refs, 1); + } + /* Make this explicit - the "/" root has 0 elements, that is, we * will always merge it, and it will always sort and merge first. * All others are sorted and tested by the number of slashes. @@ -2290,6 +2296,11 @@ static const char *urlsection(cmd_parms *cmd, void *mconfig, const char *arg) conf->d_is_fnmatch = apr_fnmatch_test(conf->d) != 0; conf->r = r; + if (r) { + conf->refs = apr_array_make(cmd->pool, 8, sizeof(char *)); + ap_regname(r, conf->refs, 1); + } + ap_add_per_url_conf(cmd->server, new_url_conf); if (*arg != '\0') { @@ -2372,6 +2383,11 @@ static const char *filesection(cmd_parms *cmd, void *mconfig, const char *arg) conf->d_is_fnmatch = apr_fnmatch_test(conf->d) != 0; conf->r = r; + if (r) { + conf->refs = apr_array_make(cmd->pool, 8, sizeof(char *)); + ap_regname(r, conf->refs, 1); + } + ap_add_file_conf(cmd->pool, (core_dir_config *)mconfig, new_file_conf); if (*arg != '\0') { diff --git a/server/request.c b/server/request.c index 12dd58c315..736c4c444e 100644 --- a/server/request.c +++ b/server/request.c @@ -741,6 +741,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) apr_size_t buflen; char *buf; unsigned int seg, startseg; + apr_pool_t *rxpool = NULL; /* Invariant: from the first time filename_len is set until * it goes out of scope, filename_len==strlen(r->filename) @@ -1196,6 +1197,10 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) */ for (; sec_idx < num_sec; ++sec_idx) { + int nmatch = 0; + int i; + ap_regmatch_t *pmatch = NULL; + core_dir_config *entry_core; entry_core = ap_get_core_module_config(sec_ent[sec_idx]); @@ -1203,10 +1208,29 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) continue; } - if (ap_regexec(entry_core->r, r->filename, 0, NULL, 0)) { + if (entry_core->refs && entry_core->refs->nelts) { + if (!rxpool) { + apr_pool_create(&rxpool, r->pool); + } + nmatch = entry_core->refs->nelts; + pmatch = apr_palloc(rxpool, nmatch*sizeof(ap_regmatch_t)); + } + + if (ap_regexec(entry_core->r, r->filename, nmatch, pmatch, 0)) { continue; } + for (i = 0; i < nmatch; i++) { + if (pmatch[i].rm_so >= 0 && pmatch[i].rm_eo >= 0 && + ((const char **)entry_core->refs->elts)[i]) { + apr_table_setn(r->subprocess_env, + ((const char **)entry_core->refs->elts)[i], + apr_pstrndup(r->pool, + r->filename + pmatch[i].rm_so, + pmatch[i].rm_eo - pmatch[i].rm_so)); + } + } + /* If we haven't already continue'd above, we have a match. * * Calculate our full-context core opts & override. @@ -1245,6 +1269,10 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) last_walk->merged = now_merged; } + if (rxpool) { + apr_pool_destroy(rxpool); + } + /* Whoops - everything matched in sequence, but either the original * walk found some additional matches (which we need to truncate), or * this walk found some additional matches. @@ -1382,6 +1410,7 @@ AP_DECLARE(int) ap_location_walk(request_rec *r) int matches = cache->walked->nelts; int cached_matches = matches; walk_walked_t *last_walk = (walk_walked_t*)cache->walked->elts; + apr_pool_t *rxpool = NULL; cached &= auth_internal_per_conf; cache->cached = entry_uri; @@ -1403,16 +1432,48 @@ AP_DECLARE(int) ap_location_walk(request_rec *r) * not slash terminated, then this uri must be slash * terminated (or at the end of the string) to match. */ - if (entry_core->r - ? ap_regexec(entry_core->r, r->uri, 0, NULL, 0) - : (entry_core->d_is_fnmatch + if (entry_core->r) { + + int nmatch = 0; + int i; + ap_regmatch_t *pmatch = NULL; + + if (entry_core->refs && entry_core->refs->nelts) { + if (!rxpool) { + apr_pool_create(&rxpool, r->pool); + } + nmatch = entry_core->refs->nelts; + pmatch = apr_palloc(rxpool, nmatch*sizeof(ap_regmatch_t)); + } + + if (ap_regexec(entry_core->r, r->uri, nmatch, pmatch, 0)) { + continue; + } + + for (i = 0; i < nmatch; i++) { + if (pmatch[i].rm_so >= 0 && pmatch[i].rm_eo >= 0 && + ((const char **)entry_core->refs->elts)[i]) { + apr_table_setn(r->subprocess_env, + ((const char **)entry_core->refs->elts)[i], + apr_pstrndup(r->pool, + r->uri + pmatch[i].rm_so, + pmatch[i].rm_eo - pmatch[i].rm_so)); + } + } + + } + else { + + if ((entry_core->d_is_fnmatch ? apr_fnmatch(entry_core->d, cache->cached, APR_FNM_PATHNAME) : (strncmp(entry_core->d, cache->cached, len) || (len > 0 && entry_core->d[len - 1] != '/' && cache->cached[len] != '/' && cache->cached[len] != '\0')))) { - continue; + continue; + } + } /* If we merged this same section last time, reuse it @@ -1447,6 +1508,10 @@ AP_DECLARE(int) ap_location_walk(request_rec *r) last_walk->merged = now_merged; } + if (rxpool) { + apr_pool_destroy(rxpool); + } + /* Whoops - everything matched in sequence, but either the original * walk found some additional matches (which we need to truncate), or * this walk found some additional matches. @@ -1556,6 +1621,7 @@ AP_DECLARE(int) ap_file_walk(request_rec *r) int matches = cache->walked->nelts; int cached_matches = matches; walk_walked_t *last_walk = (walk_walked_t*)cache->walked->elts; + apr_pool_t *rxpool = NULL; cached &= auth_internal_per_conf; cache->cached = test_file; @@ -1568,12 +1634,42 @@ AP_DECLARE(int) ap_file_walk(request_rec *r) core_dir_config *entry_core; entry_core = ap_get_core_module_config(sec_ent[sec_idx]); - if (entry_core->r - ? ap_regexec(entry_core->r, cache->cached , 0, NULL, 0) - : (entry_core->d_is_fnmatch - ? apr_fnmatch(entry_core->d, cache->cached, APR_FNM_PATHNAME) - : strcmp(entry_core->d, cache->cached))) { - continue; + if (entry_core->r) { + + int nmatch = 0; + int i; + ap_regmatch_t *pmatch = NULL; + + if (entry_core->refs && entry_core->refs->nelts) { + if (!rxpool) { + apr_pool_create(&rxpool, r->pool); + } + nmatch = entry_core->refs->nelts; + pmatch = apr_palloc(rxpool, nmatch*sizeof(ap_regmatch_t)); + } + + if (ap_regexec(entry_core->r, cache->cached, nmatch, pmatch, 0)) { + continue; + } + + for (i = 0; i < nmatch; i++) { + if (pmatch[i].rm_so >= 0 && pmatch[i].rm_eo >= 0 && + ((const char **)entry_core->refs->elts)[i]) { + apr_table_setn(r->subprocess_env, + ((const char **)entry_core->refs->elts)[i], + apr_pstrndup(r->pool, + cache->cached + pmatch[i].rm_so, + pmatch[i].rm_eo - pmatch[i].rm_so)); + } + } + + } + else { + if ((entry_core->d_is_fnmatch + ? apr_fnmatch(entry_core->d, cache->cached, APR_FNM_PATHNAME) + : strcmp(entry_core->d, cache->cached))) { + continue; + } } /* If we merged this same section last time, reuse it @@ -1608,6 +1704,10 @@ AP_DECLARE(int) ap_file_walk(request_rec *r) last_walk->merged = now_merged; } + if (rxpool) { + apr_pool_destroy(rxpool); + } + /* Whoops - everything matched in sequence, but either the original * walk found some additional matches (which we need to truncate), or * this walk found some additional matches. diff --git a/server/util_pcre.c b/server/util_pcre.c index 1e83cad080..41761487e4 100644 --- a/server/util_pcre.c +++ b/server/util_pcre.c @@ -45,6 +45,7 @@ POSSIBILITY OF SUCH DAMAGE. #include "httpd.h" #include "apr_strings.h" +#include "apr_tables.h" #include "pcre.h" #define APR_WANT_STRFUNC @@ -124,7 +125,7 @@ AP_DECLARE(int) ap_regcomp(ap_regex_t * preg, const char *pattern, int cflags) const char *errorptr; int erroffset; int errcode = 0; - int options = 0; + int options = PCRE_DUPNAMES; if ((cflags & AP_REG_ICASE) != 0) options |= PCRE_CASELESS; @@ -256,4 +257,38 @@ AP_DECLARE(int) ap_regexec_len(const ap_regex_t *preg, const char *buff, } } +AP_DECLARE(int) ap_regname(const ap_regex_t *preg, + apr_array_header_t *names, int upper) +{ + int namecount; + int nameentrysize; + int i; + char *nametable; + + pcre_fullinfo((const pcre *)preg->re_pcre, NULL, + PCRE_INFO_NAMECOUNT, &namecount); + pcre_fullinfo((const pcre *)preg->re_pcre, NULL, + PCRE_INFO_NAMEENTRYSIZE, &nameentrysize); + pcre_fullinfo((const pcre *)preg->re_pcre, NULL, + PCRE_INFO_NAMETABLE, &nametable); + + for (i = 0; i < namecount; i++) { + const char *offset = nametable + i * nameentrysize; + int capture = ((offset[0] << 8) + offset[1]); + while (names->nelts <= capture) { + apr_array_push(names); + } + if (upper) { + char *name = ((char **)names->elts)[capture] = + apr_pstrdup(names->pool, offset + 2); + ap_str_toupper(name); + } + else { + ((const char **)names->elts)[capture] = offset + 2; + } + } + + return namecount; +} + /* End of pcreposix.c */ -- 2.40.0