-*- 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]
* 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" */
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
* @{
*/
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
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".
*/
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,
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
}
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;
}
/* walk the filepath */
- return process_resource_config_fnmatch(s, rootpath, filepath, conftree, p, ptemp,
- 0, optional);
+ return ap_dir_fnmatch(&w, rootpath, filepath);
}
}
#include "ap_config.h"
#include "apr_base64.h"
+#include "apr_fnmatch.h"
#include "httpd.h"
#include "http_main.h"
#include "http_log.h"
#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.
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;
+}