]> granicus.if.org Git - apache/blob - modules/generators/mod_autoindex.c
* Generate valid XHTML output by adding the xhtml namespace.
[apache] / modules / generators / mod_autoindex.c
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_autoindex.c: Handles the on-the-fly html index generation
19  *
20  * Rob McCool
21  * 3/23/93
22  *
23  * Adapted to Apache by rst.
24  *
25  * Version sort added by Martin Pool <mbp@humbug.org.au>.
26  */
27
28 #include "apr_strings.h"
29 #include "apr_fnmatch.h"
30 #include "apr_strings.h"
31 #include "apr_lib.h"
32
33 #define APR_WANT_STRFUNC
34 #include "apr_want.h"
35
36 #include "ap_config.h"
37 #include "httpd.h"
38 #include "http_config.h"
39 #include "http_core.h"
40 #include "http_request.h"
41 #include "http_protocol.h"
42 #include "http_log.h"
43 #include "http_main.h"
44 #include "util_script.h"
45
46 #include "mod_core.h"
47
48 module AP_MODULE_DECLARE_DATA autoindex_module;
49
50 /****************************************************************
51  *
52  * Handling configuration directives...
53  */
54
55 #define NO_OPTIONS          (1 <<  0)  /* Indexing options */
56 #define ICONS_ARE_LINKS     (1 <<  1)
57 #define SCAN_HTML_TITLES    (1 <<  2)
58 #define SUPPRESS_ICON       (1 <<  3)
59 #define SUPPRESS_LAST_MOD   (1 <<  4)
60 #define SUPPRESS_SIZE       (1 <<  5)
61 #define SUPPRESS_DESC       (1 <<  6)
62 #define SUPPRESS_PREAMBLE   (1 <<  7)
63 #define SUPPRESS_COLSORT    (1 <<  8)
64 #define SUPPRESS_RULES      (1 <<  9)
65 #define FOLDERS_FIRST       (1 << 10)
66 #define VERSION_SORT        (1 << 11)
67 #define TRACK_MODIFIED      (1 << 12)
68 #define FANCY_INDEXING      (1 << 13)
69 #define TABLE_INDEXING      (1 << 14)
70 #define IGNORE_CLIENT       (1 << 15)
71 #define IGNORE_CASE         (1 << 16)
72 #define EMIT_XHTML          (1 << 17)
73 #define SHOW_FORBIDDEN      (1 << 18)
74
75 #define K_NOADJUST 0
76 #define K_ADJUST 1
77 #define K_UNSET 2
78
79 /*
80  * Define keys for sorting.
81  */
82 #define K_NAME 'N'              /* Sort by file name (default) */
83 #define K_LAST_MOD 'M'          /* Last modification date */
84 #define K_SIZE 'S'              /* Size (absolute, not as displayed) */
85 #define K_DESC 'D'              /* Description */
86 #define K_VALID "NMSD"          /* String containing _all_ valid K_ opts */
87
88 #define D_ASCENDING 'A'
89 #define D_DESCENDING 'D'
90 #define D_VALID "AD"            /* String containing _all_ valid D_ opts */
91
92 /*
93  * These are the dimensions of the default icons supplied with Apache.
94  */
95 #define DEFAULT_ICON_WIDTH 20
96 #define DEFAULT_ICON_HEIGHT 22
97
98 /*
99  * Other default dimensions.
100  */
101 #define DEFAULT_NAME_WIDTH 23
102 #define DEFAULT_DESC_WIDTH 23
103
104 struct item {
105     char *type;
106     char *apply_to;
107     char *apply_path;
108     char *data;
109 };
110
111 typedef struct ai_desc_t {
112     char *pattern;
113     char *description;
114     int full_path;
115     int wildcards;
116 } ai_desc_t;
117
118 typedef struct autoindex_config_struct {
119
120     char *default_icon;
121     char *style_sheet;
122     apr_int32_t opts;
123     apr_int32_t incremented_opts;
124     apr_int32_t decremented_opts;
125     int name_width;
126     int name_adjust;
127     int desc_width;
128     int desc_adjust;
129     int icon_width;
130     int icon_height;
131     char default_keyid;
132     char default_direction;
133
134     apr_array_header_t *icon_list;
135     apr_array_header_t *alt_list;
136     apr_array_header_t *desc_list;
137     apr_array_header_t *ign_list;
138     apr_array_header_t *hdr_list;
139     apr_array_header_t *rdme_list;
140
141     char *ctype;
142     char *charset;
143 } autoindex_config_rec;
144
145 static char c_by_encoding, c_by_type, c_by_path;
146
147 #define BY_ENCODING &c_by_encoding
148 #define BY_TYPE &c_by_type
149 #define BY_PATH &c_by_path
150
151 /*
152  * This routine puts the standard HTML header at the top of the index page.
153  * We include the DOCTYPE because we may be using features therefrom (i.e.,
154  * HEIGHT and WIDTH attributes on the icons if we're FancyIndexing).
155  */
156 static void emit_preamble(request_rec *r, int xhtml, const char *title)
157 {
158     autoindex_config_rec *d;
159
160     d = (autoindex_config_rec *) ap_get_module_config(r->per_dir_config,
161                                                       &autoindex_module);
162
163     if (xhtml) {
164         ap_rvputs(r, DOCTYPE_XHTML_1_0T,
165                   "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
166                   "<head>\n  <title>Index of ", title,
167                   "</title>\n", NULL);
168     } else {
169         ap_rvputs(r, DOCTYPE_HTML_3_2,
170                   "<html>\n <head>\n"
171                   "<title>Index of ", title,
172                   "</title>\n", NULL);
173     }
174
175     if (d->style_sheet != NULL) {
176         ap_rvputs(r, "  <link rel=\"stylesheet\" href=\"", d->style_sheet,
177                 "\" type=\"text/css\"", xhtml ? " />\n" : ">\n", NULL);
178     }
179     ap_rvputs(r, " </head>\n <body>\n", NULL);
180 }
181
182 static void push_item(apr_array_header_t *arr, char *type, const char *to,
183                       const char *path, const char *data)
184 {
185     struct item *p = (struct item *) apr_array_push(arr);
186
187     if (!to) {
188         to = "";
189     }
190     if (!path) {
191         path = "";
192     }
193
194     p->type = type;
195     p->data = data ? apr_pstrdup(arr->pool, data) : NULL;
196     p->apply_path = apr_pstrcat(arr->pool, path, "*", NULL);
197
198     if ((type == BY_PATH) && (!ap_is_matchexp(to))) {
199         p->apply_to = apr_pstrcat(arr->pool, "*", to, NULL);
200     }
201     else if (to) {
202         p->apply_to = apr_pstrdup(arr->pool, to);
203     }
204     else {
205         p->apply_to = NULL;
206     }
207 }
208
209 static const char *add_alt(cmd_parms *cmd, void *d, const char *alt,
210                            const char *to)
211 {
212     if (cmd->info == BY_PATH) {
213         if (!strcmp(to, "**DIRECTORY**")) {
214             to = "^^DIRECTORY^^";
215         }
216     }
217     if (cmd->info == BY_ENCODING) {
218         char *tmp = apr_pstrdup(cmd->pool, to);
219         ap_str_tolower(tmp);
220         to = tmp;
221     }
222
223     push_item(((autoindex_config_rec *) d)->alt_list, cmd->info, to,
224               cmd->path, alt);
225     return NULL;
226 }
227
228 static const char *add_icon(cmd_parms *cmd, void *d, const char *icon,
229                             const char *to)
230 {
231     char *iconbak = apr_pstrdup(cmd->pool, icon);
232
233     if (icon[0] == '(') {
234         char *alt;
235         char *cl = strchr(iconbak, ')');
236
237         if (cl == NULL) {
238             return "missing closing paren";
239         }
240         alt = ap_getword_nc(cmd->pool, &iconbak, ',');
241         *cl = '\0';                             /* Lose closing paren */
242         add_alt(cmd, d, &alt[1], to);
243     }
244     if (cmd->info == BY_PATH) {
245         if (!strcmp(to, "**DIRECTORY**")) {
246             to = "^^DIRECTORY^^";
247         }
248     }
249     if (cmd->info == BY_ENCODING) {
250         char *tmp = apr_pstrdup(cmd->pool, to);
251         ap_str_tolower(tmp);
252         to = tmp;
253     }
254
255     push_item(((autoindex_config_rec *) d)->icon_list, cmd->info, to,
256               cmd->path, iconbak);
257     return NULL;
258 }
259
260 /*
261  * Add description text for a filename pattern.  If the pattern has
262  * wildcards already (or we need to add them), add leading and
263  * trailing wildcards to it to ensure substring processing.  If the
264  * pattern contains a '/' anywhere, force wildcard matching mode,
265  * add a slash to the prefix so that "bar/bletch" won't be matched
266  * by "foobar/bletch", and make a note that there's a delimiter;
267  * the matching routine simplifies to just the actual filename
268  * whenever it can.  This allows definitions in parent directories
269  * to be made for files in subordinate ones using relative paths.
270  */
271
272 /*
273  * Absent a strcasestr() function, we have to force wildcards on
274  * systems for which "AAA" and "aaa" mean the same file.
275  */
276 #ifdef CASE_BLIND_FILESYSTEM
277 #define WILDCARDS_REQUIRED 1
278 #else
279 #define WILDCARDS_REQUIRED 0
280 #endif
281
282 static const char *add_desc(cmd_parms *cmd, void *d, const char *desc,
283                             const char *to)
284 {
285     autoindex_config_rec *dcfg = (autoindex_config_rec *) d;
286     ai_desc_t *desc_entry;
287     char *prefix = "";
288
289     desc_entry = (ai_desc_t *) apr_array_push(dcfg->desc_list);
290     desc_entry->full_path = (ap_strchr_c(to, '/') == NULL) ? 0 : 1;
291     desc_entry->wildcards = (WILDCARDS_REQUIRED
292                              || desc_entry->full_path
293                              || apr_fnmatch_test(to));
294     if (desc_entry->wildcards) {
295         prefix = desc_entry->full_path ? "*/" : "*";
296         desc_entry->pattern = apr_pstrcat(dcfg->desc_list->pool,
297                                           prefix, to, "*", NULL);
298     }
299     else {
300         desc_entry->pattern = apr_pstrdup(dcfg->desc_list->pool, to);
301     }
302     desc_entry->description = apr_pstrdup(dcfg->desc_list->pool, desc);
303     return NULL;
304 }
305
306 static const char *add_ignore(cmd_parms *cmd, void *d, const char *ext)
307 {
308     push_item(((autoindex_config_rec *) d)->ign_list, 0, ext, cmd->path, NULL);
309     return NULL;
310 }
311
312 static const char *add_header(cmd_parms *cmd, void *d, const char *name)
313 {
314     push_item(((autoindex_config_rec *) d)->hdr_list, 0, NULL, cmd->path,
315               name);
316     return NULL;
317 }
318
319 static const char *add_readme(cmd_parms *cmd, void *d, const char *name)
320 {
321     push_item(((autoindex_config_rec *) d)->rdme_list, 0, NULL, cmd->path,
322               name);
323     return NULL;
324 }
325
326 static const char *add_opts(cmd_parms *cmd, void *d, int argc, char *const argv[])
327 {
328     int i;
329     char *w;
330     apr_int32_t opts;
331     apr_int32_t opts_add;
332     apr_int32_t opts_remove;
333     char action;
334     autoindex_config_rec *d_cfg = (autoindex_config_rec *) d;
335
336     opts = d_cfg->opts;
337     opts_add = d_cfg->incremented_opts;
338     opts_remove = d_cfg->decremented_opts;
339
340     for (i = 0; i < argc; i++) {
341         int option = 0;
342         w = argv[i];
343
344         if ((*w == '+') || (*w == '-')) {
345             action = *(w++);
346         }
347         else {
348             action = '\0';
349         }
350         if (!strcasecmp(w, "FancyIndexing")) {
351             option = FANCY_INDEXING;
352         }
353         else if (!strcasecmp(w, "FoldersFirst")) {
354             option = FOLDERS_FIRST;
355         }
356         else if (!strcasecmp(w, "HTMLTable")) {
357             option = TABLE_INDEXING;
358         }
359         else if (!strcasecmp(w, "IconsAreLinks")) {
360             option = ICONS_ARE_LINKS;
361         }
362         else if (!strcasecmp(w, "IgnoreCase")) {
363             option = IGNORE_CASE;
364         }
365         else if (!strcasecmp(w, "IgnoreClient")) {
366             option = IGNORE_CLIENT;
367         }
368         else if (!strcasecmp(w, "ScanHTMLTitles")) {
369             option = SCAN_HTML_TITLES;
370         }
371         else if (!strcasecmp(w, "SuppressColumnSorting")) {
372             option = SUPPRESS_COLSORT;
373         }
374         else if (!strcasecmp(w, "SuppressDescription")) {
375             option = SUPPRESS_DESC;
376         }
377         else if (!strcasecmp(w, "SuppressHTMLPreamble")) {
378             option = SUPPRESS_PREAMBLE;
379         }
380         else if (!strcasecmp(w, "SuppressIcon")) {
381             option = SUPPRESS_ICON;
382         }
383         else if (!strcasecmp(w, "SuppressLastModified")) {
384             option = SUPPRESS_LAST_MOD;
385         }
386         else if (!strcasecmp(w, "SuppressSize")) {
387             option = SUPPRESS_SIZE;
388         }
389         else if (!strcasecmp(w, "SuppressRules")) {
390             option = SUPPRESS_RULES;
391         }
392         else if (!strcasecmp(w, "TrackModified")) {
393             option = TRACK_MODIFIED;
394         }
395         else if (!strcasecmp(w, "VersionSort")) {
396             option = VERSION_SORT;
397         }
398         else if (!strcasecmp(w, "XHTML")) {
399             option = EMIT_XHTML;
400         }
401         else if (!strcasecmp(w, "ShowForbidden")) {
402             option = SHOW_FORBIDDEN;
403         }
404         else if (!strcasecmp(w, "None")) {
405             if (action != '\0') {
406                 return "Cannot combine '+' or '-' with 'None' keyword";
407             }
408             opts = NO_OPTIONS;
409             opts_add = 0;
410             opts_remove = 0;
411         }
412         else if (!strcasecmp(w, "IconWidth")) {
413             if (action != '-') {
414                 d_cfg->icon_width = DEFAULT_ICON_WIDTH;
415             }
416             else {
417                 d_cfg->icon_width = 0;
418             }
419         }
420         else if (!strncasecmp(w, "IconWidth=", 10)) {
421             if (action == '-') {
422                 return "Cannot combine '-' with IconWidth=n";
423             }
424             d_cfg->icon_width = atoi(&w[10]);
425         }
426         else if (!strcasecmp(w, "IconHeight")) {
427             if (action != '-') {
428                 d_cfg->icon_height = DEFAULT_ICON_HEIGHT;
429             }
430             else {
431                 d_cfg->icon_height = 0;
432             }
433         }
434         else if (!strncasecmp(w, "IconHeight=", 11)) {
435             if (action == '-') {
436                 return "Cannot combine '-' with IconHeight=n";
437             }
438             d_cfg->icon_height = atoi(&w[11]);
439         }
440         else if (!strcasecmp(w, "NameWidth")) {
441             if (action != '-') {
442                 return "NameWidth with no value may only appear as "
443                        "'-NameWidth'";
444             }
445             d_cfg->name_width = DEFAULT_NAME_WIDTH;
446             d_cfg->name_adjust = K_NOADJUST;
447         }
448         else if (!strncasecmp(w, "NameWidth=", 10)) {
449             if (action == '-') {
450                 return "Cannot combine '-' with NameWidth=n";
451             }
452             if (w[10] == '*') {
453                 d_cfg->name_adjust = K_ADJUST;
454             }
455             else {
456                 int width = atoi(&w[10]);
457
458                 if (width && (width < 5)) {
459                     return "NameWidth value must be greater than 5";
460                 }
461                 d_cfg->name_width = width;
462                 d_cfg->name_adjust = K_NOADJUST;
463             }
464         }
465         else if (!strcasecmp(w, "DescriptionWidth")) {
466             if (action != '-') {
467                 return "DescriptionWidth with no value may only appear as "
468                        "'-DescriptionWidth'";
469             }
470             d_cfg->desc_width = DEFAULT_DESC_WIDTH;
471             d_cfg->desc_adjust = K_NOADJUST;
472         }
473         else if (!strncasecmp(w, "DescriptionWidth=", 17)) {
474             if (action == '-') {
475                 return "Cannot combine '-' with DescriptionWidth=n";
476             }
477             if (w[17] == '*') {
478                 d_cfg->desc_adjust = K_ADJUST;
479             }
480             else {
481                 int width = atoi(&w[17]);
482
483                 if (width && (width < 12)) {
484                     return "DescriptionWidth value must be greater than 12";
485                 }
486                 d_cfg->desc_width = width;
487                 d_cfg->desc_adjust = K_NOADJUST;
488             }
489         }
490         else if (!strncasecmp(w, "Type=", 5)) {
491             d_cfg->ctype = apr_pstrdup(cmd->pool, &w[5]);
492         }
493         else if (!strncasecmp(w, "Charset=", 8)) {
494             d_cfg->charset = apr_pstrdup(cmd->pool, &w[8]);
495         }
496         else {
497             return "Invalid directory indexing option";
498         }
499         if (action == '\0') {
500             opts |= option;
501             opts_add = 0;
502             opts_remove = 0;
503         }
504         else if (action == '+') {
505             opts_add |= option;
506             opts_remove &= ~option;
507         }
508         else {
509             opts_remove |= option;
510             opts_add &= ~option;
511         }
512     }
513     if ((opts & NO_OPTIONS) && (opts & ~NO_OPTIONS)) {
514         return "Cannot combine other IndexOptions keywords with 'None'";
515     }
516     d_cfg->incremented_opts = opts_add;
517     d_cfg->decremented_opts = opts_remove;
518     d_cfg->opts = opts;
519     return NULL;
520 }
521
522 static const char *set_default_order(cmd_parms *cmd, void *m,
523                                      const char *direction, const char *key)
524 {
525     autoindex_config_rec *d_cfg = (autoindex_config_rec *) m;
526
527     if (!strcasecmp(direction, "Ascending")) {
528         d_cfg->default_direction = D_ASCENDING;
529     }
530     else if (!strcasecmp(direction, "Descending")) {
531         d_cfg->default_direction = D_DESCENDING;
532     }
533     else {
534         return "First keyword must be 'Ascending' or 'Descending'";
535     }
536
537     if (!strcasecmp(key, "Name")) {
538         d_cfg->default_keyid = K_NAME;
539     }
540     else if (!strcasecmp(key, "Date")) {
541         d_cfg->default_keyid = K_LAST_MOD;
542     }
543     else if (!strcasecmp(key, "Size")) {
544         d_cfg->default_keyid = K_SIZE;
545     }
546     else if (!strcasecmp(key, "Description")) {
547         d_cfg->default_keyid = K_DESC;
548     }
549     else {
550         return "Second keyword must be 'Name', 'Date', 'Size', or "
551                "'Description'";
552     }
553
554     return NULL;
555 }
556
557 #define DIR_CMD_PERMS OR_INDEXES
558
559 static const command_rec autoindex_cmds[] =
560 {
561     AP_INIT_ITERATE2("AddIcon", add_icon, BY_PATH, DIR_CMD_PERMS,
562                      "an icon URL followed by one or more filenames"),
563     AP_INIT_ITERATE2("AddIconByType", add_icon, BY_TYPE, DIR_CMD_PERMS,
564                      "an icon URL followed by one or more MIME types"),
565     AP_INIT_ITERATE2("AddIconByEncoding", add_icon, BY_ENCODING, DIR_CMD_PERMS,
566                      "an icon URL followed by one or more content encodings"),
567     AP_INIT_ITERATE2("AddAlt", add_alt, BY_PATH, DIR_CMD_PERMS,
568                      "alternate descriptive text followed by one or more "
569                      "filenames"),
570     AP_INIT_ITERATE2("AddAltByType", add_alt, BY_TYPE, DIR_CMD_PERMS,
571                      "alternate descriptive text followed by one or more MIME "
572                      "types"),
573     AP_INIT_ITERATE2("AddAltByEncoding", add_alt, BY_ENCODING, DIR_CMD_PERMS,
574                      "alternate descriptive text followed by one or more "
575                      "content encodings"),
576     AP_INIT_TAKE_ARGV("IndexOptions", add_opts, NULL, DIR_CMD_PERMS,
577                       "one or more index options [+|-][]"),
578     AP_INIT_TAKE2("IndexOrderDefault", set_default_order, NULL, DIR_CMD_PERMS,
579                   "{Ascending,Descending} {Name,Size,Description,Date}"),
580     AP_INIT_ITERATE("IndexIgnore", add_ignore, NULL, DIR_CMD_PERMS,
581                     "one or more file extensions"),
582     AP_INIT_ITERATE2("AddDescription", add_desc, BY_PATH, DIR_CMD_PERMS,
583                      "Descriptive text followed by one or more filenames"),
584     AP_INIT_TAKE1("HeaderName", add_header, NULL, DIR_CMD_PERMS,
585                   "a filename"),
586     AP_INIT_TAKE1("ReadmeName", add_readme, NULL, DIR_CMD_PERMS,
587                   "a filename"),
588     AP_INIT_RAW_ARGS("FancyIndexing", ap_set_deprecated, NULL, OR_ALL,
589                      "The FancyIndexing directive is no longer supported. "
590                      "Use IndexOptions FancyIndexing."),
591     AP_INIT_TAKE1("DefaultIcon", ap_set_string_slot,
592                   (void *)APR_OFFSETOF(autoindex_config_rec, default_icon),
593                   DIR_CMD_PERMS, "an icon URL"),
594     AP_INIT_TAKE1("IndexStyleSheet", ap_set_string_slot,
595                   (void *)APR_OFFSETOF(autoindex_config_rec, style_sheet),
596                   DIR_CMD_PERMS, "URL to style sheet"),
597     {NULL}
598 };
599
600 static void *create_autoindex_config(apr_pool_t *p, char *dummy)
601 {
602     autoindex_config_rec *new =
603     (autoindex_config_rec *) apr_pcalloc(p, sizeof(autoindex_config_rec));
604
605     new->icon_width = 0;
606     new->icon_height = 0;
607     new->name_width = DEFAULT_NAME_WIDTH;
608     new->name_adjust = K_UNSET;
609     new->desc_width = DEFAULT_DESC_WIDTH;
610     new->desc_adjust = K_UNSET;
611     new->icon_list = apr_array_make(p, 4, sizeof(struct item));
612     new->alt_list = apr_array_make(p, 4, sizeof(struct item));
613     new->desc_list = apr_array_make(p, 4, sizeof(ai_desc_t));
614     new->ign_list = apr_array_make(p, 4, sizeof(struct item));
615     new->hdr_list = apr_array_make(p, 4, sizeof(struct item));
616     new->rdme_list = apr_array_make(p, 4, sizeof(struct item));
617     new->opts = 0;
618     new->incremented_opts = 0;
619     new->decremented_opts = 0;
620     new->default_keyid = '\0';
621     new->default_direction = '\0';
622
623     return (void *) new;
624 }
625
626 static void *merge_autoindex_configs(apr_pool_t *p, void *basev, void *addv)
627 {
628     autoindex_config_rec *new;
629     autoindex_config_rec *base = (autoindex_config_rec *) basev;
630     autoindex_config_rec *add = (autoindex_config_rec *) addv;
631
632     new = (autoindex_config_rec *) apr_pcalloc(p, sizeof(autoindex_config_rec));
633     new->default_icon = add->default_icon ? add->default_icon
634                                           : base->default_icon;
635     new->style_sheet = add->style_sheet ? add->style_sheet
636                                           : base->style_sheet;
637     new->icon_height = add->icon_height ? add->icon_height : base->icon_height;
638     new->icon_width = add->icon_width ? add->icon_width : base->icon_width;
639
640     new->ctype = add->ctype ? add->ctype : base->ctype;
641     new->charset = add->charset ? add->charset : base->charset;
642
643     new->alt_list = apr_array_append(p, add->alt_list, base->alt_list);
644     new->ign_list = apr_array_append(p, add->ign_list, base->ign_list);
645     new->hdr_list = apr_array_append(p, add->hdr_list, base->hdr_list);
646     new->desc_list = apr_array_append(p, add->desc_list, base->desc_list);
647     new->icon_list = apr_array_append(p, add->icon_list, base->icon_list);
648     new->rdme_list = apr_array_append(p, add->rdme_list, base->rdme_list);
649     if (add->opts & NO_OPTIONS) {
650         /*
651          * If the current directory says 'no options' then we also
652          * clear any incremental mods from being inheritable further down.
653          */
654         new->opts = NO_OPTIONS;
655         new->incremented_opts = 0;
656         new->decremented_opts = 0;
657     }
658     else {
659         /*
660          * If there were any nonincremental options selected for
661          * this directory, they dominate and we don't inherit *anything.*
662          * Contrariwise, we *do* inherit if the only settings here are
663          * incremental ones.
664          */
665         if (add->opts == 0) {
666             new->incremented_opts = (base->incremented_opts
667                                      | add->incremented_opts)
668                                     & ~add->decremented_opts;
669             new->decremented_opts = (base->decremented_opts
670                                      | add->decremented_opts);
671             /*
672              * We may have incremental settings, so make sure we don't
673              * inadvertently inherit an IndexOptions None from above.
674              */
675             new->opts = (base->opts & ~NO_OPTIONS);
676         }
677         else {
678             /*
679              * There are local nonincremental settings, which clear
680              * all inheritance from above.  They *are* the new base settings.
681              */
682             new->opts = add->opts;;
683         }
684         /*
685          * We're guaranteed that there'll be no overlap between
686          * the add-options and the remove-options.
687          */
688         new->opts |= new->incremented_opts;
689         new->opts &= ~new->decremented_opts;
690     }
691     /*
692      * Inherit the NameWidth settings if there aren't any specific to
693      * the new location; otherwise we'll end up using the defaults set in the
694      * config-rec creation routine.
695      */
696     if (add->name_adjust == K_UNSET) {
697         new->name_width = base->name_width;
698         new->name_adjust = base->name_adjust;
699     }
700     else {
701         new->name_width = add->name_width;
702         new->name_adjust = add->name_adjust;
703     }
704
705     /*
706      * Likewise for DescriptionWidth.
707      */
708     if (add->desc_adjust == K_UNSET) {
709         new->desc_width = base->desc_width;
710         new->desc_adjust = base->desc_adjust;
711     }
712     else {
713         new->desc_width = add->desc_width;
714         new->desc_adjust = add->desc_adjust;
715     }
716
717     new->default_keyid = add->default_keyid ? add->default_keyid
718                                             : base->default_keyid;
719     new->default_direction = add->default_direction ? add->default_direction
720                                                     : base->default_direction;
721     return new;
722 }
723
724 /****************************************************************
725  *
726  * Looking things up in config entries...
727  */
728
729 /* Structure used to hold entries when we're actually building an index */
730
731 struct ent {
732     char *name;
733     char *icon;
734     char *alt;
735     char *desc;
736     apr_off_t size;
737     apr_time_t lm;
738     struct ent *next;
739     int ascending, ignore_case, version_sort;
740     char key;
741     int isdir;
742 };
743
744 static char *find_item(request_rec *r, apr_array_header_t *list, int path_only)
745 {
746     const char *content_type = ap_field_noparam(r->pool, r->content_type);
747     const char *content_encoding = r->content_encoding;
748     char *path = r->filename;
749
750     struct item *items = (struct item *) list->elts;
751     int i;
752
753     for (i = 0; i < list->nelts; ++i) {
754         struct item *p = &items[i];
755
756         /* Special cased for ^^DIRECTORY^^ and ^^BLANKICON^^ */
757         if ((path[0] == '^') || (!ap_strcmp_match(path, p->apply_path))) {
758             if (!*(p->apply_to)) {
759                 return p->data;
760             }
761             else if (p->type == BY_PATH || path[0] == '^') {
762                 if (!ap_strcmp_match(path, p->apply_to)) {
763                     return p->data;
764                 }
765             }
766             else if (!path_only) {
767                 if (!content_encoding) {
768                     if (p->type == BY_TYPE) {
769                         if (content_type
770                             && !ap_strcasecmp_match(content_type,
771                                                     p->apply_to)) {
772                             return p->data;
773                         }
774                     }
775                 }
776                 else {
777                     if (p->type == BY_ENCODING) {
778                         if (!ap_strcasecmp_match(content_encoding,
779                                                  p->apply_to)) {
780                             return p->data;
781                         }
782                     }
783                 }
784             }
785         }
786     }
787     return NULL;
788 }
789
790 #define find_icon(d,p,t) find_item(p,d->icon_list,t)
791 #define find_alt(d,p,t) find_item(p,d->alt_list,t)
792 #define find_header(d,p) find_item(p,d->hdr_list,0)
793 #define find_readme(d,p) find_item(p,d->rdme_list,0)
794
795 static char *find_default_item(char *bogus_name, apr_array_header_t *list)
796 {
797     request_rec r;
798     /* Bleah.  I tried to clean up find_item, and it lead to this bit
799      * of ugliness.   Note that the fields initialized are precisely
800      * those that find_item looks at...
801      */
802     r.filename = bogus_name;
803     r.content_type = r.content_encoding = NULL;
804     return find_item(&r, list, 1);
805 }
806
807 #define find_default_icon(d,n) find_default_item(n, d->icon_list)
808 #define find_default_alt(d,n) find_default_item(n, d->alt_list)
809
810 /*
811  * Look through the list of pattern/description pairs and return the first one
812  * if any) that matches the filename in the request.  If multiple patterns
813  * match, only the first one is used; since the order in the array is the
814  * same as the order in which directives were processed, earlier matching
815  * directives will dominate.
816  */
817
818 #ifdef CASE_BLIND_FILESYSTEM
819 #define MATCH_FLAGS APR_FNM_CASE_BLIND
820 #else
821 #define MATCH_FLAGS 0
822 #endif
823
824 static char *find_desc(autoindex_config_rec *dcfg, const char *filename_full)
825 {
826     int i;
827     ai_desc_t *list = (ai_desc_t *) dcfg->desc_list->elts;
828     const char *filename_only;
829     const char *filename;
830
831     /*
832      * If the filename includes a path, extract just the name itself
833      * for the simple matches.
834      */
835     if ((filename_only = ap_strrchr_c(filename_full, '/')) == NULL) {
836         filename_only = filename_full;
837     }
838     else {
839         filename_only++;
840     }
841     for (i = 0; i < dcfg->desc_list->nelts; ++i) {
842         ai_desc_t *tuple = &list[i];
843         int found;
844
845         /*
846          * Only use the full-path filename if the pattern contains '/'s.
847          */
848         filename = (tuple->full_path) ? filename_full : filename_only;
849         /*
850          * Make the comparison using the cheapest method; only do
851          * wildcard checking if we must.
852          */
853         if (tuple->wildcards) {
854             found = (apr_fnmatch(tuple->pattern, filename, MATCH_FLAGS) == 0);
855         }
856         else {
857             found = (ap_strstr_c(filename, tuple->pattern) != NULL);
858         }
859         if (found) {
860             return tuple->description;
861         }
862     }
863     return NULL;
864 }
865
866 static int ignore_entry(autoindex_config_rec *d, char *path)
867 {
868     apr_array_header_t *list = d->ign_list;
869     struct item *items = (struct item *) list->elts;
870     char *tt;
871     int i;
872
873     if ((tt = strrchr(path, '/')) == NULL) {
874         tt = path;
875     }
876     else {
877         tt++;
878     }
879
880     for (i = 0; i < list->nelts; ++i) {
881         struct item *p = &items[i];
882         char *ap;
883
884         if ((ap = strrchr(p->apply_to, '/')) == NULL) {
885             ap = p->apply_to;
886         }
887         else {
888             ap++;
889         }
890
891 #ifndef CASE_BLIND_FILESYSTEM
892         if (!ap_strcmp_match(path, p->apply_path)
893             && !ap_strcmp_match(tt, ap)) {
894             return 1;
895         }
896 #else  /* !CASE_BLIND_FILESYSTEM */
897         /*
898          * On some platforms, the match must be case-blind.  This is really
899          * a factor of the filesystem involved, but we can't detect that
900          * reliably - so we have to granularise at the OS level.
901          */
902         if (!ap_strcasecmp_match(path, p->apply_path)
903             && !ap_strcasecmp_match(tt, ap)) {
904             return 1;
905         }
906 #endif /* !CASE_BLIND_FILESYSTEM */
907     }
908     return 0;
909 }
910
911 /*****************************************************************
912  *
913  * Actually generating output
914  */
915
916 /*
917  * Elements of the emitted document:
918  *      Preamble
919  *              Emitted unless SUPPRESS_PREAMBLE is set AND ap_run_sub_req
920  *              succeeds for the (content_type == text/html) header file.
921  *      Header file
922  *              Emitted if found (and able).
923  *      H1 tag line
924  *              Emitted if a header file is NOT emitted.
925  *      Directory stuff
926  *              Always emitted.
927  *      HR
928  *              Emitted if FANCY_INDEXING is set.
929  *      Readme file
930  *              Emitted if found (and able).
931  *      ServerSig
932  *              Emitted if ServerSignature is not Off AND a readme file
933  *              is NOT emitted.
934  *      Postamble
935  *              Emitted unless SUPPRESS_PREAMBLE is set AND ap_run_sub_req
936  *              succeeds for the (content_type == text/html) readme file.
937  */
938
939
940 /*
941  * emit a plain text file
942  */
943 static void do_emit_plain(request_rec *r, apr_file_t *f)
944 {
945     char buf[AP_IOBUFSIZE + 1];
946     int ch;
947     apr_size_t i, c, n;
948     apr_status_t rv;
949
950     ap_rputs("<pre>\n", r);
951     while (!apr_file_eof(f)) {
952         do {
953             n = sizeof(char) * AP_IOBUFSIZE;
954             rv = apr_file_read(f, buf, &n);
955         } while (APR_STATUS_IS_EINTR(rv));
956         if (n == 0 || rv != APR_SUCCESS) {
957             /* ###: better error here? */
958             break;
959         }
960         buf[n] = '\0';
961         c = 0;
962         while (c < n) {
963             for (i = c; i < n; i++) {
964                 if (buf[i] == '<' || buf[i] == '>' || buf[i] == '&') {
965                     break;
966                 }
967             }
968             ch = buf[i];
969             buf[i] = '\0';
970             ap_rputs(&buf[c], r);
971             if (ch == '<') {
972                 ap_rputs("&lt;", r);
973             }
974             else if (ch == '>') {
975                 ap_rputs("&gt;", r);
976             }
977             else if (ch == '&') {
978                 ap_rputs("&amp;", r);
979             }
980             c = i + 1;
981         }
982     }
983     ap_rputs("</pre>\n", r);
984 }
985
986 /*
987  * Handle the preamble through the H1 tag line, inclusive.  Locate
988  * the file with a subrequests.  Process text/html documents by actually
989  * running the subrequest; text/xxx documents get copied verbatim,
990  * and any other content type is ignored.  This means that a non-text
991  * document (such as HEADER.gif) might get multiviewed as the result
992  * instead of a text document, meaning nothing will be displayed, but
993  * oh well.
994  */
995 static void emit_head(request_rec *r, char *header_fname, int suppress_amble,
996                       int emit_xhtml, char *title)
997 {
998     apr_table_t *hdrs = r->headers_in;
999     apr_file_t *f = NULL;
1000     request_rec *rr = NULL;
1001     int emit_amble = 1;
1002     int emit_H1 = 1;
1003     const char *r_accept;
1004     const char *r_accept_enc;
1005
1006     /*
1007      * If there's a header file, send a subrequest to look for it.  If it's
1008      * found and html do the subrequest, otherwise handle it
1009      */
1010     r_accept = apr_table_get(hdrs, "Accept");
1011     r_accept_enc = apr_table_get(hdrs, "Accept-Encoding");
1012     apr_table_setn(hdrs, "Accept", "text/html, text/plain");
1013     apr_table_unset(hdrs, "Accept-Encoding");
1014
1015
1016     if ((header_fname != NULL) && r->args) {
1017         header_fname = apr_pstrcat(r->pool, header_fname, "?", r->args, NULL);
1018     }
1019
1020     if ((header_fname != NULL)
1021         && (rr = ap_sub_req_lookup_uri(header_fname, r, r->output_filters))
1022         && (rr->status == HTTP_OK)
1023         && (rr->filename != NULL)
1024         && (rr->finfo.filetype == APR_REG)) {
1025         /*
1026          * Check for the two specific cases we allow: text/html and
1027          * text/anything-else.  The former is allowed to be processed for
1028          * SSIs.
1029          */
1030         if (rr->content_type != NULL) {
1031             if (!strcasecmp(ap_field_noparam(r->pool, rr->content_type),
1032                             "text/html")) {
1033                 ap_filter_t *f;
1034                /* Hope everything will work... */
1035                 emit_amble = 0;
1036                 emit_H1 = 0;
1037
1038                 if (! suppress_amble) {
1039                     emit_preamble(r, emit_xhtml, title);
1040                 }
1041                 /* This is a hack, but I can't find any better way to do this.
1042                  * The problem is that we have already created the sub-request,
1043                  * but we just inserted the OLD_WRITE filter, and the
1044                  * sub-request needs to pass its data through the OLD_WRITE
1045                  * filter, or things go horribly wrong (missing data, data in
1046                  * the wrong order, etc).  To fix it, if you create a
1047                  * sub-request and then insert the OLD_WRITE filter before you
1048                  * run the request, you need to make sure that the sub-request
1049                  * data goes through the OLD_WRITE filter.  Just steal this
1050                  * code.  The long-term solution is to remove the ap_r*
1051                  * functions.
1052                  */
1053                 for (f=rr->output_filters;
1054                      f->frec != ap_subreq_core_filter_handle; f = f->next);
1055                 f->next = r->output_filters;
1056
1057                 /*
1058                  * If there's a problem running the subrequest, display the
1059                  * preamble if we didn't do it before -- the header file
1060                  * didn't get displayed.
1061                  */
1062                 if (ap_run_sub_req(rr) != OK) {
1063                     /* It didn't work */
1064                     emit_amble = suppress_amble;
1065                     emit_H1 = 1;
1066                 }
1067             }
1068             else if (!strncasecmp("text/", rr->content_type, 5)) {
1069                 /*
1070                  * If we can open the file, prefix it with the preamble
1071                  * regardless; since we'll be sending a <pre> block around
1072                  * the file's contents, any HTML header it had won't end up
1073                  * where it belongs.
1074                  */
1075                 if (apr_file_open(&f, rr->filename, APR_READ,
1076                                   APR_OS_DEFAULT, r->pool) == APR_SUCCESS) {
1077                     emit_preamble(r, emit_xhtml, title);
1078                     emit_amble = 0;
1079                     do_emit_plain(r, f);
1080                     apr_file_close(f);
1081                     emit_H1 = 0;
1082                 }
1083             }
1084         }
1085     }
1086
1087     if (r_accept) {
1088         apr_table_setn(hdrs, "Accept", r_accept);
1089     }
1090     else {
1091         apr_table_unset(hdrs, "Accept");
1092     }
1093
1094     if (r_accept_enc) {
1095         apr_table_setn(hdrs, "Accept-Encoding", r_accept_enc);
1096     }
1097
1098     if (emit_amble) {
1099         emit_preamble(r, emit_xhtml, title);
1100     }
1101     if (emit_H1) {
1102         ap_rvputs(r, "<h1>Index of ", title, "</h1>\n", NULL);
1103     }
1104     if (rr != NULL) {
1105         ap_destroy_sub_req(rr);
1106     }
1107 }
1108
1109
1110 /*
1111  * Handle the Readme file through the postamble, inclusive.  Locate
1112  * the file with a subrequests.  Process text/html documents by actually
1113  * running the subrequest; text/xxx documents get copied verbatim,
1114  * and any other content type is ignored.  This means that a non-text
1115  * document (such as FOOTER.gif) might get multiviewed as the result
1116  * instead of a text document, meaning nothing will be displayed, but
1117  * oh well.
1118  */
1119 static void emit_tail(request_rec *r, char *readme_fname, int suppress_amble)
1120 {
1121     apr_file_t *f = NULL;
1122     request_rec *rr = NULL;
1123     int suppress_post = 0;
1124     int suppress_sig = 0;
1125
1126     /*
1127      * If there's a readme file, send a subrequest to look for it.  If it's
1128      * found and a text file, handle it -- otherwise fall through and
1129      * pretend there's nothing there.
1130      */
1131     if ((readme_fname != NULL)
1132         && (rr = ap_sub_req_lookup_uri(readme_fname, r, r->output_filters))
1133         && (rr->status == HTTP_OK)
1134         && (rr->filename != NULL)
1135         && rr->finfo.filetype == APR_REG) {
1136         /*
1137          * Check for the two specific cases we allow: text/html and
1138          * text/anything-else.  The former is allowed to be processed for
1139          * SSIs.
1140          */
1141         if (rr->content_type != NULL) {
1142             if (!strcasecmp(ap_field_noparam(r->pool, rr->content_type),
1143                             "text/html")) {
1144                 ap_filter_t *f;
1145                 for (f=rr->output_filters;
1146                      f->frec != ap_subreq_core_filter_handle; f = f->next);
1147                 f->next = r->output_filters;
1148
1149
1150                 if (ap_run_sub_req(rr) == OK) {
1151                     /* worked... */
1152                     suppress_sig = 1;
1153                     suppress_post = suppress_amble;
1154                 }
1155             }
1156             else if (!strncasecmp("text/", rr->content_type, 5)) {
1157                 /*
1158                  * If we can open the file, suppress the signature.
1159                  */
1160                 if (apr_file_open(&f, rr->filename, APR_READ,
1161                                   APR_OS_DEFAULT, r->pool) == APR_SUCCESS) {
1162                     do_emit_plain(r, f);
1163                     apr_file_close(f);
1164                     suppress_sig = 1;
1165                 }
1166             }
1167         }
1168     }
1169
1170     if (!suppress_sig) {
1171         ap_rputs(ap_psignature("", r), r);
1172     }
1173     if (!suppress_post) {
1174         ap_rputs("</body></html>\n", r);
1175     }
1176     if (rr != NULL) {
1177         ap_destroy_sub_req(rr);
1178     }
1179 }
1180
1181
1182 static char *find_title(request_rec *r)
1183 {
1184     char titlebuf[MAX_STRING_LEN], *find = "<title>";
1185     apr_file_t *thefile = NULL;
1186     int x, y, p;
1187     apr_size_t n;
1188
1189     if (r->status != HTTP_OK) {
1190         return NULL;
1191     }
1192     if ((r->content_type != NULL)
1193         && (!strcasecmp(ap_field_noparam(r->pool, r->content_type),
1194                         "text/html")
1195             || !strcmp(r->content_type, INCLUDES_MAGIC_TYPE))
1196         && !r->content_encoding) {
1197         if (apr_file_open(&thefile, r->filename, APR_READ,
1198                           APR_OS_DEFAULT, r->pool) != APR_SUCCESS) {
1199             return NULL;
1200         }
1201         n = sizeof(char) * (MAX_STRING_LEN - 1);
1202         apr_file_read(thefile, titlebuf, &n);
1203         if (n <= 0) {
1204             apr_file_close(thefile);
1205             return NULL;
1206         }
1207         titlebuf[n] = '\0';
1208         for (x = 0, p = 0; titlebuf[x]; x++) {
1209             if (apr_tolower(titlebuf[x]) == find[p]) {
1210                 if (!find[++p]) {
1211                     if ((p = ap_ind(&titlebuf[++x], '<')) != -1) {
1212                         titlebuf[x + p] = '\0';
1213                     }
1214                     /* Scan for line breaks for Tanmoy's secretary */
1215                     for (y = x; titlebuf[y]; y++) {
1216                         if ((titlebuf[y] == CR) || (titlebuf[y] == LF)) {
1217                             if (y == x) {
1218                                 x++;
1219                             }
1220                             else {
1221                                 titlebuf[y] = ' ';
1222                             }
1223                         }
1224                     }
1225                     apr_file_close(thefile);
1226                     return apr_pstrdup(r->pool, &titlebuf[x]);
1227                 }
1228             }
1229             else {
1230                 p = 0;
1231             }
1232         }
1233         apr_file_close(thefile);
1234     }
1235     return NULL;
1236 }
1237
1238 static struct ent *make_parent_entry(apr_int32_t autoindex_opts,
1239                                      autoindex_config_rec *d,
1240                                      request_rec *r, char keyid,
1241                                      char direction)
1242 {
1243     struct ent *p = (struct ent *) apr_pcalloc(r->pool, sizeof(struct ent));
1244     char *testpath;
1245     /*
1246      * p->name is now the true parent URI.
1247      * testpath is a crafted lie, so that the syntax '/some/..'
1248      * (or simply '..')be used to describe 'up' from '/some/'
1249      * when processeing IndexIgnore, and Icon|Alt|Desc configs.
1250      */
1251
1252     /* The output has always been to the parent.  Don't make ourself
1253      * our own parent (worthless cyclical reference).
1254      */
1255     if (!(p->name = ap_make_full_path(r->pool, r->uri, "../"))) {
1256         return (NULL);
1257     }
1258     ap_getparents(p->name);
1259     if (!*p->name) {
1260         return (NULL);
1261     }
1262
1263     /* IndexIgnore has always compared "/thispath/.." */
1264     testpath = ap_make_full_path(r->pool, r->filename, "..");
1265     if (ignore_entry(d, testpath)) {
1266         return (NULL);
1267     }
1268
1269     p->size = -1;
1270     p->lm = -1;
1271     p->key = apr_toupper(keyid);
1272     p->ascending = (apr_toupper(direction) == D_ASCENDING);
1273     p->version_sort = autoindex_opts & VERSION_SORT;
1274     if (autoindex_opts & FANCY_INDEXING) {
1275         if (!(p->icon = find_default_icon(d, testpath))) {
1276             p->icon = find_default_icon(d, "^^DIRECTORY^^");
1277         }
1278         if (!(p->alt = find_default_alt(d, testpath))) {
1279             if (!(p->alt = find_default_alt(d, "^^DIRECTORY^^"))) {
1280                 p->alt = "DIR";
1281             }
1282         }
1283         p->desc = find_desc(d, testpath);
1284     }
1285     return p;
1286 }
1287
1288 static struct ent *make_autoindex_entry(const apr_finfo_t *dirent,
1289                                         int autoindex_opts,
1290                                         autoindex_config_rec *d,
1291                                         request_rec *r, char keyid,
1292                                         char direction,
1293                                         const char *pattern)
1294 {
1295     request_rec *rr;
1296     struct ent *p;
1297     int show_forbidden = 0;
1298
1299     /* Dot is ignored, Parent is handled by make_parent_entry() */
1300     if ((dirent->name[0] == '.') && (!dirent->name[1]
1301         || ((dirent->name[1] == '.') && !dirent->name[2])))
1302         return (NULL);
1303
1304     /*
1305      * On some platforms, the match must be case-blind.  This is really
1306      * a factor of the filesystem involved, but we can't detect that
1307      * reliably - so we have to granularise at the OS level.
1308      */
1309     if (pattern && (apr_fnmatch(pattern, dirent->name,
1310                                 APR_FNM_NOESCAPE | APR_FNM_PERIOD
1311 #ifdef CASE_BLIND_FILESYSTEM
1312                                 | APR_FNM_CASE_BLIND
1313 #endif
1314                                 )
1315                     != APR_SUCCESS)) {
1316         return (NULL);
1317     }
1318
1319     if (ignore_entry(d, ap_make_full_path(r->pool,
1320                                           r->filename, dirent->name))) {
1321         return (NULL);
1322     }
1323
1324     if (!(rr = ap_sub_req_lookup_dirent(dirent, r, AP_SUBREQ_NO_ARGS, NULL))) {
1325         return (NULL);
1326     }
1327
1328     if((autoindex_opts & SHOW_FORBIDDEN)
1329         && (rr->status == HTTP_UNAUTHORIZED || rr->status == HTTP_FORBIDDEN)) {
1330         show_forbidden = 1;
1331     }
1332
1333     if ((rr->finfo.filetype != APR_DIR && rr->finfo.filetype != APR_REG)
1334         || !(rr->status == OK || ap_is_HTTP_SUCCESS(rr->status)
1335                               || ap_is_HTTP_REDIRECT(rr->status)
1336                               || show_forbidden == 1)) {
1337         ap_destroy_sub_req(rr);
1338         return (NULL);
1339     }
1340
1341     p = (struct ent *) apr_pcalloc(r->pool, sizeof(struct ent));
1342     if (dirent->filetype == APR_DIR) {
1343         p->name = apr_pstrcat(r->pool, dirent->name, "/", NULL);
1344     }
1345     else {
1346         p->name = apr_pstrdup(r->pool, dirent->name);
1347     }
1348     p->size = -1;
1349     p->icon = NULL;
1350     p->alt = NULL;
1351     p->desc = NULL;
1352     p->lm = -1;
1353     p->isdir = 0;
1354     p->key = apr_toupper(keyid);
1355     p->ascending = (apr_toupper(direction) == D_ASCENDING);
1356     p->version_sort = !!(autoindex_opts & VERSION_SORT);
1357     p->ignore_case = !!(autoindex_opts & IGNORE_CASE);
1358
1359     if (autoindex_opts & (FANCY_INDEXING | TABLE_INDEXING)) {
1360         p->lm = rr->finfo.mtime;
1361         if (dirent->filetype == APR_DIR) {
1362             if (autoindex_opts & FOLDERS_FIRST) {
1363                 p->isdir = 1;
1364             }
1365             rr->filename = ap_make_dirstr_parent (rr->pool, rr->filename);
1366
1367             /* omit the trailing slash (1.3 compat) */
1368             rr->filename[strlen(rr->filename) - 1] = '\0';
1369
1370             if (!(p->icon = find_icon(d, rr, 1))) {
1371                 p->icon = find_default_icon(d, "^^DIRECTORY^^");
1372             }
1373             if (!(p->alt = find_alt(d, rr, 1))) {
1374                 if (!(p->alt = find_default_alt(d, "^^DIRECTORY^^"))) {
1375                     p->alt = "DIR";
1376                 }
1377             }
1378         }
1379         else {
1380             p->icon = find_icon(d, rr, 0);
1381             p->alt = find_alt(d, rr, 0);
1382             p->size = rr->finfo.size;
1383         }
1384
1385         p->desc = find_desc(d, rr->filename);
1386
1387         if ((!p->desc) && (autoindex_opts & SCAN_HTML_TITLES)) {
1388             p->desc = apr_pstrdup(r->pool, find_title(rr));
1389         }
1390     }
1391     ap_destroy_sub_req(rr);
1392     /*
1393      * We don't need to take any special action for the file size key.
1394      * If we did, it would go here.
1395      */
1396     if (keyid == K_LAST_MOD) {
1397         if (p->lm < 0) {
1398             p->lm = 0;
1399         }
1400     }
1401     return (p);
1402 }
1403
1404 static char *terminate_description(autoindex_config_rec *d, char *desc,
1405                                    apr_int32_t autoindex_opts, int desc_width)
1406 {
1407     int maxsize = desc_width;
1408     register int x;
1409
1410     /*
1411      * If there's no DescriptionWidth in effect, default to the old
1412      * behaviour of adjusting the description size depending upon
1413      * what else is being displayed.  Otherwise, stick with the
1414      * setting.
1415      */
1416     if (d->desc_adjust == K_UNSET) {
1417         if (autoindex_opts & SUPPRESS_ICON) {
1418             maxsize += 6;
1419         }
1420         if (autoindex_opts & SUPPRESS_LAST_MOD) {
1421             maxsize += 19;
1422         }
1423         if (autoindex_opts & SUPPRESS_SIZE) {
1424             maxsize += 7;
1425         }
1426     }
1427     for (x = 0; desc[x] && ((maxsize > 0) || (desc[x] == '<')); x++) {
1428         if (desc[x] == '<') {
1429             while (desc[x] != '>') {
1430                 if (!desc[x]) {
1431                     maxsize = 0;
1432                     break;
1433                 }
1434                 ++x;
1435             }
1436         }
1437         else if (desc[x] == '&') {
1438             /* entities like &auml; count as one character */
1439             --maxsize;
1440             for ( ; desc[x] != ';'; ++x) {
1441                 if (desc[x] == '\0') {
1442                      maxsize = 0;
1443                      break;
1444                 }
1445             }
1446         }
1447         else {
1448             --maxsize;
1449         }
1450     }
1451     if (!maxsize && desc[x] != '\0') {
1452         desc[x - 1] = '>';      /* Grump. */
1453         desc[x] = '\0';         /* Double Grump! */
1454     }
1455     return desc;
1456 }
1457
1458 /*
1459  * Emit the anchor for the specified field.  If a field is the key for the
1460  * current request, the link changes its meaning to reverse the order when
1461  * selected again.  Non-active fields always start in ascending order.
1462  */
1463 static void emit_link(request_rec *r, const char *anchor, char column,
1464                       char curkey, char curdirection,
1465                       const char *colargs, int nosort)
1466 {
1467     if (!nosort) {
1468         char qvalue[9];
1469
1470         qvalue[0] = '?';
1471         qvalue[1] = 'C';
1472         qvalue[2] = '=';
1473         qvalue[3] = column;
1474         qvalue[4] = ';';
1475         qvalue[5] = 'O';
1476         qvalue[6] = '=';
1477                     /* reverse? */
1478         qvalue[7] = ((curkey == column) && (curdirection == D_ASCENDING))
1479                       ? D_DESCENDING : D_ASCENDING;
1480         qvalue[8] = '\0';
1481         ap_rvputs(r, "<a href=\"", qvalue, colargs ? colargs : "",
1482                      "\">", anchor, "</a>", NULL);
1483     }
1484     else {
1485         ap_rputs(anchor, r);
1486     }
1487 }
1488
1489 static void output_directories(struct ent **ar, int n,
1490                                autoindex_config_rec *d, request_rec *r,
1491                                apr_int32_t autoindex_opts, char keyid,
1492                                char direction, const char *colargs)
1493 {
1494     int x;
1495     apr_size_t rv;
1496     char *name = r->uri;
1497     char *tp;
1498     int static_columns = !!(autoindex_opts & SUPPRESS_COLSORT);
1499     apr_pool_t *scratch;
1500     int name_width;
1501     int desc_width;
1502     char *name_scratch;
1503     char *pad_scratch;
1504     char *breakrow = "";
1505
1506     apr_pool_create(&scratch, r->pool);
1507     if (name[0] == '\0') {
1508         name = "/";
1509     }
1510
1511     name_width = d->name_width;
1512     desc_width = d->desc_width;
1513
1514     if ((autoindex_opts & (FANCY_INDEXING | TABLE_INDEXING))
1515                         == FANCY_INDEXING) {
1516         if (d->name_adjust == K_ADJUST) {
1517             for (x = 0; x < n; x++) {
1518                 int t = strlen(ar[x]->name);
1519                 if (t > name_width) {
1520                     name_width = t;
1521                 }
1522             }
1523         }
1524
1525         if (d->desc_adjust == K_ADJUST) {
1526             for (x = 0; x < n; x++) {
1527                 if (ar[x]->desc != NULL) {
1528                     int t = strlen(ar[x]->desc);
1529                     if (t > desc_width) {
1530                         desc_width = t;
1531                     }
1532                 }
1533             }
1534         }
1535     }
1536     name_scratch = apr_palloc(r->pool, name_width + 1);
1537     pad_scratch = apr_palloc(r->pool, name_width + 1);
1538     memset(pad_scratch, ' ', name_width);
1539     pad_scratch[name_width] = '\0';
1540
1541     if (autoindex_opts & TABLE_INDEXING) {
1542         int cols = 1;
1543         ap_rputs("<table><tr>", r);
1544         if (!(autoindex_opts & SUPPRESS_ICON)) {
1545             ap_rputs("<th>", r);
1546             if ((tp = find_default_icon(d, "^^BLANKICON^^"))) {
1547                 ap_rvputs(r, "<img src=\"", ap_escape_html(scratch, tp),
1548                              "\" alt=\"[ICO]\"", NULL);
1549                 if (d->icon_width) {
1550                     ap_rprintf(r, " width=\"%d\"", d->icon_width);
1551                 }
1552                 if (d->icon_height) {
1553                     ap_rprintf(r, " height=\"%d\"", d->icon_height);
1554                 }
1555
1556                 if (autoindex_opts & EMIT_XHTML) {
1557                     ap_rputs(" /", r);
1558                 }
1559                 ap_rputs("></th>", r);
1560             }
1561             else {
1562                 ap_rputs("&nbsp;</th>", r);
1563             }
1564
1565             ++cols;
1566         }
1567         ap_rputs("<th>", r);
1568         emit_link(r, "Name", K_NAME, keyid, direction,
1569                   colargs, static_columns);
1570         if (!(autoindex_opts & SUPPRESS_LAST_MOD)) {
1571             ap_rputs("</th><th>", r);
1572             emit_link(r, "Last modified", K_LAST_MOD, keyid, direction,
1573                       colargs, static_columns);
1574             ++cols;
1575         }
1576         if (!(autoindex_opts & SUPPRESS_SIZE)) {
1577             ap_rputs("</th><th>", r);
1578             emit_link(r, "Size", K_SIZE, keyid, direction,
1579                       colargs, static_columns);
1580             ++cols;
1581         }
1582         if (!(autoindex_opts & SUPPRESS_DESC)) {
1583             ap_rputs("</th><th>", r);
1584             emit_link(r, "Description", K_DESC, keyid, direction,
1585                       colargs, static_columns);
1586             ++cols;
1587         }
1588         if (!(autoindex_opts & SUPPRESS_RULES)) {
1589             breakrow = apr_psprintf(r->pool,
1590                                     "<tr><th colspan=\"%d\">"
1591                                     "<hr%s></th></tr>\n", cols,
1592                                     (autoindex_opts & EMIT_XHTML) ? " /" : "");
1593         }
1594         ap_rvputs(r, "</th></tr>", breakrow, NULL);
1595     }
1596     else if (autoindex_opts & FANCY_INDEXING) {
1597         ap_rputs("<pre>", r);
1598         if (!(autoindex_opts & SUPPRESS_ICON)) {
1599             if ((tp = find_default_icon(d, "^^BLANKICON^^"))) {
1600                 ap_rvputs(r, "<img src=\"", ap_escape_html(scratch, tp),
1601                              "\" alt=\"Icon \"", NULL);
1602                 if (d->icon_width) {
1603                     ap_rprintf(r, " width=\"%d\"", d->icon_width);
1604                 }
1605                 if (d->icon_height) {
1606                     ap_rprintf(r, " height=\"%d\"", d->icon_height);
1607                 }
1608
1609                 if (autoindex_opts & EMIT_XHTML) {
1610                     ap_rputs(" /", r);
1611                 }
1612                 ap_rputs("> ", r);
1613             }
1614             else {
1615                 ap_rputs("      ", r);
1616             }
1617         }
1618         emit_link(r, "Name", K_NAME, keyid, direction,
1619                   colargs, static_columns);
1620         ap_rputs(pad_scratch + 4, r);
1621         /*
1622          * Emit the guaranteed-at-least-one-space-between-columns byte.
1623          */
1624         ap_rputs(" ", r);
1625         if (!(autoindex_opts & SUPPRESS_LAST_MOD)) {
1626             emit_link(r, "Last modified", K_LAST_MOD, keyid, direction,
1627                       colargs, static_columns);
1628             ap_rputs("      ", r);
1629         }
1630         if (!(autoindex_opts & SUPPRESS_SIZE)) {
1631             emit_link(r, "Size", K_SIZE, keyid, direction,
1632                       colargs, static_columns);
1633             ap_rputs("  ", r);
1634         }
1635         if (!(autoindex_opts & SUPPRESS_DESC)) {
1636             emit_link(r, "Description", K_DESC, keyid, direction,
1637                       colargs, static_columns);
1638         }
1639         if (!(autoindex_opts & SUPPRESS_RULES)) {
1640             ap_rputs("<hr", r);
1641             if (autoindex_opts & EMIT_XHTML) {
1642                 ap_rputs(" /", r);
1643             }
1644             ap_rputs(">", r);
1645         }
1646         else {
1647             ap_rputc('\n', r);
1648         }
1649     }
1650     else {
1651         ap_rputs("<ul>", r);
1652     }
1653
1654     for (x = 0; x < n; x++) {
1655         char *anchor, *t, *t2;
1656         int nwidth;
1657
1658         apr_pool_clear(scratch);
1659
1660         t = ar[x]->name;
1661         anchor = ap_escape_html(scratch, ap_os_escape_path(scratch, t, 0));
1662
1663         if (!x && t[0] == '/') {
1664             t2 = "Parent Directory";
1665         }
1666         else {
1667             t2 = t;
1668         }
1669
1670         if (autoindex_opts & TABLE_INDEXING) {
1671             ap_rputs("<tr>", r);
1672             if (!(autoindex_opts & SUPPRESS_ICON)) {
1673                 ap_rputs("<td valign=\"top\">", r);
1674                 if (autoindex_opts & ICONS_ARE_LINKS) {
1675                     ap_rvputs(r, "<a href=\"", anchor, "\">", NULL);
1676                 }
1677                 if ((ar[x]->icon) || d->default_icon) {
1678                     ap_rvputs(r, "<img src=\"",
1679                               ap_escape_html(scratch,
1680                                              ar[x]->icon ? ar[x]->icon
1681                                                          : d->default_icon),
1682                               "\" alt=\"[", (ar[x]->alt ? ar[x]->alt : "   "),
1683                               "]\"", NULL);
1684                     if (d->icon_width) {
1685                         ap_rprintf(r, " width=\"%d\"", d->icon_width);
1686                     }
1687                     if (d->icon_height) {
1688                         ap_rprintf(r, " height=\"%d\"", d->icon_height);
1689                     }
1690
1691                     if (autoindex_opts & EMIT_XHTML) {
1692                         ap_rputs(" /", r);
1693                     }
1694                     ap_rputs(">", r);
1695                 }
1696                 else {
1697                     ap_rputs("&nbsp;", r);
1698                 }
1699                 if (autoindex_opts & ICONS_ARE_LINKS) {
1700                     ap_rputs("</a></td>", r);
1701                 }
1702                 else {
1703                     ap_rputs("</td>", r);
1704                 }
1705             }
1706             if (d->name_adjust == K_ADJUST) {
1707                 ap_rvputs(r, "<td><a href=\"", anchor, "\">",
1708                           ap_escape_html(scratch, t2), "</a>", NULL);
1709             }
1710             else {
1711                 nwidth = strlen(t2);
1712                 if (nwidth > name_width) {
1713                   memcpy(name_scratch, t2, name_width - 3);
1714                   name_scratch[name_width - 3] = '.';
1715                   name_scratch[name_width - 2] = '.';
1716                   name_scratch[name_width - 1] = '>';
1717                   name_scratch[name_width] = 0;
1718                   t2 = name_scratch;
1719                   nwidth = name_width;
1720                 }
1721                 ap_rvputs(r, "<td><a href=\"", anchor, "\">",
1722                           ap_escape_html(scratch, t2),
1723                           "</a>", pad_scratch + nwidth, NULL);
1724             }
1725             if (!(autoindex_opts & SUPPRESS_LAST_MOD)) {
1726                 if (ar[x]->lm != -1) {
1727                     char time_str[MAX_STRING_LEN];
1728                     apr_time_exp_t ts;
1729                     apr_time_exp_lt(&ts, ar[x]->lm);
1730                     apr_strftime(time_str, &rv, MAX_STRING_LEN,
1731                                  "</td><td align=\"right\">%d-%b-%Y %H:%M  ",
1732                                  &ts);
1733                     ap_rputs(time_str, r);
1734                 }
1735                 else {
1736                     ap_rputs("</td><td>&nbsp;", r);
1737                 }
1738             }
1739             if (!(autoindex_opts & SUPPRESS_SIZE)) {
1740                 char buf[5];
1741                 ap_rvputs(r, "</td><td align=\"right\">",
1742                           apr_strfsize(ar[x]->size, buf), NULL);
1743             }
1744             if (!(autoindex_opts & SUPPRESS_DESC)) {
1745                 if (ar[x]->desc) {
1746                     if (d->desc_adjust == K_ADJUST) {
1747                         ap_rvputs(r, "</td><td>", ar[x]->desc, NULL);
1748                     }
1749                     else {
1750                         ap_rvputs(r, "</td><td>",
1751                                   terminate_description(d, ar[x]->desc,
1752                                                         autoindex_opts,
1753                                                         desc_width), NULL);
1754                     }
1755                 }
1756             }
1757             else {
1758                 ap_rputs("</td><td>&nbsp;", r);
1759             }
1760             ap_rputs("</td></tr>\n", r);
1761         }
1762         else if (autoindex_opts & FANCY_INDEXING) {
1763             if (!(autoindex_opts & SUPPRESS_ICON)) {
1764                 if (autoindex_opts & ICONS_ARE_LINKS) {
1765                     ap_rvputs(r, "<a href=\"", anchor, "\">", NULL);
1766                 }
1767                 if ((ar[x]->icon) || d->default_icon) {
1768                     ap_rvputs(r, "<img src=\"",
1769                               ap_escape_html(scratch,
1770                                              ar[x]->icon ? ar[x]->icon
1771                                                          : d->default_icon),
1772                               "\" alt=\"[", (ar[x]->alt ? ar[x]->alt : "   "),
1773                               "]\"", NULL);
1774                     if (d->icon_width) {
1775                         ap_rprintf(r, " width=\"%d\"", d->icon_width);
1776                     }
1777                     if (d->icon_height) {
1778                         ap_rprintf(r, " height=\"%d\"", d->icon_height);
1779                     }
1780
1781                     if (autoindex_opts & EMIT_XHTML) {
1782                         ap_rputs(" /", r);
1783                     }
1784                     ap_rputs(">", r);
1785                 }
1786                 else {
1787                     ap_rputs("     ", r);
1788                 }
1789                 if (autoindex_opts & ICONS_ARE_LINKS) {
1790                     ap_rputs("</a> ", r);
1791                 }
1792                 else {
1793                     ap_rputc(' ', r);
1794                 }
1795             }
1796             nwidth = strlen(t2);
1797             if (nwidth > name_width) {
1798                 memcpy(name_scratch, t2, name_width - 3);
1799                 name_scratch[name_width - 3] = '.';
1800                 name_scratch[name_width - 2] = '.';
1801                 name_scratch[name_width - 1] = '>';
1802                 name_scratch[name_width] = 0;
1803                 t2 = name_scratch;
1804                 nwidth = name_width;
1805             }
1806             ap_rvputs(r, "<a href=\"", anchor, "\">",
1807                       ap_escape_html(scratch, t2),
1808                       "</a>", pad_scratch + nwidth, NULL);
1809             /*
1810              * The blank before the storm.. er, before the next field.
1811              */
1812             ap_rputs(" ", r);
1813             if (!(autoindex_opts & SUPPRESS_LAST_MOD)) {
1814                 if (ar[x]->lm != -1) {
1815                     char time_str[MAX_STRING_LEN];
1816                     apr_time_exp_t ts;
1817                     apr_time_exp_lt(&ts, ar[x]->lm);
1818                     apr_strftime(time_str, &rv, MAX_STRING_LEN,
1819                                 "%d-%b-%Y %H:%M  ", &ts);
1820                     ap_rputs(time_str, r);
1821                 }
1822                 else {
1823                     /*Length="22-Feb-1998 23:42  " (see 4 lines above) */
1824                     ap_rputs("                   ", r);
1825                 }
1826             }
1827             if (!(autoindex_opts & SUPPRESS_SIZE)) {
1828                 char buf[5];
1829                 ap_rputs(apr_strfsize(ar[x]->size, buf), r);
1830                 ap_rputs("  ", r);
1831             }
1832             if (!(autoindex_opts & SUPPRESS_DESC)) {
1833                 if (ar[x]->desc) {
1834                     ap_rputs(terminate_description(d, ar[x]->desc,
1835                                                    autoindex_opts,
1836                                                    desc_width), r);
1837                 }
1838             }
1839             ap_rputc('\n', r);
1840         }
1841         else {
1842             ap_rvputs(r, "<li><a href=\"", anchor, "\"> ",
1843                       ap_escape_html(scratch, t2),
1844                       "</a></li>\n", NULL);
1845         }
1846     }
1847     if (autoindex_opts & TABLE_INDEXING) {
1848         ap_rvputs(r, breakrow, "</table>\n", NULL);
1849     }
1850     else if (autoindex_opts & FANCY_INDEXING) {
1851         if (!(autoindex_opts & SUPPRESS_RULES)) {
1852             ap_rputs("<hr", r);
1853             if (autoindex_opts & EMIT_XHTML) {
1854                 ap_rputs(" /", r);
1855             }
1856             ap_rputs("></pre>\n", r);
1857         }
1858         else {
1859             ap_rputs("</pre>\n", r);
1860         }
1861     }
1862     else {
1863         ap_rputs("</ul>\n", r);
1864     }
1865 }
1866
1867 /*
1868  * Compare two file entries according to the sort criteria.  The return
1869  * is essentially a signum function value.
1870  */
1871
1872 static int dsortf(struct ent **e1, struct ent **e2)
1873 {
1874     struct ent *c1;
1875     struct ent *c2;
1876     int result = 0;
1877
1878     /*
1879      * First, see if either of the entries is for the parent directory.
1880      * If so, that *always* sorts lower than anything else.
1881      */
1882     if ((*e1)->name[0] == '/') {
1883         return -1;
1884     }
1885     if ((*e2)->name[0] == '/') {
1886         return 1;
1887     }
1888     /*
1889      * Now see if one's a directory and one isn't, if we're set
1890      * isdir for FOLDERS_FIRST.
1891      */
1892     if ((*e1)->isdir != (*e2)->isdir) {
1893         return (*e1)->isdir ? -1 : 1;
1894     }
1895     /*
1896      * All of our comparisons will be of the c1 entry against the c2 one,
1897      * so assign them appropriately to take care of the ordering.
1898      */
1899     if ((*e1)->ascending) {
1900         c1 = *e1;
1901         c2 = *e2;
1902     }
1903     else {
1904         c1 = *e2;
1905         c2 = *e1;
1906     }
1907
1908     switch (c1->key) {
1909     case K_LAST_MOD:
1910         if (c1->lm > c2->lm) {
1911             return 1;
1912         }
1913         else if (c1->lm < c2->lm) {
1914             return -1;
1915         }
1916         break;
1917     case K_SIZE:
1918         if (c1->size > c2->size) {
1919             return 1;
1920         }
1921         else if (c1->size < c2->size) {
1922             return -1;
1923         }
1924         break;
1925     case K_DESC:
1926         if (c1->version_sort) {
1927             result = apr_strnatcmp(c1->desc ? c1->desc : "",
1928                                    c2->desc ? c2->desc : "");
1929         }
1930         else {
1931             result = strcmp(c1->desc ? c1->desc : "",
1932                             c2->desc ? c2->desc : "");
1933         }
1934         if (result) {
1935             return result;
1936         }
1937         break;
1938     }
1939
1940     /* names may identical when treated case-insensitively,
1941      * so always fall back on strcmp() flavors to put entries
1942      * in deterministic order.  This means that 'ABC' and 'abc'
1943      * will always appear in the same order, rather than
1944      * variably between 'ABC abc' and 'abc ABC' order.
1945      */
1946
1947     if (c1->version_sort) {
1948         if (c1->ignore_case) {
1949             result = apr_strnatcasecmp (c1->name, c2->name);
1950         }
1951         if (!result) {
1952             result = apr_strnatcmp(c1->name, c2->name);
1953         }
1954     }
1955
1956     /* The names may be identical in respects other other than
1957      * filename case when strnatcmp is used above, so fall back
1958      * to strcmp on conflicts so that fn1.01.zzz and fn1.1.zzz
1959      * are also sorted in a deterministic order.
1960      */
1961
1962     if (!result && c1->ignore_case) {
1963         result = strcasecmp (c1->name, c2->name);
1964     }
1965
1966     if (!result) {
1967         result = strcmp (c1->name, c2->name);
1968     }
1969
1970     return result;
1971 }
1972
1973
1974 static int index_directory(request_rec *r,
1975                            autoindex_config_rec *autoindex_conf)
1976 {
1977     char *title_name = ap_escape_html(r->pool, r->uri);
1978     char *title_endp;
1979     char *name = r->filename;
1980     char *pstring = NULL;
1981     apr_finfo_t dirent;
1982     apr_dir_t *thedir;
1983     apr_status_t status;
1984     int num_ent = 0, x;
1985     struct ent *head, *p;
1986     struct ent **ar = NULL;
1987     const char *qstring;
1988     apr_int32_t autoindex_opts = autoindex_conf->opts;
1989     char keyid;
1990     char direction;
1991     char *colargs;
1992     char *fullpath;
1993     apr_size_t dirpathlen;
1994     char *ctype = "text/html";
1995     char *charset;
1996
1997     if ((status = apr_dir_open(&thedir, name, r->pool)) != APR_SUCCESS) {
1998         ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
1999                       "Can't open directory for index: %s", r->filename);
2000         return HTTP_FORBIDDEN;
2001     }
2002
2003     if (autoindex_conf->ctype) {
2004         ctype = autoindex_conf->ctype;
2005     }
2006     if (autoindex_conf->charset) {
2007         charset = autoindex_conf->charset;
2008     }
2009     else {
2010 #if APR_HAS_UNICODE_FS
2011         charset = "UTF-8";
2012 #else
2013         charset = "ISO-8859-1";
2014 #endif
2015     }
2016     if (*charset) {
2017         ap_set_content_type(r, apr_pstrcat(r->pool, ctype, ";charset=",
2018                             charset, NULL));
2019     }
2020     else {
2021         ap_set_content_type(r, ctype);
2022     }
2023
2024     if (autoindex_opts & TRACK_MODIFIED) {
2025         ap_update_mtime(r, r->finfo.mtime);
2026         ap_set_last_modified(r);
2027         ap_set_etag(r);
2028     }
2029     if (r->header_only) {
2030         apr_dir_close(thedir);
2031         return 0;
2032     }
2033
2034     /*
2035      * If there is no specific ordering defined for this directory,
2036      * default to ascending by filename.
2037      */
2038     keyid = autoindex_conf->default_keyid
2039                 ? autoindex_conf->default_keyid : K_NAME;
2040     direction = autoindex_conf->default_direction
2041                 ? autoindex_conf->default_direction : D_ASCENDING;
2042
2043     /*
2044      * Figure out what sort of indexing (if any) we're supposed to use.
2045      *
2046      * If no QUERY_STRING was specified or client query strings have been
2047      * explicitly disabled.
2048      * If we are ignoring the client, suppress column sorting as well.
2049      */
2050     if (autoindex_opts & IGNORE_CLIENT) {
2051         qstring = NULL;
2052         autoindex_opts |= SUPPRESS_COLSORT;
2053         colargs = "";
2054     }
2055     else {
2056         char fval[5], vval[5], *ppre = "", *epattern = "";
2057         fval[0] = '\0'; vval[0] = '\0';
2058         qstring = r->args;
2059
2060         while (qstring && *qstring) {
2061
2062             /* C= First Sort key Column (N, M, S, D) */
2063             if (   qstring[0] == 'C' && qstring[1] == '='
2064                 && qstring[2] && strchr(K_VALID, qstring[2])
2065                 && (   qstring[3] == '&' || qstring[3] == ';'
2066                     || !qstring[3])) {
2067                 keyid = qstring[2];
2068                 qstring += qstring[3] ? 4 : 3;
2069             }
2070
2071             /* O= Sort order (A, D) */
2072             else if (   qstring[0] == 'O' && qstring[1] == '='
2073                      && (   (qstring[2] == D_ASCENDING)
2074                          || (qstring[2] == D_DESCENDING))
2075                      && (   qstring[3] == '&' || qstring[3] == ';'
2076                          || !qstring[3])) {
2077                 direction = qstring[2];
2078                 qstring += qstring[3] ? 4 : 3;
2079             }
2080
2081             /* F= Output Format (0 plain, 1 fancy (pre), 2 table) */
2082             else if (   qstring[0] == 'F' && qstring[1] == '='
2083                      && qstring[2] && strchr("012", qstring[2])
2084                      && (   qstring[3] == '&' || qstring[3] == ';'
2085                          || !qstring[3])) {
2086                 if (qstring[2] == '0') {
2087                     autoindex_opts &= ~(FANCY_INDEXING | TABLE_INDEXING);
2088                 }
2089                 else if (qstring[2] == '1') {
2090                     autoindex_opts = (autoindex_opts | FANCY_INDEXING)
2091                         & ~TABLE_INDEXING;
2092                 }
2093                 else if (qstring[2] == '2') {
2094                     autoindex_opts |= FANCY_INDEXING | TABLE_INDEXING;
2095                 }
2096                 strcpy(fval, ";F= ");
2097                 fval[3] = qstring[2];
2098                 qstring += qstring[3] ? 4 : 3;
2099             }
2100
2101             /* V= Version sort (0, 1) */
2102             else if (   qstring[0] == 'V' && qstring[1] == '='
2103                      && (qstring[2] == '0' || qstring[2] == '1')
2104                      && (   qstring[3] == '&' || qstring[3] == ';'
2105                          || !qstring[3])) {
2106                 if (qstring[2] == '0') {
2107                     autoindex_opts &= ~VERSION_SORT;
2108                 }
2109                 else if (qstring[2] == '1') {
2110                     autoindex_opts |= VERSION_SORT;
2111                 }
2112                 strcpy(vval, ";V= ");
2113                 vval[3] = qstring[2];
2114                 qstring += qstring[3] ? 4 : 3;
2115             }
2116
2117             /* P= wildcard pattern (*.foo) */
2118             else if (qstring[0] == 'P' && qstring[1] == '=') {
2119                 const char *eos = qstring += 2; /* for efficiency */
2120
2121                 while (*eos && *eos != '&' && *eos != ';') {
2122                     ++eos;
2123                 }
2124
2125                 if (eos == qstring) {
2126                     pstring = NULL;
2127                 }
2128                 else {
2129                     pstring = apr_pstrndup(r->pool, qstring, eos - qstring);
2130                     if (ap_unescape_url(pstring) != OK) {
2131                         /* ignore the pattern, if it's bad. */
2132                         pstring = NULL;
2133                     }
2134                     else {
2135                         ppre = ";P=";
2136                         /* be correct */
2137                         epattern = ap_escape_uri(r->pool, pstring);
2138                     }
2139                 }
2140
2141                 if (*eos && *++eos) {
2142                     qstring = eos;
2143                 }
2144                 else {
2145                     qstring = NULL;
2146                 }
2147             }
2148
2149             /* Syntax error?  Ignore the remainder! */
2150             else {
2151                 qstring = NULL;
2152             }
2153         }
2154         colargs = apr_pstrcat(r->pool, fval, vval, ppre, epattern, NULL);
2155     }
2156
2157     /* Spew HTML preamble */
2158     title_endp = title_name + strlen(title_name) - 1;
2159
2160     while (title_endp > title_name && *title_endp == '/') {
2161         *title_endp-- = '\0';
2162     }
2163
2164     emit_head(r, find_header(autoindex_conf, r),
2165               autoindex_opts & SUPPRESS_PREAMBLE,
2166               autoindex_opts & EMIT_XHTML, title_name);
2167
2168     /*
2169      * Since we don't know how many dir. entries there are, put them into a
2170      * linked list and then arrayificate them so qsort can use them.
2171      */
2172     head = NULL;
2173     p = make_parent_entry(autoindex_opts, autoindex_conf, r, keyid, direction);
2174     if (p != NULL) {
2175         p->next = head;
2176         head = p;
2177         num_ent++;
2178     }
2179     fullpath = apr_palloc(r->pool, APR_PATH_MAX);
2180     dirpathlen = strlen(name);
2181     memcpy(fullpath, name, dirpathlen);
2182
2183     do {
2184         status = apr_dir_read(&dirent, APR_FINFO_MIN | APR_FINFO_NAME, thedir);
2185         if (APR_STATUS_IS_INCOMPLETE(status)) {
2186             continue; /* ignore un-stat()able files */
2187         }
2188         else if (status != APR_SUCCESS) {
2189             break;
2190         }
2191
2192         /* We want to explode symlinks here. */
2193         if (dirent.filetype == APR_LNK) {
2194             const char *savename;
2195             apr_finfo_t fi;
2196             /* We *must* have FNAME. */
2197             savename = dirent.name;
2198             apr_cpystrn(fullpath + dirpathlen, dirent.name,
2199                         APR_PATH_MAX - dirpathlen);
2200             status = apr_stat(&fi, fullpath,
2201                               dirent.valid & ~(APR_FINFO_NAME), r->pool);
2202             if (status != APR_SUCCESS) {
2203                 /* Something bad happened, skip this file. */
2204                 continue;
2205             }
2206             memcpy(&dirent, &fi, sizeof(fi));
2207             dirent.name = savename;
2208             dirent.valid |= APR_FINFO_NAME;
2209         }
2210         p = make_autoindex_entry(&dirent, autoindex_opts, autoindex_conf, r,
2211                                  keyid, direction, pstring);
2212         if (p != NULL) {
2213             p->next = head;
2214             head = p;
2215             num_ent++;
2216         }
2217     } while (1);
2218
2219     if (num_ent > 0) {
2220         ar = (struct ent **) apr_palloc(r->pool,
2221                                         num_ent * sizeof(struct ent *));
2222         p = head;
2223         x = 0;
2224         while (p) {
2225             ar[x++] = p;
2226             p = p->next;
2227         }
2228
2229         qsort((void *) ar, num_ent, sizeof(struct ent *),
2230               (int (*)(const void *, const void *)) dsortf);
2231     }
2232     output_directories(ar, num_ent, autoindex_conf, r, autoindex_opts,
2233                        keyid, direction, colargs);
2234     apr_dir_close(thedir);
2235
2236     emit_tail(r, find_readme(autoindex_conf, r),
2237               autoindex_opts & SUPPRESS_PREAMBLE);
2238
2239     return 0;
2240 }
2241
2242 /* The formal handler... */
2243
2244 static int handle_autoindex(request_rec *r)
2245 {
2246     autoindex_config_rec *d;
2247     int allow_opts;
2248
2249     if(strcmp(r->handler,DIR_MAGIC_TYPE)) {
2250         return DECLINED;
2251     }
2252
2253     allow_opts = ap_allow_options(r);
2254
2255     d = (autoindex_config_rec *) ap_get_module_config(r->per_dir_config,
2256                                                       &autoindex_module);
2257
2258     r->allowed |= (AP_METHOD_BIT << M_GET);
2259     if (r->method_number != M_GET) {
2260         return DECLINED;
2261     }
2262
2263     /* OK, nothing easy.  Trot out the heavy artillery... */
2264
2265     if (allow_opts & OPT_INDEXES) {
2266         int errstatus;
2267
2268         if ((errstatus = ap_discard_request_body(r)) != OK) {
2269             return errstatus;
2270         }
2271
2272         /* KLUDGE --- make the sub_req lookups happen in the right directory.
2273          * Fixing this in the sub_req_lookup functions themselves is difficult,
2274          * and would probably break virtual includes...
2275          */
2276
2277         if (r->filename[strlen(r->filename) - 1] != '/') {
2278             r->filename = apr_pstrcat(r->pool, r->filename, "/", NULL);
2279         }
2280         return index_directory(r, d);
2281     }
2282     else {
2283         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2284                       "Directory index forbidden by "
2285                       "Options directive: %s", r->filename);
2286         return HTTP_FORBIDDEN;
2287     }
2288 }
2289
2290 static void register_hooks(apr_pool_t *p)
2291 {
2292     ap_hook_handler(handle_autoindex,NULL,NULL,APR_HOOK_MIDDLE);
2293 }
2294
2295 module AP_MODULE_DECLARE_DATA autoindex_module =
2296 {
2297     STANDARD20_MODULE_STUFF,
2298     create_autoindex_config,    /* dir config creater */
2299     merge_autoindex_configs,    /* dir merger --- default is to override */
2300     NULL,                       /* server config */
2301     NULL,                       /* merge server config */
2302     autoindex_cmds,             /* command apr_table_t */
2303     register_hooks              /* register hooks */
2304 };