1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17/* 18 * mod_dir.c: handle default index files, and trailing-/ redirects 19 */ 20 21#include "apr_strings.h" 22#include "apr_lib.h" 23#include "ap_config.h" 24#include "httpd.h" 25#include "http_config.h" 26#include "http_core.h" 27#include "http_request.h" 28#include "http_protocol.h" 29#include "http_log.h" 30#include "http_main.h" 31#include "util_script.h" 32#include "mod_rewrite.h" 33 34module AP_MODULE_DECLARE_DATA dir_module; 35 36typedef enum { 37 MODDIR_OFF = 0, 38 MODDIR_ON, 39 MODDIR_UNSET 40} moddir_cfg; 41 42#define REDIRECT_OFF 0 43#define REDIRECT_UNSET 1 44 45typedef struct dir_config_struct { 46 apr_array_header_t *index_names; 47 moddir_cfg do_slash; 48 moddir_cfg checkhandler; 49 int redirect_index; 50 const char *dflt; 51} dir_config_rec; 52 53#define DIR_CMD_PERMS OR_INDEXES 54 55static const char *add_index(cmd_parms *cmd, void *dummy, const char *arg) 56{ 57 dir_config_rec *d = dummy; 58 const char *t, *w; 59 int count = 0; 60 61 if (!d->index_names) { 62 d->index_names = apr_array_make(cmd->pool, 2, sizeof(char *)); 63 } 64 65 t = arg; 66 while ((w = ap_getword_conf(cmd->pool, &t)) && w[0]) { 67 if (count == 0 && !strcasecmp(w, "disabled")) { 68 /* peek to see if "disabled" is first in a series of arguments */ 69 const char *tt = t; 70 const char *ww = ap_getword_conf(cmd->temp_pool, &tt); 71 if (ww[0] == '\0') { 72 /* "disabled" is first, and alone */ 73 apr_array_clear(d->index_names); 74 break; 75 } 76 } 77 *(const char **)apr_array_push(d->index_names) = w; 78 count++; 79 } 80 81 return NULL; 82} 83 84static const char *configure_slash(cmd_parms *cmd, void *d_, int arg) 85{ 86 dir_config_rec *d = d_; 87 88 d->do_slash = arg ? MODDIR_ON : MODDIR_OFF; 89 return NULL; 90} 91static const char *configure_checkhandler(cmd_parms *cmd, void *d_, int arg) 92{ 93 dir_config_rec *d = d_; 94 95 d->checkhandler = arg ? MODDIR_ON : MODDIR_OFF; 96 return NULL; 97} 98static const char *configure_redirect(cmd_parms *cmd, void *d_, const char *arg1) 99{ 100 dir_config_rec *d = d_; 101 int status; 102 103 if (!strcasecmp(arg1, "ON")) 104 status = HTTP_MOVED_TEMPORARILY; 105 else if (!strcasecmp(arg1, "OFF")) 106 status = REDIRECT_OFF; 107 else if (!strcasecmp(arg1, "permanent")) 108 status = HTTP_MOVED_PERMANENTLY; 109 else if (!strcasecmp(arg1, "temp")) 110 status = HTTP_MOVED_TEMPORARILY; 111 else if (!strcasecmp(arg1, "seeother")) 112 status = HTTP_SEE_OTHER; 113 else if (apr_isdigit(*arg1)) { 114 status = atoi(arg1); 115 if (!ap_is_HTTP_REDIRECT(status)) { 116 return "DirectoryIndexRedirect only accepts values between 300 and 399"; 117 } 118 } 119 else { 120 return "DirectoryIndexRedirect ON|OFF|permanent|temp|seeother|3xx"; 121 } 122 123 d->redirect_index = status; 124 return NULL; 125} 126static const command_rec dir_cmds[] = 127{ 128 AP_INIT_TAKE1("FallbackResource", ap_set_string_slot, 129 (void*)APR_OFFSETOF(dir_config_rec, dflt), 130 DIR_CMD_PERMS, "Set a default handler"), 131 AP_INIT_RAW_ARGS("DirectoryIndex", add_index, NULL, DIR_CMD_PERMS, 132 "a list of file names"), 133 AP_INIT_FLAG("DirectorySlash", configure_slash, NULL, DIR_CMD_PERMS, 134 "On or Off"), 135 AP_INIT_FLAG("DirectoryCheckHandler", configure_checkhandler, NULL, DIR_CMD_PERMS, 136 "On or Off"), 137 AP_INIT_TAKE1("DirectoryIndexRedirect", configure_redirect, 138 NULL, DIR_CMD_PERMS, "On, Off, or a 3xx status code."), 139 140 {NULL} 141}; 142 143static void *create_dir_config(apr_pool_t *p, char *dummy) 144{ 145 dir_config_rec *new = apr_pcalloc(p, sizeof(dir_config_rec)); 146 147 new->index_names = NULL; 148 new->do_slash = MODDIR_UNSET; 149 new->checkhandler = MODDIR_UNSET; 150 new->redirect_index = REDIRECT_UNSET; 151 return (void *) new; 152} 153 154static void *merge_dir_configs(apr_pool_t *p, void *basev, void *addv) 155{ 156 dir_config_rec *new = apr_pcalloc(p, sizeof(dir_config_rec)); 157 dir_config_rec *base = (dir_config_rec *)basev; 158 dir_config_rec *add = (dir_config_rec *)addv; 159 160 new->index_names = add->index_names ? add->index_names : base->index_names; 161 new->do_slash = 162 (add->do_slash == MODDIR_UNSET) ? base->do_slash : add->do_slash; 163 new->checkhandler = 164 (add->checkhandler == MODDIR_UNSET) ? base->checkhandler : add->checkhandler; 165 new->redirect_index= 166 (add->redirect_index == REDIRECT_UNSET) ? base->redirect_index : add->redirect_index; 167 new->dflt = add->dflt ? add->dflt : base->dflt; 168 return new; 169} 170 171static int fixup_dflt(request_rec *r) 172{ 173 dir_config_rec *d = ap_get_module_config(r->per_dir_config, &dir_module); 174 const char *name_ptr; 175 request_rec *rr; 176 int error_notfound = 0; 177 178 name_ptr = d->dflt; 179 if ((name_ptr == NULL) || !(strcasecmp(name_ptr,"disabled"))){ 180 return DECLINED; 181 } 182 /* XXX: if FallbackResource points to something that doesn't exist, 183 * this may recurse until it hits the limit for internal redirects 184 * before returning an Internal Server Error. 185 */ 186 187 /* The logic of this function is basically cloned and simplified 188 * from fixup_dir below. See the comments there. 189 */ 190 if (r->args != NULL) { 191 name_ptr = apr_pstrcat(r->pool, name_ptr, "?", r->args, NULL); 192 } 193 rr = ap_sub_req_lookup_uri(name_ptr, r, r->output_filters); 194 if (rr->status == HTTP_OK 195 && ( (rr->handler && !strcmp(rr->handler, "proxy-server")) 196 || rr->finfo.filetype == APR_REG)) { 197 ap_internal_fast_redirect(rr, r); 198 return OK; 199 } 200 else if (ap_is_HTTP_REDIRECT(rr->status)) { 201 202 apr_pool_join(r->pool, rr->pool); 203 r->notes = apr_table_overlay(r->pool, r->notes, rr->notes); 204 r->headers_out = apr_table_overlay(r->pool, r->headers_out, 205 rr->headers_out); 206 r->err_headers_out = apr_table_overlay(r->pool, r->err_headers_out, 207 rr->err_headers_out); 208 error_notfound = rr->status; 209 } 210 else if (rr->status && rr->status != HTTP_NOT_FOUND 211 && rr->status != HTTP_OK) { 212 error_notfound = rr->status; 213 } 214 215 ap_destroy_sub_req(rr); 216 if (error_notfound) { 217 return error_notfound; 218 } 219 220 /* nothing for us to do, pass on through */ 221 return DECLINED; 222} 223 224static int fixup_dir(request_rec *r) 225{ 226 dir_config_rec *d; 227 char *dummy_ptr[1]; 228 char **names_ptr; 229 int num_names; 230 int error_notfound = 0; 231 232 /* In case mod_mime wasn't present, and no handler was assigned. */ 233 if (!r->handler) { 234 r->handler = DIR_MAGIC_TYPE; 235 } 236 237 /* Never tolerate path_info on dir requests */ 238 if (r->path_info && *r->path_info) { 239 return DECLINED; 240 } 241 242 d = (dir_config_rec *)ap_get_module_config(r->per_dir_config, 243 &dir_module); 244 245 /* Redirect requests that are not '/' terminated */ 246 if (r->uri[0] == '\0' || r->uri[strlen(r->uri) - 1] != '/') 247 { 248 char *ifile; 249 250 if (!d->do_slash) { 251 return DECLINED; 252 } 253 254 /* Only redirect non-get requests if we have no note to warn 255 * that this browser cannot handle redirs on non-GET requests 256 * (such as Microsoft's WebFolders). 257 */ 258 if ((r->method_number != M_GET) 259 && apr_table_get(r->subprocess_env, "redirect-carefully")) { 260 return DECLINED; 261 } 262 263 if (r->args != NULL) { 264 ifile = apr_pstrcat(r->pool, ap_escape_uri(r->pool, r->uri), 265 "/", "?", r->args, NULL); 266 } 267 else { 268 ifile = apr_pstrcat(r->pool, ap_escape_uri(r->pool, r->uri), 269 "/", NULL); 270 } 271 272 apr_table_setn(r->headers_out, "Location", 273 ap_construct_url(r->pool, ifile, r)); 274 return HTTP_MOVED_PERMANENTLY; 275 } 276 277 /* we're running between mod_rewrites fixup and its internal redirect handler, step aside */ 278 if (!strcmp(r->handler, REWRITE_REDIRECT_HANDLER_NAME)) { 279 return DECLINED; 280 } 281 282 if (d->checkhandler == MODDIR_ON && strcmp(r->handler, DIR_MAGIC_TYPE)) { 283 return DECLINED; 284 } 285 286 if (d->index_names) { 287 names_ptr = (char **)d->index_names->elts; 288 num_names = d->index_names->nelts; 289 } 290 else { 291 dummy_ptr[0] = AP_DEFAULT_INDEX; 292 names_ptr = dummy_ptr; 293 num_names = 1; 294 } 295 296 for (; num_names; ++names_ptr, --num_names) { 297 /* XXX: Is this name_ptr considered escaped yet, or not??? */ 298 char *name_ptr = *names_ptr; 299 request_rec *rr; 300 301 /* Once upon a time args were handled _after_ the successful redirect. 302 * But that redirect might then _refuse_ the given r->args, creating 303 * a nasty tangle. It seems safer to consider the r->args while we 304 * determine if name_ptr is our viable index, and therefore set them 305 * up correctly on redirect. 306 */ 307 if (r->args != NULL) { 308 name_ptr = apr_pstrcat(r->pool, name_ptr, "?", r->args, NULL); 309 } 310 311 rr = ap_sub_req_lookup_uri(name_ptr, r, r->output_filters); 312 313 /* The sub request lookup is very liberal, and the core map_to_storage 314 * handler will almost always result in HTTP_OK as /foo/index.html 315 * may be /foo with PATH_INFO="/index.html", or even / with 316 * PATH_INFO="/foo/index.html". To get around this we insist that the 317 * the index be a regular filetype. 318 * 319 * Another reason is that the core handler also makes the assumption 320 * that if r->finfo is still NULL by the time it gets called, the 321 * file does not exist. 322 */ 323 if (rr->status == HTTP_OK 324 && ( (rr->handler && !strcmp(rr->handler, "proxy-server")) 325 || rr->finfo.filetype == APR_REG)) { 326 327 if (ap_is_HTTP_REDIRECT(d->redirect_index)) { 328 apr_table_setn(r->headers_out, "Location", ap_construct_url(r->pool, rr->uri, r)); 329 return d->redirect_index; 330 } 331 332 ap_internal_fast_redirect(rr, r); 333 return OK; 334 } 335 336 /* If the request returned a redirect, propagate it to the client */ 337 338 if (ap_is_HTTP_REDIRECT(rr->status) 339 || (rr->status == HTTP_NOT_ACCEPTABLE && num_names == 1) 340 || (rr->status == HTTP_UNAUTHORIZED && num_names == 1)) { 341 342 apr_pool_join(r->pool, rr->pool); 343 error_notfound = rr->status; 344 r->notes = apr_table_overlay(r->pool, r->notes, rr->notes); 345 r->headers_out = apr_table_overlay(r->pool, r->headers_out, 346 rr->headers_out); 347 r->err_headers_out = apr_table_overlay(r->pool, r->err_headers_out, 348 rr->err_headers_out); 349 return error_notfound; 350 } 351 352 /* If the request returned something other than 404 (or 200), 353 * it means the module encountered some sort of problem. To be 354 * secure, we should return the error, rather than allow autoindex 355 * to create a (possibly unsafe) directory index. 356 * 357 * So we store the error, and if none of the listed files 358 * exist, we return the last error response we got, instead 359 * of a directory listing. 360 */ 361 if (rr->status && rr->status != HTTP_NOT_FOUND 362 && rr->status != HTTP_OK) { 363 error_notfound = rr->status; 364 } 365 366 ap_destroy_sub_req(rr); 367 } 368 369 if (error_notfound) { 370 return error_notfound; 371 } 372 373 /* record what we tried, mostly for the benefit of mod_autoindex */ 374 apr_table_set(r->notes, "dir-index-names", 375 d->index_names ? 376 apr_array_pstrcat(r->pool, d->index_names, ','): 377 AP_DEFAULT_INDEX); 378 379 /* nothing for us to do, pass on through */ 380 return DECLINED; 381} 382 383static int dir_fixups(request_rec *r) 384{ 385 if (r->finfo.filetype == APR_DIR) { 386 /* serve up a directory */ 387 return fixup_dir(r); 388 } 389 else if ((r->finfo.filetype == APR_NOFILE) && (r->handler == NULL)) { 390 /* No handler and nothing in the filesystem - use fallback */ 391 return fixup_dflt(r); 392 } 393 return DECLINED; 394} 395 396static void register_hooks(apr_pool_t *p) 397{ 398 ap_hook_fixups(dir_fixups,NULL,NULL,APR_HOOK_LAST); 399} 400 401AP_DECLARE_MODULE(dir) = { 402 STANDARD20_MODULE_STUFF, 403 create_dir_config, /* create per-directory config structure */ 404 merge_dir_configs, /* merge per-directory config structures */ 405 NULL, /* create per-server config structure */ 406 NULL, /* merge per-server config structures */ 407 dir_cmds, /* command apr_table_t */ 408 register_hooks /* register hooks */ 409}; 410