From: Graham Leggett Date: Sun, 25 Nov 2018 21:15:21 +0000 (+0000) Subject: core: Split out the ability to parse wildcard files and directories X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=b1e34549c190b63293cbef10cb80383fa8607b29;p=apache core: Split out the ability to parse wildcard files and directories from the Include/IncludeOptional directives into a generic set of functions ap_dir_nofnmatch() and ap_dir_fnmatch(). git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/trunk@1847430 13f79535-47bb-0310-9956-ffa450edef68 --- diff --git a/CHANGES b/CHANGES index 6397342b59..31d44bc20b 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,10 @@ -*- coding: utf-8 -*- Changes with Apache 2.5.1 + *) core: Split out the ability to parse wildcard files and directories + from the Include/IncludeOptional directives into a generic set of + functions ap_dir_nofnmatch() and ap_dir_fnmatch(). [Graham Leggett] + *) mod_dav: Fix an unlikely time-window where some incorrect data could be returned from a PROPFIND request [Ruediger Pluem] diff --git a/include/ap_mmn.h b/include/ap_mmn.h index 235e70d40e..27cea23d03 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -608,6 +608,7 @@ * fields by struct ap_filter_private *priv * 20180906.1 (2.5.1-dev) Don't export ap_filter_recycle() anymore * 20180906.2 (2.5.1-dev) Add ap_state_dir_relative() + * 20180906.3 (2.5.1-dev) Add ap_dir_nofnmatch() and ap_dir_fnmatch(). */ #define MODULE_MAGIC_COOKIE 0x41503235UL /* "AP25" */ diff --git a/include/http_config.h b/include/http_config.h index f940f84f6a..b74c61bf3f 100644 --- a/include/http_config.h +++ b/include/http_config.h @@ -938,6 +938,21 @@ AP_DECLARE(const char *) ap_walk_config(ap_directive_t *conftree, cmd_parms *parms, ap_conf_vector_t *section_vector); +/** + * Convenience function to create a ap_dir_match_t structure from a cmd_parms. + * + * @param cmd The command. + * @param flags Flags to indicate whether optional or recursive. + * @param cb Callback for each file found that matches the wildcard. Return NULL on + * success, an error string on error. + * @param ctx Context for the callback. + * @return Structure ap_dir_match_t with fields populated, allocated from the + * cmd->temp_pool. + */ +AP_DECLARE(ap_dir_match_t *)ap_dir_cfgmatch(cmd_parms *cmd, int flags, + const char *(*cb)(ap_dir_match_t *w, const char *fname), void *ctx) + __attribute__((nonnull(1,3))); + /** * @defgroup ap_check_cmd_context Check command context * @{ diff --git a/include/httpd.h b/include/httpd.h index df77049215..3b60765805 100644 --- a/include/httpd.h +++ b/include/httpd.h @@ -2559,6 +2559,92 @@ AP_DECLARE(int) ap_cstr_casecmp(const char *s1, const char *s2); */ AP_DECLARE(int) ap_cstr_casecmpn(const char *s1, const char *s2, apr_size_t n); +/** + * Default flags for apr_dir_*(). + */ +#define AP_DIR_FLAG_NONE 0 + +/** + * If set, wildcards that match no files or directories will be ignored, otherwise + * an error is triggered. + */ +#define AP_DIR_FLAG_OPTIONAL 1 + +/** + * If set, and the wildcard resolves to a directory, recursively find all files + * below that directory, otherwise return the directory. + */ +#define AP_DIR_FLAG_RECURSIVE 2 + +/** + * Structure to provide the state of a directory match. + */ +typedef struct ap_dir_match_t ap_dir_match_t; + +/** + * Concrete structure to provide the state of a directory match. + */ +struct ap_dir_match_t { + /** Pool to use for allocating the result */ + apr_pool_t *p; + /** Temporary pool used for directory traversal */ + apr_pool_t *ptemp; + /** Prefix for log messages */ + const char *prefix; + /** Callback for each file found that matches the wildcard. Return NULL on success, an error string on error. */ + const char *(*cb)(ap_dir_match_t *w, const char *fname); + /** Context for the callback */ + void *ctx; + /** Flags to indicate whether optional or recursive */ + int flags; + /** Recursion depth safety check */ + unsigned int depth; +}; + +/** + * Search for files given a non wildcard filename with non native separators. + * + * If the provided filename points at a file, the callback within ap_dir_match_t is + * triggered for that file, and this function returns the result of the callback. + * + * If the provided filename points at a directory, and recursive within ap_dir_match_t + * is true, the callback will be triggered for every file found recursively beneath + * that directory, otherwise the callback is triggered once for the directory itself. + * This function returns the result of the callback. + * + * If the provided path points to neither a file nor a directory, and optional within + * ap_dir_match_t is true, this function returns NULL. If optional within ap_dir_match_t + * is false, this function will return an error string indicating that the path does not + * exist. + * + * @param w Directory match structure containing callback and context. + * @param fname The name of the file or directory, with non native separators. + * @return NULL on success, or a string describing the error. + */ +AP_DECLARE(const char *)ap_dir_nofnmatch(ap_dir_match_t *w, const char *fname) + __attribute__((nonnull(1,2))); + +/** + * Search for files given a wildcard filename with non native separators. + * + * If the filename contains a wildcard, all files and directories that match the wildcard + * will be returned. + * + * ap_dir_nofnmatch() is called for each directory and file found, and the callback + * within ap_dir_match_t triggered as described above. + * + * Wildcards may appear in both directory and file components in the path, and + * wildcards may appear more than once. + * + * @param w Directory match structure containing callback and context. + * @param path Path prefix for search, with non native separators and no wildcards. + * @param fname The name of the file or directory, with non native separators and + * optional wildcards. + * @return NULL on success, or a string describing the error. + */ +AP_DECLARE(const char *)ap_dir_fnmatch(ap_dir_match_t *w, const char *path, + const char *fname) __attribute__((nonnull(1,3))); + #ifdef __cplusplus } #endif diff --git a/server/config.c b/server/config.c index b06b6e55ce..dcadba0c66 100644 --- a/server/config.c +++ b/server/config.c @@ -1795,18 +1795,6 @@ static const char *process_command_config(server_rec *s, return NULL; } -typedef struct { - const char *fname; -} fnames; - -static int fname_alphasort(const void *fn1, const void *fn2) -{ - const fnames *f1 = fn1; - const fnames *f2 = fn2; - - return strcmp(f1->fname,f2->fname); -} - /** * Used by -D DUMP_INCLUDES to output the config file "tree". */ @@ -1902,200 +1890,15 @@ AP_DECLARE(const char *) ap_process_resource_config(server_rec *s, return NULL; } -static const char *process_resource_config_nofnmatch(server_rec *s, - const char *fname, - ap_directive_t **conftree, - apr_pool_t *p, - apr_pool_t *ptemp, - unsigned depth, - int optional) -{ - const char *error; - apr_status_t rv; - - if (ap_is_directory(ptemp, fname)) { - apr_dir_t *dirp; - apr_finfo_t dirent; - int current; - apr_array_header_t *candidates = NULL; - fnames *fnew; - char *path = apr_pstrdup(ptemp, fname); - - if (++depth > AP_MAX_INCLUDE_DIR_DEPTH) { - return apr_psprintf(p, "Directory %s exceeds the maximum include " - "directory nesting level of %u. You have " - "probably a recursion somewhere.", path, - AP_MAX_INCLUDE_DIR_DEPTH); - } - - /* - * first course of business is to grok all the directory - * entries here and store 'em away. Recall we need full pathnames - * for this. - */ - rv = apr_dir_open(&dirp, path, ptemp); - if (rv != APR_SUCCESS) { - return apr_psprintf(p, "Could not open config directory %s: %pm", - path, &rv); - } - - candidates = apr_array_make(ptemp, 1, sizeof(fnames)); - while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp) == APR_SUCCESS) { - /* strip out '.' and '..' */ - if (strcmp(dirent.name, ".") - && strcmp(dirent.name, "..")) { - fnew = (fnames *) apr_array_push(candidates); - fnew->fname = ap_make_full_path(ptemp, path, dirent.name); - } - } - - apr_dir_close(dirp); - if (candidates->nelts != 0) { - qsort((void *) candidates->elts, candidates->nelts, - sizeof(fnames), fname_alphasort); - - /* - * Now recurse these... we handle errors and subdirectories - * via the recursion, which is nice - */ - for (current = 0; current < candidates->nelts; ++current) { - fnew = &((fnames *) candidates->elts)[current]; - error = process_resource_config_nofnmatch(s, fnew->fname, - conftree, p, ptemp, - depth, optional); - if (error) { - return error; - } - } - } - - return NULL; - } - else if (optional) { - /* If the optinal flag is set (like for IncludeOptional) we can - * tolerate that no file or directory is present and bail out. - */ - apr_finfo_t finfo; - if (apr_stat(&finfo, fname, APR_FINFO_TYPE, ptemp) != APR_SUCCESS - || finfo.filetype == APR_NOFILE) - return NULL; - } - - return ap_process_resource_config(s, fname, conftree, p, ptemp); -} +typedef struct { + server_rec *s; + ap_directive_t **conftree; +} configs; -static const char *process_resource_config_fnmatch(server_rec *s, - const char *path, - const char *fname, - ap_directive_t **conftree, - apr_pool_t *p, - apr_pool_t *ptemp, - unsigned depth, - int optional) +static const char *process_resource_config_cb(ap_dir_match_t *w, const char *fname) { - const char *rest; - apr_status_t rv; - apr_dir_t *dirp; - apr_finfo_t dirent; - apr_array_header_t *candidates = NULL; - fnames *fnew; - int current; - - /* find the first part of the filename */ - rest = ap_strchr_c(fname, '/'); - if (rest) { - fname = apr_pstrmemdup(ptemp, fname, rest - fname); - rest++; - } - - /* optimisation - if the filename isn't a wildcard, process it directly */ - if (!apr_fnmatch_test(fname)) { - path = ap_make_full_path(ptemp, path, fname); - if (!rest) { - return process_resource_config_nofnmatch(s, path, - conftree, p, - ptemp, 0, optional); - } - else { - return process_resource_config_fnmatch(s, path, rest, - conftree, p, - ptemp, 0, optional); - } - } - - /* - * first course of business is to grok all the directory - * entries here and store 'em away. Recall we need full pathnames - * for this. - */ - rv = apr_dir_open(&dirp, path, ptemp); - if (rv != APR_SUCCESS) { - /* If the directory doesn't exist and the optional flag is set - * there is no need to return an error. - */ - if (rv == APR_ENOENT && optional) { - return NULL; - } - return apr_psprintf(p, "Could not open config directory %s: %pm", - path, &rv); - } - - candidates = apr_array_make(ptemp, 1, sizeof(fnames)); - while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp) == APR_SUCCESS) { - /* strip out '.' and '..' */ - if (strcmp(dirent.name, ".") - && strcmp(dirent.name, "..") - && (apr_fnmatch(fname, dirent.name, - APR_FNM_PERIOD) == APR_SUCCESS)) { - const char *full_path = ap_make_full_path(ptemp, path, dirent.name); - /* If matching internal to path, and we happen to match something - * other than a directory, skip it - */ - if (rest && (dirent.filetype != APR_DIR)) { - continue; - } - fnew = (fnames *) apr_array_push(candidates); - fnew->fname = full_path; - } - } - - apr_dir_close(dirp); - if (candidates->nelts != 0) { - const char *error; - - qsort((void *) candidates->elts, candidates->nelts, - sizeof(fnames), fname_alphasort); - - /* - * Now recurse these... we handle errors and subdirectories - * via the recursion, which is nice - */ - for (current = 0; current < candidates->nelts; ++current) { - fnew = &((fnames *) candidates->elts)[current]; - if (!rest) { - error = process_resource_config_nofnmatch(s, fnew->fname, - conftree, p, - ptemp, 0, optional); - } - else { - error = process_resource_config_fnmatch(s, fnew->fname, rest, - conftree, p, - ptemp, 0, optional); - } - if (error) { - return error; - } - } - } - else { - - if (!optional) { - return apr_psprintf(p, "No matches for the wildcard '%s' in '%s', failing " - "(use IncludeOptional if required)", fname, path); - } - } - - return NULL; + configs *cfgs = w->ctx; + return ap_process_resource_config(cfgs->s, fname, cfgs->conftree, w->p, w->ptemp); } AP_DECLARE(const char *) ap_process_fnmatch_configs(server_rec *s, @@ -2105,8 +1908,18 @@ AP_DECLARE(const char *) ap_process_fnmatch_configs(server_rec *s, apr_pool_t *ptemp, int optional) { - /* XXX: lstat() won't work on the wildcard pattern... - */ + configs cfgs; + ap_dir_match_t w; + + cfgs.s = s; + cfgs.conftree = conftree; + + w.prefix = "Include/IncludeOptional: "; + w.p = p; + w.ptemp = ptemp; + w.flags = (optional ? AP_DIR_FLAG_OPTIONAL : AP_DIR_FLAG_NONE) | AP_DIR_FLAG_RECURSIVE; + w.cb = process_resource_config_cb; + w.ctx = &cfgs; /* don't require conf/httpd.conf if we have a -C or -c switch */ if ((ap_server_pre_read_config->nelts @@ -2119,7 +1932,7 @@ AP_DECLARE(const char *) ap_process_fnmatch_configs(server_rec *s, } if (!apr_fnmatch_test(fname)) { - return process_resource_config_nofnmatch(s, fname, conftree, p, ptemp, 0, optional); + return ap_dir_nofnmatch(&w, fname); } else { apr_status_t status; @@ -2137,8 +1950,7 @@ AP_DECLARE(const char *) ap_process_fnmatch_configs(server_rec *s, } /* walk the filepath */ - return process_resource_config_fnmatch(s, rootpath, filepath, conftree, p, ptemp, - 0, optional); + return ap_dir_fnmatch(&w, rootpath, filepath); } } diff --git a/server/util.c b/server/util.c index b99bf4812d..691846d906 100644 --- a/server/util.c +++ b/server/util.c @@ -48,6 +48,7 @@ #include "ap_config.h" #include "apr_base64.h" +#include "apr_fnmatch.h" #include "httpd.h" #include "http_main.h" #include "http_log.h" @@ -97,6 +98,11 @@ #undef APLOG_MODULE_INDEX #define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX +/* maximum nesting level for config directories */ +#ifndef AP_MAX_FNMATCH_DIR_DEPTH +#define AP_MAX_FNMATCH_DIR_DEPTH (128) +#endif + /* * Examine a field value (such as a media-/content-type) string and return * it sans any parameters; e.g., strip off any ';charset=foo' and the like. @@ -3444,3 +3450,206 @@ AP_DECLARE(int) ap_cstr_casecmpn(const char *s1, const char *s2, apr_size_t n) return 0; } +typedef struct { + const char *fname; +} fnames; + +static int fname_alphasort(const void *fn1, const void *fn2) +{ + const fnames *f1 = fn1; + const fnames *f2 = fn2; + + return strcmp(f1->fname,f2->fname); +} + +AP_DECLARE(ap_dir_match_t *)ap_dir_cfgmatch(cmd_parms *cmd, int flags, + const char *(*cb)(ap_dir_match_t *w, const char *fname), void *ctx) +{ + ap_dir_match_t *w = apr_palloc(cmd->temp_pool, sizeof(cmd_parms)); + + w->prefix = apr_pstrcat(cmd->pool, cmd->cmd->name, ": ", NULL); + w->p = cmd->pool; + w->ptemp = cmd->temp_pool; + w->flags = flags; + w->cb = cb; + w->ctx = ctx; + w->depth = 0; + + return w; +} + +AP_DECLARE(const char *)ap_dir_nofnmatch(ap_dir_match_t *w, const char *fname) +{ + const char *error; + apr_status_t rv; + + if ((w->flags & AP_DIR_FLAG_RECURSIVE) && ap_is_directory(w->ptemp, fname)) { + apr_dir_t *dirp; + apr_finfo_t dirent; + int current; + apr_array_header_t *candidates = NULL; + fnames *fnew; + char *path = apr_pstrdup(w->ptemp, fname); + + if (++w->depth > AP_MAX_FNMATCH_DIR_DEPTH) { + return apr_psprintf(w->p, "%sDirectory '%s' exceeds the maximum include " + "directory nesting level of %u. You have " + "probably a recursion somewhere.", w->prefix ? w->prefix : "", path, + AP_MAX_FNMATCH_DIR_DEPTH); + } + + /* + * first course of business is to grok all the directory + * entries here and store 'em away. Recall we need full pathnames + * for this. + */ + rv = apr_dir_open(&dirp, path, w->ptemp); + if (rv != APR_SUCCESS) { + return apr_psprintf(w->p, "%sCould not open directory %s: %pm", + w->prefix ? w->prefix : "", path, &rv); + } + + candidates = apr_array_make(w->ptemp, 1, sizeof(fnames)); + while (apr_dir_read(&dirent, APR_FINFO_DIRENT, dirp) == APR_SUCCESS) { + /* strip out '.' and '..' */ + if (strcmp(dirent.name, ".") + && strcmp(dirent.name, "..")) { + fnew = (fnames *) apr_array_push(candidates); + fnew->fname = ap_make_full_path(w->ptemp, path, dirent.name); + } + } + + apr_dir_close(dirp); + if (candidates->nelts != 0) { + qsort((void *) candidates->elts, candidates->nelts, + sizeof(fnames), fname_alphasort); + + /* + * Now recurse these... we handle errors and subdirectories + * via the recursion, which is nice + */ + for (current = 0; current < candidates->nelts; ++current) { + fnew = &((fnames *) candidates->elts)[current]; + error = ap_dir_nofnmatch(w, fnew->fname); + if (error) { + return error; + } + } + } + + w->depth--; + + return NULL; + } + else if (w->flags & AP_DIR_FLAG_OPTIONAL) { + /* If the optional flag is set (like for IncludeOptional) we can + * tolerate that no file or directory is present and bail out. + */ + apr_finfo_t finfo; + if (apr_stat(&finfo, fname, APR_FINFO_TYPE, w->ptemp) != APR_SUCCESS + || finfo.filetype == APR_NOFILE) + return NULL; + } + + return w->cb(w, fname); +} + +AP_DECLARE(const char *)ap_dir_fnmatch(ap_dir_match_t *w, const char *path, + const char *fname) +{ + const char *rest; + apr_status_t rv; + apr_dir_t *dirp; + apr_finfo_t dirent; + apr_array_header_t *candidates = NULL; + fnames *fnew; + int current; + + /* find the first part of the filename */ + rest = ap_strchr_c(fname, '/'); + if (rest) { + fname = apr_pstrmemdup(w->ptemp, fname, rest - fname); + rest++; + } + + /* optimisation - if the filename isn't a wildcard, process it directly */ + if (!apr_fnmatch_test(fname)) { + path = path ? ap_make_full_path(w->ptemp, path, fname) : fname; + if (!rest) { + return ap_dir_nofnmatch(w, path); + } + else { + return ap_dir_fnmatch(w, path, rest); + } + } + + /* + * first course of business is to grok all the directory + * entries here and store 'em away. Recall we need full pathnames + * for this. + */ + rv = apr_dir_open(&dirp, path, w->ptemp); + if (rv != APR_SUCCESS) { + /* If the directory doesn't exist and the optional flag is set + * there is no need to return an error. + */ + if (rv == APR_ENOENT && (w->flags & AP_DIR_FLAG_OPTIONAL)) { + return NULL; + } + return apr_psprintf(w->p, "%sCould not open directory %s: %pm", + w->prefix ? w->prefix : "", path, &rv); + } + + candidates = apr_array_make(w->ptemp, 1, sizeof(fnames)); + while (apr_dir_read(&dirent, APR_FINFO_DIRENT | APR_FINFO_TYPE, dirp) == APR_SUCCESS) { + /* strip out '.' and '..' */ + if (strcmp(dirent.name, ".") + && strcmp(dirent.name, "..") + && (apr_fnmatch(fname, dirent.name, + APR_FNM_PERIOD) == APR_SUCCESS)) { + const char *full_path = ap_make_full_path(w->ptemp, path, dirent.name); + /* If matching internal to path, and we happen to match something + * other than a directory, skip it + */ + if (rest && (dirent.filetype != APR_DIR)) { + continue; + } + fnew = (fnames *) apr_array_push(candidates); + fnew->fname = full_path; + } + } + + apr_dir_close(dirp); + if (candidates->nelts != 0) { + const char *error; + + qsort((void *) candidates->elts, candidates->nelts, + sizeof(fnames), fname_alphasort); + + /* + * Now recurse these... we handle errors and subdirectories + * via the recursion, which is nice + */ + for (current = 0; current < candidates->nelts; ++current) { + fnew = &((fnames *) candidates->elts)[current]; + if (!rest) { + error = ap_dir_nofnmatch(w, fnew->fname); + } + else { + error = ap_dir_fnmatch(w, fnew->fname, rest); + } + if (error) { + return error; + } + } + } + else { + + if (!(w->flags & AP_DIR_FLAG_OPTIONAL)) { + return apr_psprintf(w->p, "%sNo matches for the wildcard '%s' in '%s', failing", + w->prefix ? w->prefix : "", fname, path); + } + } + + return NULL; +}