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