AP_DECLARE(int) ap_directory_walk(request_rec *r)
{
+ ap_conf_vector_t *now_merged = NULL;
core_server_config *sconf = ap_get_module_config(r->server->module_config,
&core_module);
- ap_conf_vector_t *per_dir_defaults = r->per_dir_config;
ap_conf_vector_t **sec_ent = (ap_conf_vector_t **) sconf->sec_dir->elts;
int num_sec = sconf->sec_dir->nelts;
- int sec_idx;
- unsigned int seg, startseg;
- int res;
- ap_conf_vector_t *entry_config;
- core_dir_config *entry_core;
+ walk_cache_t *cache;
+ char *entry_dir;
apr_status_t rv;
- apr_size_t buflen;
- char *seg_name;
- char *delim;
/* XXX: Better (faster) tests needed!!!
*
"Module bug? Request filename path %s is missing or "
"or not absolute for uri %s",
r->filename ? r->filename : "<NULL>", r->uri);
- return OK;
+ return OK;
}
/*
- * Go down the directory hierarchy. Where we have to check for symlinks,
- * do so. Where a .htaccess file has permission to override anything,
- * try to find one. If either of these things fails, we could poke
- * around, see why, and adjust the lookup_rec accordingly --- this might
- * save us a call to get_path_info (with the attendant stat()s); however,
- * for the moment, that's not worth the trouble.
- *
* r->path_info tracks the remaining source path.
* r->filename tracks the path as we build it.
* we begin our adventure at the root...
*/
- r->path_info = r->filename;
- if ((rv = apr_filepath_merge(&r->path_info, NULL, r->filename,
+ if ((rv = apr_filepath_merge(&entry_dir, NULL, r->filename,
APR_FILEPATH_NOTRELATIVE, r->pool))
- == APR_SUCCESS) {
- char *buf;
- rv = apr_filepath_root(&r->filename, &r->path_info,
- APR_FILEPATH_TRUENAME, r->pool);
- buflen = strlen(r->filename) + strlen(r->path_info) + 1;
- buf = apr_palloc(r->pool, buflen);
- strcpy (buf, r->filename);
- r->filename = buf;
- r->finfo.valid = APR_FINFO_TYPE;
- r->finfo.filetype = APR_DIR; /* It's the root, of course it's a dir */
- } else {
- ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
- "Config bug? Request filename path %s is invalid or "
+ != APR_SUCCESS) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_INFO, 0, r,
+ "Module bug? Request filename path %s is invalid or "
"or not absolute for uri %s",
r->filename, r->uri);
- return HTTP_INTERNAL_SERVER_ERROR;
+ return OK;
}
+ r->filename = entry_dir;
/*
- * seg keeps track of which segment we've copied.
- * sec_idx keeps track of which section we're on, since sections are
- * ordered by number of segments. See core_reorder_directories
+ *
+ * Go down the directory hierarchy. Where we have to check for symlinks,
+ * do so. Where a .htaccess file has permission to override anything,
+ * try to find one.
*/
- startseg = seg = ap_count_dirs(r->filename);
- sec_idx = 0;
- do {
- int overrides_here;
- core_dir_config *core_dir = ap_get_module_config(per_dir_defaults,
- &core_module);
-
- /* We have no trailing slash, but we sure would appreciate one...
+ cache = prep_walk_cache("ap_directory_walk::cache", r);
+
+ if (r->finfo.filetype == APR_REG)
+ entry_dir = ap_make_dirstr_parent(r->pool, entry_dir);
+ else if (r->filename[strlen(r->filename) - 1] != '/')
+ entry_dir = apr_pstrcat(r->pool, r->filename, "/", NULL);
+
+ /* If we have a file already matches the path of r->filename,
+ * and the vhost's list of directory sections hasn't changed,
+ * we can skip rewalking the directory_walk entries.
+ */
+ if (cache->cached && ((r->finfo.filetype == APR_REG)
+ || (r->finfo.filetype == APR_DIR))
+ && (cache->dir_conf_tested == sec_ent)
+ && (strcmp(entry_dir, cache->cached) == 0)) {
+ /* Well this looks really familiar! If our end-result (per_dir_result)
+ * didn't change, we have absolutely nothing to do :)
+ * Otherwise (as is the case with most dir_merged/file_merged requests)
+ * we must merge our dir_conf_merged onto this new r->per_dir_config.
+ */
+ if (cache->per_dir_result == r->per_dir_config)
+ return OK;
+ if (cache->walked->nelts)
+ now_merged = ((walk_walked_t*)cache->walked->elts)
+ [cache->walked->nelts - 1].merged;
+ }
+ else {
+ /* We start now_merged from NULL since we want to build
+ * a locations list that can be merged to any vhost.
*/
- if (sec_idx && r->filename[strlen(r->filename)-1] != '/')
- strcat(r->filename, "/");
+ int sec_idx;
+ int matches = cache->walked->nelts;
+ walk_walked_t *last_walk = (walk_walked_t*)cache->walked->elts;
+ core_dir_config *this_dir;
+ allow_options_t opts;
+ allow_options_t opts_add;
+ allow_options_t opts_remove;
+ overrides_t override;
- /* Begin *this* level by looking for matching <Directory> sections
- * from the server config.
+ apr_size_t buflen;
+ char *buf;
+ unsigned int seg, startseg;
+
+ /*
+ * We must play our own mimi-merge game here, for the few
+ * running dir_config values we care about within dir_walk.
+ * We didn't start the merge from r->per_dir_config, so we
+ * accumulate opts and override as we merge, from the globals.
*/
- for (; sec_idx < num_sec; ++sec_idx) {
- const char *entry_dir;
+ this_dir = ap_get_module_config(r->per_dir_config, &core_module);
+ opts = this_dir->opts;
+ opts_add = this_dir->opts_add;
+ opts_remove = this_dir->opts_remove;
+ override = this_dir->override;
- entry_config = sec_ent[sec_idx];
- entry_core = ap_get_module_config(entry_config, &core_module);
- entry_dir = entry_core->d;
+ r->path_info = r->filename;
+ rv = apr_filepath_root(&r->filename, &r->path_info,
+ APR_FILEPATH_TRUENAME, r->pool);
+ buflen = strlen(r->filename) + strlen(r->path_info) + 1;
+ buf = apr_palloc(r->pool, buflen);
+ strcpy (buf, r->filename);
+ r->filename = buf;
+ r->finfo.valid = APR_FINFO_TYPE;
+ r->finfo.filetype = APR_DIR; /* It's the root, of course it's a dir */
- /* No more possible matches for this many segments?
- * We are done when we find relative/regex/longer components.
+ /*
+ * seg keeps track of which segment we've copied.
+ * sec_idx keeps track of which section we're on, since sections are
+ * ordered by number of segments. See core_reorder_directories
+ */
+ startseg = seg = ap_count_dirs(r->filename);
+ sec_idx = 0;
+ do {
+ int res;
+ char *seg_name;
+ char *delim;
+
+ /* We have no trailing slash, but we sure would appreciate one...
*/
- if (entry_core->r || entry_core->d_components > seg)
- break;
+ if (sec_idx && r->filename[strlen(r->filename)-1] != '/')
+ strcat(r->filename, "/");
- /* We will never skip '0' element components, e.g. plain old
- * <Directory >, and <Directory "/"> are classified as zero
- * so that Win32/Netware/OS2 etc all pick them up.
- * Otherwise, skip over the mismatches.
+ /* Begin *this* level by looking for matching <Directory> sections
+ * from the server config.
*/
- if (entry_core->d_components
- && (entry_core->d_is_fnmatch
- ? (apr_fnmatch(entry_dir, r->filename, FNM_PATHNAME) != APR_SUCCESS)
- : (strcmp(r->filename, entry_dir) != 0))) {
- continue;
- }
-
- per_dir_defaults = ap_merge_per_dir_configs(r->pool,
- per_dir_defaults,
- entry_config);
- core_dir = ap_get_module_config(per_dir_defaults,
- &core_module);
- }
- overrides_here = core_dir->override;
+ for (; sec_idx < num_sec; ++sec_idx) {
- /* If .htaccess files are enabled, check for one. */
- if (overrides_here) {
- ap_conf_vector_t *htaccess_conf = NULL;
-
- res = ap_parse_htaccess(&htaccess_conf, r, overrides_here,
- apr_pstrdup(r->pool, r->filename),
- sconf->access_name);
- if (res)
- return res;
+ ap_conf_vector_t *entry_config = sec_ent[sec_idx];
+ core_dir_config *entry_core;
+ entry_core = ap_get_module_config(entry_config, &core_module);
- if (htaccess_conf) {
- per_dir_defaults = ap_merge_per_dir_configs(r->pool,
- per_dir_defaults,
- htaccess_conf);
- r->per_dir_config = per_dir_defaults;
- }
- }
+ /* No more possible matches for this many segments?
+ * We are done when we find relative/regex/longer components.
+ */
+ if (entry_core->r || entry_core->d_components > seg)
+ break;
- /* That temporary trailing slash was useful, now drop it.
- */
- if (seg > startseg)
- r->filename[strlen(r->filename) - 1] = '\0';
+ /* We will never skip '0' element components, e.g. plain old
+ * <Directory >, and <Directory "/"> are classified as zero
+ * so that Win32/Netware/OS2 etc all pick them up.
+ * Otherwise, skip over the mismatches.
+ */
+ if (entry_core->d_components
+ && (entry_core->d_is_fnmatch
+ ? (apr_fnmatch(entry_core->d, r->filename, FNM_PATHNAME) != APR_SUCCESS)
+ : (strcmp(r->filename, entry_core->d) != 0))) {
+ continue;
+ }
- /* Time for all good things to come to an end?
- */
- if (!r->path_info || !*r->path_info)
- break;
+ /* If we merged this same section last time, reuse it
+ */
+ if (matches) {
+ if (last_walk->matched == sec_ent[sec_idx]) {
+ now_merged = last_walk->merged;
+ ++last_walk;
+ --matches;
+ goto minimerge;
+ }
+ /* We fell out of sync. This is our own copy of walked,
+ * so truncate the remaining matches and reset remaining.
+ */
+ cache->walked->nelts -= matches;
+ matches = 0;
+ }
- /* Now it's time for the next segment...
- * We will assume the next element is an end node, and fix it up
- * below as necessary...
- */
-
- seg_name = strchr(r->filename, '\0');
- delim = strchr(r->path_info + (*r->path_info == '/' ? 1 : 0), '/');
- if (delim) {
- *delim = '\0';
- strcpy(seg_name, r->path_info);
- r->path_info = delim;
- *delim = '/';
- }
- else {
- strcpy(seg_name, r->path_info);
- r->path_info = strchr(r->path_info, '\0');
- }
- if (*seg_name == '/')
- ++seg_name;
-
- /* If nothing remained but a '/' string, we are finished
- */
- if (!*seg_name)
- break;
+ if (now_merged)
+ now_merged = ap_merge_per_dir_configs(r->pool,
+ now_merged,
+ sec_ent[sec_idx]);
+ else
+ now_merged = sec_ent[sec_idx];
+
+ last_walk = (walk_walked_t*)apr_array_push(cache->walked);
+ last_walk->matched = sec_ent[sec_idx];
+ last_walk->merged = now_merged;
+
+ /* Do a mini-merge to our globally-based running calculations of
+ * core_dir->override and core_dir->opts, since now_merged
+ * never considered the global config. Of course, if there is no
+ * core config at this level, continue without a thought.
+ * See core.c::merge_core_dir_configs() for explanation.
+ */
+minimerge:
+ this_dir = ap_get_module_config(sec_ent[sec_idx], &core_module);
- /* XXX: Optimization required:
- * If...we have allowed symlinks, and
- * if...we find the segment exists in the directory list
- * skip the lstat and dummy up an APR_DIR value for r->finfo
- * this means case sensitive platforms go quite quickly.
- * Case insensitive platforms might be given the wrong path,
- * but if it's not found in the cache, then we know we have
- * something to test (the misspelling is never cached.)
- */
+ if (!this_dir)
+ continue;
- /* We choose apr_lstat here, rather that apr_stat, so that we
- * capture this path object rather than its target. We will
- * replace the info with our target's info below. We especially
- * want the name of this 'link' object, not the name of its
- * target, if we are fixing case.
- */
- rv = apr_lstat(&r->finfo, r->filename, APR_FINFO_MIN | APR_FINFO_NAME, r->pool);
+ if (this_dir->opts & OPT_UNSET) {
+ opts_add = (opts_add & ~this_dir->opts_remove) | this_dir->opts_add;
+ opts_remove = (opts_remove & ~this_dir->opts_add)
+ | this_dir->opts_remove;
+ opts = (opts & ~opts_remove) | opts_add;
+ }
+ else {
+ opts = this_dir->opts;
+ opts_add = this_dir->opts_add;
+ opts_remove = this_dir->opts_remove;
+ }
+ if (!(this_dir->override & OR_UNSET)) {
+ override = this_dir->override;
+ }
+ }
- if (APR_STATUS_IS_ENOENT(rv)) {
- /* Nothing? That could be nice. But our directory walk is done.
+ /* If .htaccess files are enabled, check for one, provided we
+ * have reached a real path.
*/
- r->finfo.filetype = APR_NOFILE;
- break;
- }
- else if (APR_STATUS_IS_EACCES(rv)) {
- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
- "access to %s denied", r->uri);
- return r->status = HTTP_FORBIDDEN;
- }
- else if ((rv != APR_SUCCESS && rv != APR_INCOMPLETE)
- || !(r->finfo.valid & APR_FINFO_TYPE)) {
- /* If we hit ENOTDIR, we must have over-optimized, deny
- * rather than assume not found.
+ if (seg >= startseg && override) {
+ ap_conf_vector_t *htaccess_conf = NULL;
+
+ res = ap_parse_htaccess(&htaccess_conf, r, override,
+ apr_pstrdup(r->pool, r->filename),
+ sconf->access_name);
+ if (res)
+ return res;
+
+ if (htaccess_conf) {
+
+ /* If we merged this same htaccess last time, reuse it...
+ * this wouldn't work except that we cache the htaccess
+ * sections for the lifetime of the request, so we match
+ * the same conf. Good planning (no, pure luck ;)
+ */
+ if (matches) {
+ if (last_walk->matched == htaccess_conf) {
+ now_merged = last_walk->merged;
+ ++last_walk;
+ --matches;
+ goto minimerge2;
+ }
+ /* We fell out of sync. This is our own copy of walked,
+ * so truncate the remaining matches and reset remaining.
+ */
+ cache->walked->nelts -= matches;
+ matches = 0;
+ }
+
+ if (now_merged)
+ now_merged = ap_merge_per_dir_configs(r->pool,
+ now_merged,
+ htaccess_conf);
+ else
+ now_merged = htaccess_conf;
+
+ last_walk = (walk_walked_t*)apr_array_push(cache->walked);
+ last_walk->matched = htaccess_conf;
+ last_walk->merged = now_merged;
+
+ /* Do a mini-merge to our globally-based running calculations of
+ * core_dir->override and core_dir->opts, since now_merged
+ * never considered the global config. Of course, if there is no
+ * core config at this level, continue without a thought.
+ * See core.c::merge_core_dir_configs() for explanation.
+ */
+minimerge2:
+ this_dir = ap_get_module_config(htaccess_conf, &core_module);
+
+ if (!this_dir)
+ continue;
+
+ if (this_dir->opts & OPT_UNSET) {
+ opts_add = (opts_add & ~this_dir->opts_remove) | this_dir->opts_add;
+ opts_remove = (opts_remove & ~this_dir->opts_add)
+ | this_dir->opts_remove;
+ opts = (opts & ~opts_remove) | opts_add;
+ }
+ else {
+ opts = this_dir->opts;
+ opts_add = this_dir->opts_add;
+ opts_remove = this_dir->opts_remove;
+ }
+ if (!(this_dir->override & OR_UNSET)) {
+ override = this_dir->override;
+ }
+ }
+ }
+
+ /* That temporary trailing slash was useful, now drop it.
*/
- ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
- "access to %s failed", r->uri);
- return r->status = HTTP_FORBIDDEN;
- }
- else if ((res = check_safe_file(r))) {
- r->status = res;
- return res;
- }
+ if (seg > startseg)
+ r->filename[strlen(r->filename) - 1] = '\0';
- /* Fix up the path now if we have a name, and they don't agree
- */
- if ((r->finfo.valid & APR_FINFO_NAME)
- && strcmp(seg_name, r->finfo.name)) {
- /* TODO: provide users an option that an internal/external
- * redirect is required here?
+ /* Time for all good things to come to an end?
*/
- strcpy(seg_name, r->finfo.name);
- }
+ if (!r->path_info || !*r->path_info)
+ break;
- if (r->finfo.filetype == APR_LNK)
- {
- /* Is this an possibly acceptable symlink?
+ /* Now it's time for the next segment...
+ * We will assume the next element is an end node, and fix it up
+ * below as necessary...
*/
- if ((res = resolve_symlink(r->filename, &r->finfo,
- core_dir->opts, r->pool)) != OK) {
- ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
- "Symbolic link not allowed: %s", r->filename);
- return r->status = res;
+
+ seg_name = strchr(r->filename, '\0');
+ delim = strchr(r->path_info + (*r->path_info == '/' ? 1 : 0), '/');
+ if (delim) {
+ *delim = '\0';
+ strcpy(seg_name, r->path_info);
+ r->path_info = delim;
+ *delim = '/';
+ }
+ else {
+ strcpy(seg_name, r->path_info);
+ r->path_info = strchr(r->path_info, '\0');
}
+ if (*seg_name == '/')
+ ++seg_name;
+
+ /* If nothing remained but a '/' string, we are finished
+ */
+ if (!*seg_name)
+ break;
+
+ /* XXX: Optimization required:
+ * If...we have allowed symlinks, and
+ * if...we find the segment exists in the directory list
+ * skip the lstat and dummy up an APR_DIR value for r->finfo
+ * this means case sensitive platforms go quite quickly.
+ * Case insensitive platforms might be given the wrong path,
+ * but if it's not found in the cache, then we know we have
+ * something to test (the misspelling is never cached.)
+ */
- /* Ok, we are done with the link's info, test the real target
+ /* We choose apr_lstat here, rather that apr_stat, so that we
+ * capture this path object rather than its target. We will
+ * replace the info with our target's info below. We especially
+ * want the name of this 'link' object, not the name of its
+ * target, if we are fixing case.
*/
- if (r->finfo.filetype == APR_REG) {
- /* That was fun, nothing left for us here
+ rv = apr_lstat(&r->finfo, r->filename, APR_FINFO_MIN | APR_FINFO_NAME, r->pool);
+
+ if (APR_STATUS_IS_ENOENT(rv)) {
+ /* Nothing? That could be nice. But our directory walk is done.
*/
+ r->finfo.filetype = APR_NOFILE;
break;
}
- else if (r->finfo.filetype != APR_DIR) {
- ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
- "symlink doesn't point to a file or directory: %s",
- r->filename);
+ else if (APR_STATUS_IS_EACCES(rv)) {
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "access to %s denied", r->uri);
return r->status = HTTP_FORBIDDEN;
}
- }
+ else if ((rv != APR_SUCCESS && rv != APR_INCOMPLETE)
+ || !(r->finfo.valid & APR_FINFO_TYPE)) {
+ /* If we hit ENOTDIR, we must have over-optimized, deny
+ * rather than assume not found.
+ */
+ ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+ "access to %s failed", r->uri);
+ return r->status = HTTP_FORBIDDEN;
+ }
+ else if ((res = check_safe_file(r))) {
+ r->status = res;
+ return res;
+ }
- ++seg;
- } while (r->finfo.filetype == APR_DIR);
+ /* Fix up the path now if we have a name, and they don't agree
+ */
+ if ((r->finfo.valid & APR_FINFO_NAME)
+ && strcmp(seg_name, r->finfo.name)) {
+ /* TODO: provide users an option that an internal/external
+ * redirect is required here?
+ */
+ strcpy(seg_name, r->finfo.name);
+ }
- /*
- * Now we'll deal with the regexes.
- */
- for (; sec_idx < num_sec; ++sec_idx) {
+ if (r->finfo.filetype == APR_LNK)
+ {
+ /* Is this an possibly acceptable symlink?
+ */
+ if ((res = resolve_symlink(r->filename, &r->finfo,
+ opts, r->pool)) != OK) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ "Symbolic link not allowed: %s", r->filename);
+ return r->status = res;
+ }
- entry_config = sec_ent[sec_idx];
- entry_core = ap_get_module_config(entry_config, &core_module);
+ /* Ok, we are done with the link's info, test the real target
+ */
+ if (r->finfo.filetype == APR_REG) {
+ /* That was fun, nothing left for us here
+ */
+ break;
+ }
+ else if (r->finfo.filetype != APR_DIR) {
+ ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
+ "symlink doesn't point to a file or directory: %s",
+ r->filename);
+ return r->status = HTTP_FORBIDDEN;
+ }
+ }
- if (!entry_core->r) {
- continue;
- }
- if (!ap_regexec(entry_core->r, r->filename, 0, NULL, REG_NOTEOL)) {
- per_dir_defaults = ap_merge_per_dir_configs(r->pool,
- per_dir_defaults,
- entry_config);
+ ++seg;
+ } while (r->finfo.filetype == APR_DIR);
+
+ /*
+ * Now we'll deal with the regexes, note we pick up sec_idx
+ * where we left off (we gave up after we hit entry_core->r)
+ */
+ for (; sec_idx < num_sec; ++sec_idx) {
+
+ core_dir_config *entry_core;
+ entry_core = ap_get_module_config(sec_ent[sec_idx], &core_module);
+
+ if (!entry_core->r)
+ continue;
+
+ if (ap_regexec(entry_core->r, r->filename, 0, NULL, REG_NOTEOL))
+ continue;
+
+ /* If we merged this same section last time, reuse it
+ */
+ if (matches) {
+ if (last_walk->matched == sec_ent[sec_idx]) {
+ now_merged = last_walk->merged;
+ ++last_walk;
+ --matches;
+ goto minimerge;
+ }
+ /* We fell out of sync. This is our own copy of walked,
+ * so truncate the remaining matches and reset remaining.
+ */
+ cache->walked->nelts -= matches;
+ matches = 0;
+ }
+
+ if (now_merged)
+ now_merged = ap_merge_per_dir_configs(r->pool,
+ now_merged,
+ sec_ent[sec_idx]);
+ else
+ now_merged = sec_ent[sec_idx];
+
+ last_walk = (walk_walked_t*)apr_array_push(cache->walked);
+ last_walk->matched = sec_ent[sec_idx];
+ last_walk->merged = now_merged;
}
+
+ /* Whoops - everything matched in sequence, but the original walk
+ * found some additional matches. Truncate them.
+ */
+ if (matches)
+ cache->walked->nelts -= matches;
}
- r->per_dir_config = per_dir_defaults;
/* It seems this shouldn't be needed anymore. We translated the symlink above
x into a real resource, and should have died up there. Even if we keep this,