]> granicus.if.org Git - apache/blob - modules/test/mod_autoindex.c
Namespace protect IOBUFSIZ since it is a public symbol.
[apache] / modules / test / mod_autoindex.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000-2001 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 #include "apr_strings.h"
70 #include "ap_config.h"
71 #include "httpd.h"
72 #include "http_config.h"
73 #include "http_core.h"
74 #include "http_request.h"
75 #include "http_protocol.h"
76 #include "http_log.h"
77 #include "http_main.h"
78 #include "util_script.h"
79 #include "apr_fnmatch.h"
80 #include "apr_strings.h"
81 #ifdef HAVE_STRING_H
82 #include <string.h>
83 #endif
84 #ifdef HAVE_STRINGS_H
85 #include <strings.h>
86 #endif
87
88 module AP_MODULE_DECLARE_DATA autoindex_module;
89
90 /****************************************************************
91  *
92  * Handling configuration directives...
93  */
94
95 #define HRULE 1
96 #define NO_HRULE 0
97 #define FRONT_MATTER 1
98 #define END_MATTER 0
99
100 #define FANCY_INDEXING 1        /* Indexing options */
101 #define ICONS_ARE_LINKS 2
102 #define SCAN_HTML_TITLES 4
103 #define SUPPRESS_LAST_MOD 8
104 #define SUPPRESS_SIZE 16
105 #define SUPPRESS_DESC 32
106 #define SUPPRESS_PREAMBLE 64
107 #define SUPPRESS_COLSORT 128
108 #define NO_OPTIONS 256
109 #define VERSION_SORT    512
110
111 #define K_PAD 1
112 #define K_NOPAD 0
113
114 #define K_NOADJUST 0
115 #define K_ADJUST 1
116 #define K_UNSET 2
117
118 /*
119  * Define keys for sorting.
120  */
121 #define K_NAME 'N'              /* Sort by file name (default) */
122 #define K_LAST_MOD 'M'          /* Last modification date */
123 #define K_SIZE 'S'              /* Size (absolute, not as displayed) */
124 #define K_DESC 'D'              /* Description */
125
126 #define D_ASCENDING 'A'
127 #define D_DESCENDING 'D'
128
129 /*
130  * These are the dimensions of the default icons supplied with Apache.
131  */
132 #define DEFAULT_ICON_WIDTH 20
133 #define DEFAULT_ICON_HEIGHT 22
134
135 /*
136  * Other default dimensions.
137  */
138 #define DEFAULT_NAME_WIDTH 23
139
140 struct item {
141     char *type;
142     char *apply_to;
143     char *apply_path;
144     char *data;
145 };
146
147 typedef struct ai_desc_t {
148     char *pattern;
149     char *description;
150     int full_path;
151     int wildcards;
152 } ai_desc_t;
153
154 typedef struct autoindex_config_struct {
155
156     char *default_icon;
157     int opts;
158     int incremented_opts;
159     int decremented_opts;
160     int name_width;
161     int name_adjust;
162     int icon_width;
163     int icon_height;
164     char *default_order;
165
166     apr_array_header_t *icon_list;
167     apr_array_header_t *alt_list;
168     apr_array_header_t *desc_list;
169     apr_array_header_t *ign_list;
170     apr_array_header_t *hdr_list;
171     apr_array_header_t *rdme_list;
172
173 } autoindex_config_rec;
174
175 static char c_by_encoding, c_by_type, c_by_path;
176
177 #define BY_ENCODING &c_by_encoding
178 #define BY_TYPE &c_by_type
179 #define BY_PATH &c_by_path
180
181 /*
182  * Return true if the specified string refers to the parent directory (i.e.,
183  * matches ".." or "../").  Hopefully this one call is significantly less
184  * expensive than multiple strcmp() calls.
185  */
186 static apr_inline int is_parent(const char *name)
187 {
188     /*
189      * Now, IFF the first two bytes are dots, and the third byte is either
190      * EOS (\0) or a slash followed by EOS, we have a match.
191      */
192     if (((name[0] == '.') && (name[1] == '.'))
193         && ((name[2] == '\0')
194             || ((name[2] == '/') && (name[3] == '\0')))) {
195         return 1;
196     }
197     return 0;
198 }
199
200 /*
201  * This routine puts the standard HTML header at the top of the index page.
202  * We include the DOCTYPE because we may be using features therefrom (i.e.,
203  * HEIGHT and WIDTH attributes on the icons if we're FancyIndexing).
204  */
205 static void emit_preamble(request_rec *r, char *title)
206 {
207     ap_rvputs(r, DOCTYPE_HTML_3_2,
208               "<HTML>\n <HEAD>\n  <TITLE>Index of ", title,
209               "</TITLE>\n </HEAD>\n <BODY>\n", NULL);
210 }
211
212 static void push_item(apr_array_header_t *arr, char *type, const char *to,
213                       const char *path, const char *data)
214 {
215     struct item *p = (struct item *) apr_array_push(arr);
216
217     if (!to) {
218         to = "";
219     }
220     if (!path) {
221         path = "";
222     }
223
224     p->type = type;
225     p->data = data ? apr_pstrdup(arr->cont, data) : NULL;
226     p->apply_path = apr_pstrcat(arr->cont, path, "*", NULL);
227
228     if ((type == BY_PATH) && (!ap_is_matchexp(to))) {
229         p->apply_to = apr_pstrcat(arr->cont, "*", to, NULL);
230     }
231     else if (to) {
232         p->apply_to = apr_pstrdup(arr->cont, to);
233     }
234     else {
235         p->apply_to = NULL;
236     }
237 }
238
239 static const char *add_alt(cmd_parms *cmd, void *d, const char *alt,
240                            const char *to)
241 {
242     if (cmd->info == BY_PATH) {
243         if (!strcmp(to, "**DIRECTORY**")) {
244             to = "^^DIRECTORY^^";
245         }
246     }
247     if (cmd->info == BY_ENCODING) {
248         char *tmp = apr_pstrdup(cmd->pool, to);
249         ap_str_tolower(tmp);
250         to = tmp;
251     }
252
253     push_item(((autoindex_config_rec *) d)->alt_list, cmd->info, to,
254               cmd->path, alt);
255     return NULL;
256 }
257
258 static const char *add_icon(cmd_parms *cmd, void *d, const char *icon,
259                             const char *to)
260 {
261     char *iconbak = apr_pstrdup(cmd->pool, icon);
262
263     if (icon[0] == '(') {
264         char *alt;
265         char *cl = strchr(iconbak, ')');
266
267         if (cl == NULL) {
268             return "missing closing paren";
269         }
270         alt = ap_getword_nc(cmd->pool, &iconbak, ',');
271         *cl = '\0';                             /* Lose closing paren */
272         add_alt(cmd, d, &alt[1], to);
273     }
274     if (cmd->info == BY_PATH) {
275         if (!strcmp(to, "**DIRECTORY**")) {
276             to = "^^DIRECTORY^^";
277         }
278     }
279     if (cmd->info == BY_ENCODING) {
280         char *tmp = apr_pstrdup(cmd->pool, to);
281         ap_str_tolower(tmp);
282         to = tmp;
283     }
284
285     push_item(((autoindex_config_rec *) d)->icon_list, cmd->info, to,
286               cmd->path, iconbak);
287     return NULL;
288 }
289
290 /*
291  * Add description text for a filename pattern.  If the pattern has
292  * wildcards already (or we need to add them), add leading and
293  * trailing wildcards to it to ensure substring processing.  If the
294  * pattern contains a '/' anywhere, force wildcard matching mode,
295  * add a slash to the prefix so that "bar/bletch" won't be matched
296  * by "foobar/bletch", and make a note that there's a delimiter;
297  * the matching routine simplifies to just the actual filename
298  * whenever it can.  This allows definitions in parent directories
299  * to be made for files in subordinate ones using relative paths.
300  */
301
302 /*
303  * Absent a strcasestr() function, we have to force wildcards on
304  * systems for which "AAA" and "aaa" mean the same file.
305  */
306 #ifdef CASE_BLIND_FILESYSTEM
307 #define WILDCARDS_REQUIRED 1
308 #else
309 #define WILDCARDS_REQUIRED 0
310 #endif
311
312 static const char *add_desc(cmd_parms *cmd, void *d, const char *desc,
313                             const char *to)
314 {
315     autoindex_config_rec *dcfg = (autoindex_config_rec *) d;
316     ai_desc_t *desc_entry;
317     char *prefix = "";
318
319     desc_entry = (ai_desc_t *) apr_array_push(dcfg->desc_list);
320     desc_entry->full_path = (ap_strchr_c(to, '/') == NULL) ? 0 : 1;
321     desc_entry->wildcards = (WILDCARDS_REQUIRED
322                              || desc_entry->full_path
323                              || apr_is_fnmatch(to));
324     if (desc_entry->wildcards) {
325         prefix = desc_entry->full_path ? "*/" : "*";
326         desc_entry->pattern = apr_pstrcat(dcfg->desc_list->cont,
327                                          prefix, to, "*", NULL);
328     }
329     else {
330         desc_entry->pattern = apr_pstrdup(dcfg->desc_list->cont, to);
331     }
332     desc_entry->description = apr_pstrdup(dcfg->desc_list->cont, desc);
333     return NULL;
334 }
335
336 static const char *add_ignore(cmd_parms *cmd, void *d, const char *ext)
337 {
338     push_item(((autoindex_config_rec *) d)->ign_list, 0, ext, cmd->path, NULL);
339     return NULL;
340 }
341
342 static const char *add_header(cmd_parms *cmd, void *d, const char *name)
343 {
344     push_item(((autoindex_config_rec *) d)->hdr_list, 0, NULL, cmd->path,
345               name);
346     return NULL;
347 }
348
349 static const char *add_readme(cmd_parms *cmd, void *d, const char *name)
350 {
351     push_item(((autoindex_config_rec *) d)->rdme_list, 0, NULL, cmd->path,
352               name);
353     return NULL;
354 }
355
356 /* A legacy directive, FancyIndexing is superseded by the IndexOptions
357  * keyword.  But for compatibility..
358  */
359 static const char *fancy_indexing(cmd_parms *cmd, void *d, int arg)
360 {
361     int curopts;
362     int newopts;
363     autoindex_config_rec *cfg;
364
365     cfg = (autoindex_config_rec *) d;
366     curopts = cfg->opts;
367     if (curopts & NO_OPTIONS) {
368         return "FancyIndexing directive conflicts with existing "
369                "IndexOptions None";
370     }
371     newopts = (arg ? (curopts | FANCY_INDEXING) : (curopts & ~FANCY_INDEXING));
372     cfg->opts = newopts;
373     return NULL;
374 }
375
376 static const char *add_opts(cmd_parms *cmd, void *d, const char *optstr)
377 {
378     char *w;
379     int opts;
380     int opts_add;
381     int opts_remove;
382     char action;
383     autoindex_config_rec *d_cfg = (autoindex_config_rec *) d;
384
385     opts = d_cfg->opts;
386     opts_add = d_cfg->incremented_opts;
387     opts_remove = d_cfg->decremented_opts;
388     while (optstr[0]) {
389         int option = 0;
390
391         w = ap_getword_conf(cmd->pool, &optstr);
392         if ((*w == '+') || (*w == '-')) {
393             action = *(w++);
394         }
395         else {
396             action = '\0';
397         }
398         if (!strcasecmp(w, "FancyIndexing")) {
399             option = FANCY_INDEXING;
400         }
401         else if (!strcasecmp(w, "IconsAreLinks")) {
402             option = ICONS_ARE_LINKS;
403         }
404         else if (!strcasecmp(w, "ScanHTMLTitles")) {
405             option = SCAN_HTML_TITLES;
406         }
407         else if (!strcasecmp(w, "SuppressLastModified")) {
408             option = SUPPRESS_LAST_MOD;
409         }
410         else if (!strcasecmp(w, "SuppressSize")) {
411             option = SUPPRESS_SIZE;
412         }
413         else if (!strcasecmp(w, "SuppressDescription")) {
414             option = SUPPRESS_DESC;
415         }
416         else if (!strcasecmp(w, "SuppressHTMLPreamble")) {
417             option = SUPPRESS_PREAMBLE;
418         }
419         else if (!strcasecmp(w, "SuppressColumnSorting")) {
420             option = SUPPRESS_COLSORT;
421         }
422         else if (!strcasecmp(w, "VersionSort")) {
423             option = VERSION_SORT;
424         }
425         else if (!strcasecmp(w, "None")) {
426             if (action != '\0') {
427                 return "Cannot combine '+' or '-' with 'None' keyword";
428             }
429             opts = NO_OPTIONS;
430             opts_add = 0;
431             opts_remove = 0;
432         }
433         else if (!strcasecmp(w, "IconWidth")) {
434             if (action != '-') {
435                 d_cfg->icon_width = DEFAULT_ICON_WIDTH;
436             }
437             else {
438                 d_cfg->icon_width = 0;
439             }
440         }
441         else if (!strncasecmp(w, "IconWidth=", 10)) {
442             if (action == '-') {
443                 return "Cannot combine '-' with IconWidth=n";
444             }
445             d_cfg->icon_width = atoi(&w[10]);
446         }
447         else if (!strcasecmp(w, "IconHeight")) {
448             if (action != '-') {
449                 d_cfg->icon_height = DEFAULT_ICON_HEIGHT;
450             }
451             else {
452                 d_cfg->icon_height = 0;
453             }
454         }
455         else if (!strncasecmp(w, "IconHeight=", 11)) {
456             if (action == '-') {
457                 return "Cannot combine '-' with IconHeight=n";
458             }
459             d_cfg->icon_height = atoi(&w[11]);
460         }
461         else if (!strcasecmp(w, "NameWidth")) {
462             if (action != '-') {
463                 return "NameWidth with no value may only appear as "
464                        "'-NameWidth'";
465             }
466             d_cfg->name_width = DEFAULT_NAME_WIDTH;
467             d_cfg->name_adjust = K_NOADJUST;
468         }
469         else if (!strncasecmp(w, "NameWidth=", 10)) {
470             if (action == '-') {
471                 return "Cannot combine '-' with NameWidth=n";
472             }
473             if (w[10] == '*') {
474                 d_cfg->name_adjust = K_ADJUST;
475             }
476             else {
477                 int width = atoi(&w[10]);
478
479                 if (width < 5) {
480                     return "NameWidth value must be greater than 5";
481                 }
482                 d_cfg->name_width = width;
483                 d_cfg->name_adjust = K_NOADJUST;
484             }
485         }
486         else {
487             return "Invalid directory indexing option";
488         }
489         if (action == '\0') {
490             opts |= option;
491             opts_add = 0;
492             opts_remove = 0;
493         }
494         else if (action == '+') {
495             opts_add |= option;
496             opts_remove &= ~option;
497         }
498         else {
499             opts_remove |= option;
500             opts_add &= ~option;
501         }
502     }
503     if ((opts & NO_OPTIONS) && (opts & ~NO_OPTIONS)) {
504         return "Cannot combine other IndexOptions keywords with 'None'";
505     }
506     d_cfg->incremented_opts = opts_add;
507     d_cfg->decremented_opts = opts_remove;
508     d_cfg->opts = opts;
509     return NULL;
510 }
511
512 static const char *set_default_order(cmd_parms *cmd, void *m, const char *direction,
513                                      const char *key)
514 {
515     char temp[4];
516     autoindex_config_rec *d_cfg = (autoindex_config_rec *) m;
517
518     apr_cpystrn(temp, "k=d", sizeof(temp));
519     if (!strcasecmp(direction, "Ascending")) {
520         temp[2] = D_ASCENDING;
521     }
522     else if (!strcasecmp(direction, "Descending")) {
523         temp[2] = D_DESCENDING;
524     }
525     else {
526         return "First keyword must be 'Ascending' or 'Descending'";
527     }
528
529     if (!strcasecmp(key, "Name")) {
530         temp[0] = K_NAME;
531     }
532     else if (!strcasecmp(key, "Date")) {
533         temp[0] = K_LAST_MOD;
534     }
535     else if (!strcasecmp(key, "Size")) {
536         temp[0] = K_SIZE;
537     }
538     else if (!strcasecmp(key, "Description")) {
539         temp[0] = K_DESC;
540     }
541     else {
542         return "Second keyword must be 'Name', 'Date', 'Size', or "
543             "'Description'";
544     }
545
546     if (d_cfg->default_order == NULL) {
547         d_cfg->default_order = apr_palloc(cmd->pool, 4);
548         d_cfg->default_order[3] = '\0';
549     }
550     apr_cpystrn(d_cfg->default_order, temp, sizeof(temp));
551     return NULL;
552 }
553
554 #define DIR_CMD_PERMS OR_INDEXES
555
556 static const command_rec autoindex_cmds[] =
557 {
558     AP_INIT_ITERATE2("AddIcon", add_icon, BY_PATH, DIR_CMD_PERMS, 
559                      "an icon URL followed by one or more filenames"),
560     AP_INIT_ITERATE2("AddIconByType", add_icon, BY_TYPE, DIR_CMD_PERMS,
561                      "an icon URL followed by one or more MIME types"),
562     AP_INIT_ITERATE2("AddIconByEncoding", add_icon, BY_ENCODING, DIR_CMD_PERMS,
563                      "an icon URL followed by one or more content encodings"),
564     AP_INIT_ITERATE2("AddAlt", add_alt, BY_PATH, DIR_CMD_PERMS,
565                      "alternate descriptive text followed by one or more filenames"),
566     AP_INIT_ITERATE2("AddAltByType", add_alt, BY_TYPE, DIR_CMD_PERMS,
567                      "alternate descriptive text followed by one or more MIME types"),
568     AP_INIT_ITERATE2("AddAltByEncoding", add_alt, BY_ENCODING, DIR_CMD_PERMS,
569                      "alternate descriptive text followed by one or more content encodings"),
570     AP_INIT_RAW_ARGS("IndexOptions", add_opts, NULL, DIR_CMD_PERMS,
571                      "one or more index options"),
572     AP_INIT_TAKE2("IndexOrderDefault", set_default_order, NULL, DIR_CMD_PERMS,
573                   "{Ascending,Descending} {Name,Size,Description,Date}"),
574     AP_INIT_ITERATE("IndexIgnore", add_ignore, NULL, DIR_CMD_PERMS,
575                     "one or more file extensions"),
576     AP_INIT_ITERATE2("AddDescription", add_desc, BY_PATH, DIR_CMD_PERMS,
577                      "Descriptive text followed by one or more filenames"),
578     AP_INIT_TAKE1("HeaderName", add_header, NULL, DIR_CMD_PERMS,
579                   "a filename"),
580     AP_INIT_TAKE1("ReadmeName", add_readme, NULL, DIR_CMD_PERMS,
581                   "a filename"),
582     AP_INIT_FLAG("FancyIndexing", fancy_indexing, NULL, DIR_CMD_PERMS,
583                  "Limited to 'on' or 'off' (superseded by IndexOptions FancyIndexing)"),
584     AP_INIT_TAKE1("DefaultIcon", ap_set_string_slot,
585                   (void *) XtOffsetOf(autoindex_config_rec, default_icon),
586                   DIR_CMD_PERMS, "an icon URL"),
587     {NULL}
588 };
589
590 static void *create_autoindex_config(apr_pool_t *p, char *dummy)
591 {
592     autoindex_config_rec *new =
593     (autoindex_config_rec *) apr_pcalloc(p, sizeof(autoindex_config_rec));
594
595     new->icon_width = 0;
596     new->icon_height = 0;
597     new->name_width = DEFAULT_NAME_WIDTH;
598     new->name_adjust = K_UNSET;
599     new->icon_list = apr_array_make(p, 4, sizeof(struct item));
600     new->alt_list = apr_array_make(p, 4, sizeof(struct item));
601     new->desc_list = apr_array_make(p, 4, sizeof(ai_desc_t));
602     new->ign_list = apr_array_make(p, 4, sizeof(struct item));
603     new->hdr_list = apr_array_make(p, 4, sizeof(struct item));
604     new->rdme_list = apr_array_make(p, 4, sizeof(struct item));
605     new->opts = 0;
606     new->incremented_opts = 0;
607     new->decremented_opts = 0;
608     new->default_order = NULL;
609
610     return (void *) new;
611 }
612
613 static void *merge_autoindex_configs(apr_pool_t *p, void *basev, void *addv)
614 {
615     autoindex_config_rec *new;
616     autoindex_config_rec *base = (autoindex_config_rec *) basev;
617     autoindex_config_rec *add = (autoindex_config_rec *) addv;
618
619     new = (autoindex_config_rec *) apr_pcalloc(p, sizeof(autoindex_config_rec));
620     new->default_icon = add->default_icon ? add->default_icon
621                                           : base->default_icon;
622     new->icon_height = add->icon_height ? add->icon_height : base->icon_height;
623     new->icon_width = add->icon_width ? add->icon_width : base->icon_width;
624
625     new->alt_list = apr_array_append(p, add->alt_list, base->alt_list);
626     new->ign_list = apr_array_append(p, add->ign_list, base->ign_list);
627     new->hdr_list = apr_array_append(p, add->hdr_list, base->hdr_list);
628     new->desc_list = apr_array_append(p, add->desc_list, base->desc_list);
629     new->icon_list = apr_array_append(p, add->icon_list, base->icon_list);
630     new->rdme_list = apr_array_append(p, add->rdme_list, base->rdme_list);
631     if (add->opts & NO_OPTIONS) {
632         /*
633          * If the current directory says 'no options' then we also
634          * clear any incremental mods from being inheritable further down.
635          */
636         new->opts = NO_OPTIONS;
637         new->incremented_opts = 0;
638         new->decremented_opts = 0;
639     }
640     else {
641         /*
642          * If there were any nonincremental options selected for
643          * this directory, they dominate and we don't inherit *anything.*
644          * Contrariwise, we *do* inherit if the only settings here are
645          * incremental ones.
646          */
647         if (add->opts == 0) {
648             new->incremented_opts = (base->incremented_opts 
649                                      | add->incremented_opts)
650                                     & ~add->decremented_opts;
651             new->decremented_opts = (base->decremented_opts
652                                      | add->decremented_opts);
653             /*
654              * We may have incremental settings, so make sure we don't
655              * inadvertently inherit an IndexOptions None from above.
656              */
657             new->opts = (base->opts & ~NO_OPTIONS);
658         }
659         else {
660             /*
661              * There are local nonincremental settings, which clear
662              * all inheritance from above.  They *are* the new base settings.
663              */
664             new->opts = add->opts;;
665         }
666         /*
667          * We're guaranteed that there'll be no overlap between
668          * the add-options and the remove-options.
669          */
670         new->opts |= new->incremented_opts;
671         new->opts &= ~new->decremented_opts;
672     }
673     /*
674      * Inherit the NameWidth settings if there aren't any specific to
675      * the new location; otherwise we'll end up using the defaults set in the
676      * config-rec creation routine.
677      */
678     if (add->name_adjust == K_UNSET) {
679         new->name_width = base->name_width;
680         new->name_adjust = base->name_adjust;
681     }
682     else {
683         new->name_width = add->name_width;
684         new->name_adjust = add->name_adjust;
685     }
686
687     new->default_order = (add->default_order != NULL)
688         ? add->default_order : base->default_order;
689     return new;
690 }
691
692 /****************************************************************
693  *
694  * Looking things up in config entries...
695  */
696
697 /* Structure used to hold entries when we're actually building an index */
698
699 struct ent {
700     char *name;
701     char *icon;
702     char *alt;
703     char *desc;
704     off_t size;
705     apr_time_t lm;
706     struct ent *next;
707     int ascending, version_sort;
708     char key;
709 };
710
711 static char *find_item(request_rec *r, apr_array_header_t *list, int path_only)
712 {
713     const char *content_type = ap_field_noparam(r->pool, r->content_type);
714     const char *content_encoding = r->content_encoding;
715     char *path = r->filename;
716
717     struct item *items = (struct item *) list->elts;
718     int i;
719
720     for (i = 0; i < list->nelts; ++i) {
721         struct item *p = &items[i];
722
723         /* Special cased for ^^DIRECTORY^^ and ^^BLANKICON^^ */
724         if ((path[0] == '^') || (!ap_strcmp_match(path, p->apply_path))) {
725             if (!*(p->apply_to)) {
726                 return p->data;
727             }
728             else if (p->type == BY_PATH || path[0] == '^') {
729                 if (!ap_strcmp_match(path, p->apply_to)) {
730                     return p->data;
731                 }
732             }
733             else if (!path_only) {
734                 if (!content_encoding) {
735                     if (p->type == BY_TYPE) {
736                         if (content_type
737                             && !ap_strcasecmp_match(content_type,
738                                                     p->apply_to)) {
739                             return p->data;
740                         }
741                     }
742                 }
743                 else {
744                     if (p->type == BY_ENCODING) {
745                         if (!ap_strcasecmp_match(content_encoding,
746                                                  p->apply_to)) {
747                             return p->data;
748                         }
749                     }
750                 }
751             }
752         }
753     }
754     return NULL;
755 }
756
757 #define find_icon(d,p,t) find_item(p,d->icon_list,t)
758 #define find_alt(d,p,t) find_item(p,d->alt_list,t)
759 #define find_header(d,p) find_item(p,d->hdr_list,0)
760 #define find_readme(d,p) find_item(p,d->rdme_list,0)
761
762 static char *find_default_icon(autoindex_config_rec *d, char *bogus_name)
763 {
764     request_rec r;
765
766     /* Bleah.  I tried to clean up find_item, and it lead to this bit
767      * of ugliness.   Note that the fields initialized are precisely
768      * those that find_item looks at...
769      */
770
771     r.filename = bogus_name;
772     r.content_type = r.content_encoding = NULL;
773
774     return find_item(&r, d->icon_list, 1);
775 }
776
777 /*
778  * Look through the list of pattern/description pairs and return the first one
779  * if any) that matches the filename in the request.  If multiple patterns
780  * match, only the first one is used; since the order in the array is the
781  * same as the order in which directives were processed, earlier matching
782  * directives will dominate.
783  */
784
785 #ifdef CASE_BLIND_FILESYSTEM
786 #define MATCH_FLAGS FNM_CASE_BLIND
787 #else
788 #define MATCH_FLAGS 0
789 #endif
790
791 static char *find_desc(autoindex_config_rec *dcfg, request_rec *r)
792 {
793     int i;
794     ai_desc_t *list = (ai_desc_t *) dcfg->desc_list->elts;
795     const char *filename_full = r->filename;
796     const char *filename_only;
797     const char *filename;
798
799     /*
800      * If the filename includes a path, extract just the name itself
801      * for the simple matches.
802      */
803     if ((filename_only = ap_strrchr_c(filename_full, '/')) == NULL) {
804         filename_only = filename_full;
805     }
806     else {
807         filename_only++;
808     }
809     for (i = 0; i < dcfg->desc_list->nelts; ++i) {
810         ai_desc_t *tuple = &list[i];
811         int found;
812
813         /*
814          * Only use the full-path filename if the pattern contains '/'s.
815          */
816         filename = (tuple->full_path) ? filename_full : filename_only;
817         /*
818          * Make the comparison using the cheapest method; only do
819          * wildcard checking if we must.
820          */
821         if (tuple->wildcards) {
822             found = (apr_fnmatch(tuple->pattern, filename, MATCH_FLAGS) == 0);
823         }
824         else {
825             found = (ap_strstr_c(filename, tuple->pattern) != NULL);
826         }
827         if (found) {
828             return tuple->description;
829         }
830     }
831     return NULL;
832 }
833
834 static int ignore_entry(autoindex_config_rec *d, char *path)
835 {
836     apr_array_header_t *list = d->ign_list;
837     struct item *items = (struct item *) list->elts;
838     char *tt;
839     int i;
840
841     if ((tt = strrchr(path, '/')) == NULL) {
842         tt = path;
843     }
844     else {
845         tt++;
846     }
847
848     for (i = 0; i < list->nelts; ++i) {
849         struct item *p = &items[i];
850         char *ap;
851
852         if ((ap = strrchr(p->apply_to, '/')) == NULL) {
853             ap = p->apply_to;
854         }
855         else {
856             ap++;
857         }
858
859 #ifndef CASE_BLIND_FILESYSTEM
860         if (!ap_strcmp_match(path, p->apply_path)
861             && !ap_strcmp_match(tt, ap)) {
862             return 1;
863         }
864 #else  /* !CASE_BLIND_FILESYSTEM */
865         /*
866          * On some platforms, the match must be case-blind.  This is really
867          * a factor of the filesystem involved, but we can't detect that
868          * reliably - so we have to granularise at the OS level.
869          */
870         if (!ap_strcasecmp_match(path, p->apply_path)
871             && !ap_strcasecmp_match(tt, ap)) {
872             return 1;
873         }
874 #endif /* !CASE_BLIND_FILESYSTEM */
875     }
876     return 0;
877 }
878
879 /*****************************************************************
880  *
881  * Actually generating output
882  */
883
884 /*
885  * Elements of the emitted document:
886  *      Preamble
887  *              Emitted unless SUPPRESS_PREAMBLE is set AND ap_run_sub_req
888  *              succeeds for the (content_type == text/html) header file.
889  *      Header file
890  *              Emitted if found (and able).
891  *      H1 tag line
892  *              Emitted if a header file is NOT emitted.
893  *      Directory stuff
894  *              Always emitted.
895  *      HR
896  *              Emitted if FANCY_INDEXING is set.
897  *      Readme file
898  *              Emitted if found (and able).
899  *      ServerSig
900  *              Emitted if ServerSignature is not Off AND a readme file
901  *              is NOT emitted.
902  *      Postamble
903  *              Emitted unless SUPPRESS_PREAMBLE is set AND ap_run_sub_req
904  *              succeeds for the (content_type == text/html) readme file.
905  */
906
907
908 /*
909  * emit a plain text file
910  */
911 static void do_emit_plain(request_rec *r, apr_file_t *f)
912 {
913     char buf[AP_IOBUFSIZE + 1];
914     int i, c, ch;
915     apr_size_t n;
916     apr_status_t stat;
917
918     ap_rputs("<PRE>\n", r);
919     while (!apr_file_eof(f)) {
920         do {
921             n = sizeof(char) * AP_IOBUFSIZE;
922             stat = apr_file_read(f, buf, &n);
923         }
924         while (stat != APR_SUCCESS && APR_STATUS_IS_EINTR(stat));
925         if (n == -1 || n == 0) {
926             break;
927         }
928         buf[n] = '\0';
929         c = 0;
930         while (c < n) {
931             for (i = c; i < n; i++) {
932                 if (buf[i] == '<' || buf[i] == '>' || buf[i] == '&') {
933                     break;
934                 }
935             }
936             ch = buf[i];
937             buf[i] = '\0';
938             ap_rputs(&buf[c], r);
939             if (ch == '<') {
940                 ap_rputs("&lt;", r);
941             }
942             else if (ch == '>') {
943                 ap_rputs("&gt;", r);
944             }
945             else if (ch == '&') {
946                 ap_rputs("&amp;", r);
947             }
948             c = i + 1;
949         }
950     }
951     ap_rputs("</PRE>\n", r);
952 }
953
954 /*
955  * Handle the preamble through the H1 tag line, inclusive.  Locate
956  * the file with a subrequests.  Process text/html documents by actually
957  * running the subrequest; text/xxx documents get copied verbatim,
958  * and any other content type is ignored.  This means that a non-text
959  * document (such as HEADER.gif) might get multiviewed as the result
960  * instead of a text document, meaning nothing will be displayed, but
961  * oh well.
962  */
963 static void emit_head(request_rec *r, char *header_fname, int suppress_amble,
964                       char *title)
965 {
966     apr_file_t *f = NULL;
967     request_rec *rr = NULL;
968     int emit_amble = 1;
969     int emit_H1 = 1;
970
971     /*
972      * If there's a header file, send a subrequest to look for it.  If it's
973      * found and a text file, handle it -- otherwise fall through and
974      * pretend there's nothing there.
975      */
976     if ((header_fname != NULL)
977         && (rr = ap_sub_req_lookup_uri(header_fname, r, NULL))
978         && (rr->status == HTTP_OK)
979         && (rr->filename != NULL)
980         && rr->finfo.filetype == APR_REG) {
981         /*
982          * Check for the two specific cases we allow: text/html and
983          * text/anything-else.  The former is allowed to be processed for
984          * SSIs.
985          */
986         if (rr->content_type != NULL) {
987             if (!strcasecmp(ap_field_noparam(r->pool, rr->content_type),
988                             "text/html")) {
989                 /* Hope everything will work... */
990                 emit_amble = 0;
991                 emit_H1 = 0;
992
993                 if (! suppress_amble) {
994                     emit_preamble(r, title);
995                 }
996                 /*
997                  * If there's a problem running the subrequest, display the
998                  * preamble if we didn't do it before -- the header file
999                  * didn't get displayed.
1000                  */
1001                 if (ap_run_sub_req(rr) != OK) {
1002                     /* It didn't work */
1003                     emit_amble = suppress_amble;
1004                     emit_H1 = 1;
1005                 }
1006             }
1007             else if (!strncasecmp("text/", rr->content_type, 5)) {
1008                 /*
1009                  * If we can open the file, prefix it with the preamble
1010                  * regardless; since we'll be sending a <PRE> block around
1011                  * the file's contents, any HTML header it had won't end up
1012                  * where it belongs.
1013                  */
1014                 if (apr_file_open(&f, rr->filename, APR_READ,
1015                             APR_OS_DEFAULT, r->pool) == APR_SUCCESS) {
1016                     emit_preamble(r, title);
1017                     emit_amble = 0;
1018                     do_emit_plain(r, f);
1019                     apr_file_close(f);
1020                     emit_H1 = 0;
1021                 }
1022             }
1023         }
1024     }
1025
1026     if (emit_amble) {
1027         emit_preamble(r, title);
1028     }
1029     if (emit_H1) {
1030         ap_rvputs(r, "<H1>Index of ", title, "</H1>\n", NULL);
1031     }
1032     if (rr != NULL) {
1033         ap_destroy_sub_req(rr);
1034     }
1035 }
1036
1037
1038 /*
1039  * Handle the Readme file through the postamble, inclusive.  Locate
1040  * the file with a subrequests.  Process text/html documents by actually
1041  * running the subrequest; text/xxx documents get copied verbatim,
1042  * and any other content type is ignored.  This means that a non-text
1043  * document (such as FOOTER.gif) might get multiviewed as the result
1044  * instead of a text document, meaning nothing will be displayed, but
1045  * oh well.
1046  */
1047 static void emit_tail(request_rec *r, char *readme_fname, int suppress_amble)
1048 {
1049     apr_file_t *f = NULL;
1050     request_rec *rr = NULL;
1051     int suppress_post = 0;
1052     int suppress_sig = 0;
1053
1054     /*
1055      * If there's a readme file, send a subrequest to look for it.  If it's
1056      * found and a text file, handle it -- otherwise fall through and
1057      * pretend there's nothing there.
1058      */
1059     if ((readme_fname != NULL)
1060         && (rr = ap_sub_req_lookup_uri(readme_fname, r, NULL))
1061         && (rr->status == HTTP_OK)
1062         && (rr->filename != NULL)
1063         && rr->finfo.filetype == APR_REG) {
1064         /*
1065          * Check for the two specific cases we allow: text/html and
1066          * text/anything-else.  The former is allowed to be processed for
1067          * SSIs.
1068          */
1069         if (rr->content_type != NULL) {
1070             if (!strcasecmp(ap_field_noparam(r->pool, rr->content_type),
1071                             "text/html")) {
1072                 if (ap_run_sub_req(rr) == OK) {
1073                     /* worked... */
1074                     suppress_sig = 1;
1075                     suppress_post = suppress_amble;
1076                 }
1077             }
1078             else if (!strncasecmp("text/", rr->content_type, 5)) {
1079                 /*
1080                  * If we can open the file, suppress the signature.
1081                  */
1082                 if (apr_file_open(&f, rr->filename, APR_READ,
1083                             APR_OS_DEFAULT, r->pool) == APR_SUCCESS) {
1084                     do_emit_plain(r, f);
1085                     apr_file_close(f);
1086                     suppress_sig = 1;
1087                 }
1088             }
1089         }
1090     }
1091     
1092     if (!suppress_sig) {
1093         ap_rputs(ap_psignature("", r), r);
1094     }
1095     if (!suppress_post) {
1096         ap_rputs("</BODY></HTML>\n", r);
1097     }
1098     if (rr != NULL) {
1099         ap_destroy_sub_req(rr);
1100     }
1101 }
1102
1103
1104 static char *find_title(request_rec *r)
1105 {
1106     char titlebuf[MAX_STRING_LEN], *find = "<TITLE>";
1107     apr_file_t *thefile = NULL;
1108     int x, y, p;
1109     apr_size_t n;
1110
1111     if (r->status != HTTP_OK) {
1112         return NULL;
1113     }
1114     if ((r->content_type != NULL)
1115         && (!strcasecmp(ap_field_noparam(r->pool, r->content_type),
1116                         "text/html")
1117             || !strcmp(r->content_type, INCLUDES_MAGIC_TYPE))
1118         && !r->content_encoding) {
1119         if (apr_file_open(&thefile, r->filename, APR_READ,
1120                     APR_OS_DEFAULT, r->pool) != APR_SUCCESS) {
1121             return NULL;
1122         }
1123         n = sizeof(char) * (MAX_STRING_LEN - 1);
1124         apr_file_read(thefile, titlebuf, &n);
1125         if (n <= 0) {
1126             apr_file_close(thefile);
1127             return NULL;
1128         }
1129         titlebuf[n] = '\0';
1130         for (x = 0, p = 0; titlebuf[x]; x++) {
1131             if (apr_toupper(titlebuf[x]) == find[p]) {
1132                 if (!find[++p]) {
1133                     if ((p = ap_ind(&titlebuf[++x], '<')) != -1) {
1134                         titlebuf[x + p] = '\0';
1135                     }
1136                     /* Scan for line breaks for Tanmoy's secretary */
1137                     for (y = x; titlebuf[y]; y++) {
1138                         if ((titlebuf[y] == CR) || (titlebuf[y] == LF)) {
1139                             if (y == x) {
1140                                 x++;
1141                             }
1142                             else {
1143                                 titlebuf[y] = ' ';
1144                             }
1145                         }
1146                     }
1147                     apr_file_close(thefile);
1148                     return apr_pstrdup(r->pool, &titlebuf[x]);
1149                 }
1150             }
1151             else {
1152                 p = 0;
1153             }
1154         }
1155         apr_file_close(thefile);
1156     }
1157     return NULL;
1158 }
1159
1160 static struct ent *make_autoindex_entry(const char *name, int autoindex_opts,
1161                                         autoindex_config_rec *d,
1162                                         request_rec *r, char keyid,
1163                                         char direction)
1164 {
1165     struct ent *p;
1166
1167     if ((name[0] == '.') && (!name[1])) {
1168         return (NULL);
1169     }
1170
1171     if (ignore_entry(d, ap_make_full_path(r->pool, r->filename, name))) {
1172         return (NULL);
1173     }
1174
1175     p = (struct ent *) apr_pcalloc(r->pool, sizeof(struct ent));
1176     p->name = apr_pstrdup(r->pool, name);
1177     p->size = -1;
1178     p->icon = NULL;
1179     p->alt = NULL;
1180     p->desc = NULL;
1181     p->lm = -1;
1182     p->key = apr_toupper(keyid);
1183     p->ascending = (apr_toupper(direction) == D_ASCENDING);
1184     p->version_sort = autoindex_opts & VERSION_SORT;
1185
1186     if (autoindex_opts & FANCY_INDEXING) {
1187         request_rec *rr = ap_sub_req_lookup_file(name, r, NULL);
1188
1189         if (rr->finfo.filetype != 0) {
1190             p->lm = rr->finfo.mtime;
1191             if (rr->finfo.filetype == APR_DIR) {
1192                 if (!(p->icon = find_icon(d, rr, 1))) {
1193                     p->icon = find_default_icon(d, "^^DIRECTORY^^");
1194                 }
1195                 if (!(p->alt = find_alt(d, rr, 1))) {
1196                     p->alt = "DIR";
1197                 }
1198                 p->size = -1;
1199                 p->name = apr_pstrcat(r->pool, name, "/", NULL);
1200             }
1201             else {
1202                 p->icon = find_icon(d, rr, 0);
1203                 p->alt = find_alt(d, rr, 0);
1204                 p->size = rr->finfo.size;
1205             }
1206         }
1207
1208         p->desc = find_desc(d, rr);
1209
1210         if ((!p->desc) && (autoindex_opts & SCAN_HTML_TITLES)) {
1211             p->desc = apr_pstrdup(r->pool, find_title(rr));
1212         }
1213
1214         ap_destroy_sub_req(rr);
1215     }
1216     /*
1217      * We don't need to take any special action for the file size key.  If
1218      * we did, it would go here.
1219      */
1220     if (keyid == K_LAST_MOD) {
1221         if (p->lm < 0) {
1222             p->lm = 0;
1223         }
1224     }
1225     return (p);
1226 }
1227
1228 static char *terminate_description(autoindex_config_rec *d, char *desc,
1229                                    int autoindex_opts)
1230 {
1231     int maxsize = 23;
1232     register int x;
1233
1234     if (autoindex_opts & SUPPRESS_LAST_MOD) {
1235         maxsize += 19;
1236     }
1237     if (autoindex_opts & SUPPRESS_SIZE) {
1238         maxsize += 7;
1239     }
1240
1241     for (x = 0; desc[x] && (maxsize > 0 || desc[x]=='<'); x++) {
1242         if (desc[x] == '<') {
1243             while (desc[x] != '>') {
1244                 if (!desc[x]) {
1245                     maxsize = 0;
1246                     break;
1247                 }
1248                 ++x;
1249             }
1250         }
1251         else if (desc[x] == '&') {
1252             /* entities like &auml; count as one character */
1253             --maxsize;
1254             for ( ; desc[x] != ';'; ++x) {
1255                 if (desc[x] == '\0') {
1256                      maxsize = 0;
1257                      break;
1258                 }
1259             }
1260         }
1261         else {
1262             --maxsize;
1263         }
1264     }
1265     if (!maxsize && desc[x] != '\0') {
1266         desc[x - 1] = '>';      /* Grump. */
1267         desc[x] = '\0';         /* Double Grump! */
1268     }
1269     return desc;
1270 }
1271
1272 /*
1273  * Emit the anchor for the specified field.  If a field is the key for the
1274  * current request, the link changes its meaning to reverse the order when
1275  * selected again.  Non-active fields always start in ascending order.
1276  */
1277 static void emit_link(request_rec *r, char *anchor, char fname, char curkey,
1278                       char curdirection, int nosort)
1279 {
1280     char qvalue[5];
1281     int reverse;
1282
1283     if (!nosort) {
1284         qvalue[0] = '?';
1285         qvalue[1] = fname;
1286         qvalue[2] = '=';
1287         qvalue[4] = '\0';
1288         reverse = ((curkey == fname) && (curdirection == D_ASCENDING));
1289         qvalue[3] = reverse ? D_DESCENDING : D_ASCENDING;
1290         ap_rvputs(r, "<A HREF=\"", qvalue, "\">", anchor, "</A>", NULL);
1291     }
1292     else {
1293         ap_rputs(anchor, r);
1294     }
1295 }
1296
1297 static void output_directories(struct ent **ar, int n,
1298                                autoindex_config_rec *d, request_rec *r,
1299                                int autoindex_opts, char keyid, char direction)
1300 {
1301     int x;
1302     apr_size_t rv;
1303     char *name = r->uri;
1304     char *tp;
1305     int static_columns = (autoindex_opts & SUPPRESS_COLSORT);
1306     apr_pool_t *scratch;
1307     int name_width;
1308     char *name_scratch;
1309     char *pad_scratch;
1310
1311     apr_pool_create(&scratch, r->pool);
1312     if (name[0] == '\0') {
1313         name = "/";
1314     }
1315
1316     name_width = d->name_width;
1317     if (d->name_adjust == K_ADJUST) {
1318         for (x = 0; x < n; x++) {
1319             int t = strlen(ar[x]->name);
1320             if (t > name_width) {
1321                 name_width = t;
1322             }
1323         }
1324     }
1325     name_scratch = apr_palloc(r->pool, name_width + 1);
1326     pad_scratch = apr_palloc(r->pool, name_width + 1);
1327     memset(pad_scratch, ' ', name_width);
1328     pad_scratch[name_width] = '\0';
1329
1330     if (autoindex_opts & FANCY_INDEXING) {
1331         ap_rputs("<PRE>", r);
1332         if ((tp = find_default_icon(d, "^^BLANKICON^^"))) {
1333             ap_rvputs(r, "<IMG SRC=\"", ap_escape_html(scratch, tp),
1334                    "\" ALT=\"     \"", NULL);
1335             if (d->icon_width && d->icon_height) {
1336                 ap_rprintf
1337                     (
1338                         r,
1339                         " HEIGHT=\"%d\" WIDTH=\"%d\"",
1340                         d->icon_height,
1341                         d->icon_width
1342                     );
1343             }
1344             ap_rputs("> ", r);
1345         }
1346         emit_link(r, "Name", K_NAME, keyid, direction, static_columns);
1347         ap_rputs(pad_scratch + 4, r);
1348         /*
1349          * Emit the guaranteed-at-least-one-space-between-columns byte.
1350          */
1351         ap_rputs(" ", r);
1352         if (!(autoindex_opts & SUPPRESS_LAST_MOD)) {
1353             emit_link(r, "Last modified", K_LAST_MOD, keyid, direction,
1354                       static_columns);
1355             ap_rputs("       ", r);
1356         }
1357         if (!(autoindex_opts & SUPPRESS_SIZE)) {
1358             emit_link(r, "Size", K_SIZE, keyid, direction, static_columns);
1359             ap_rputs("  ", r);
1360         }
1361         if (!(autoindex_opts & SUPPRESS_DESC)) {
1362             emit_link(r, "Description", K_DESC, keyid, direction,
1363                       static_columns);
1364         }
1365         ap_rputs("\n<HR>\n", r);
1366     }
1367     else {
1368         ap_rputs("<UL>", r);
1369     }
1370
1371     for (x = 0; x < n; x++) {
1372         char *anchor, *t, *t2;
1373         int nwidth;
1374
1375         apr_pool_clear(scratch);
1376
1377         if (is_parent(ar[x]->name)) {
1378             t = ap_make_full_path(scratch, name, "../");
1379             ap_getparents(t);
1380             if (t[0] == '\0') {
1381                 t = "/";
1382             }
1383             t2 = "Parent Directory";
1384             anchor = ap_escape_html(scratch, ap_os_escape_path(scratch, t, 0));
1385         }
1386         else {
1387             t = ar[x]->name;
1388             t2 = t;
1389             anchor = ap_escape_html(scratch, ap_os_escape_path(scratch, t, 0));
1390         }
1391
1392         if (autoindex_opts & FANCY_INDEXING) {
1393             if (autoindex_opts & ICONS_ARE_LINKS) {
1394                 ap_rvputs(r, "<A HREF=\"", anchor, "\">", NULL);
1395             }
1396             if ((ar[x]->icon) || d->default_icon) {
1397                 ap_rvputs(r, "<IMG SRC=\"",
1398                           ap_escape_html(scratch,
1399                                          ar[x]->icon ? ar[x]->icon
1400                                                      : d->default_icon),
1401                           "\" ALT=\"[", (ar[x]->alt ? ar[x]->alt : "   "),
1402                           "]\"", NULL);
1403                 if (d->icon_width && d->icon_height) {
1404                     ap_rprintf(r, " HEIGHT=\"%d\" WIDTH=\"%d\"",
1405                                d->icon_height, d->icon_width);
1406                 }
1407                 ap_rputs(">", r);
1408             }
1409             if (autoindex_opts & ICONS_ARE_LINKS) {
1410                 ap_rputs("</A>", r);
1411             }
1412
1413             nwidth = strlen(t2);
1414             if (nwidth > name_width) {
1415               memcpy(name_scratch, t2, name_width - 3);
1416               name_scratch[name_width - 3] = '.';
1417               name_scratch[name_width - 2] = '.';
1418               name_scratch[name_width - 1] = '>';
1419               name_scratch[name_width] = 0;
1420               t2 = name_scratch;
1421               nwidth = name_width;
1422             }
1423             ap_rvputs(r, " <A HREF=\"", anchor, "\">",
1424               ap_escape_html(scratch, t2), "</A>", pad_scratch + nwidth,
1425               NULL);
1426             /*
1427              * The blank before the storm.. er, before the next field.
1428              */
1429             ap_rputs(" ", r);
1430             if (!(autoindex_opts & SUPPRESS_LAST_MOD)) {
1431                 if (ar[x]->lm != -1) {
1432                     char time_str[MAX_STRING_LEN];
1433                     apr_exploded_time_t ts;
1434                     apr_explode_localtime(&ts, ar[x]->lm);
1435                     apr_strftime(time_str, &rv, MAX_STRING_LEN, 
1436                                 "%d-%b-%Y %H:%M  ", &ts);
1437                     ap_rputs(time_str, r);
1438                 }
1439                 else {
1440                     /*Length="22-Feb-1998 23:42  " (see 4 lines above) */
1441                     ap_rputs("                   ", r);
1442                 }
1443             }
1444             if (!(autoindex_opts & SUPPRESS_SIZE)) {
1445                 ap_send_size(ar[x]->size, r);
1446                 ap_rputs("  ", r);
1447             }
1448             if (!(autoindex_opts & SUPPRESS_DESC)) {
1449                 if (ar[x]->desc) {
1450                     ap_rputs(terminate_description(d, ar[x]->desc,
1451                                                    autoindex_opts), r);
1452                 }
1453             }
1454         }
1455         else {
1456             ap_rvputs(r, "<LI><A HREF=\"", anchor, "\"> ", t2,
1457                       "</A>", NULL);
1458         }
1459         ap_rputc('\n', r);
1460     }
1461     if (autoindex_opts & FANCY_INDEXING) {
1462         ap_rputs("</PRE>", r);
1463     }
1464     else {
1465         ap_rputs("</UL>", r);
1466     }
1467 }
1468
1469 /*
1470  * Compare two file entries according to the sort criteria.  The return
1471  * is essentially a signum function value.
1472  */
1473
1474 static int dsortf(struct ent **e1, struct ent **e2)
1475 {
1476     struct ent *c1;
1477     struct ent *c2;
1478     int result = 0;
1479
1480     /*
1481      * First, see if either of the entries is for the parent directory.
1482      * If so, that *always* sorts lower than anything else.
1483      */
1484     if (is_parent((*e1)->name)) {
1485         return -1;
1486     }
1487     if (is_parent((*e2)->name)) {
1488         return 1;
1489     }
1490     /*
1491      * All of our comparisons will be of the c1 entry against the c2 one,
1492      * so assign them appropriately to take care of the ordering.
1493      */
1494     if ((*e1)->ascending) {
1495         c1 = *e1;
1496         c2 = *e2;
1497     }
1498     else {
1499         c1 = *e2;
1500         c2 = *e1;
1501     }
1502
1503     switch (c1->key) {
1504     case K_LAST_MOD:
1505         if (c1->lm > c2->lm) {
1506             return 1;
1507         }
1508         else if (c1->lm < c2->lm) {
1509             return -1;
1510         }
1511         break;
1512     case K_SIZE:
1513         if (c1->size > c2->size) {
1514             return 1;
1515         }
1516         else if (c1->size < c2->size) {
1517             return -1;
1518         }
1519         break;
1520     case K_DESC:
1521         if (c1->version_sort)
1522             result = apr_strnatcmp(c1->desc ? c1->desc : "", c2->desc ? c2->desc : "");
1523         else
1524             result = strcmp(c1->desc ? c1->desc : "", c2->desc ? c2->desc : "");
1525         if (result) {
1526             return result;
1527         }
1528         break;
1529     }
1530     if (c1->version_sort)
1531         return apr_strnatcmp(c1->name, c2->name);
1532     else
1533         return strcmp(c1->name, c2->name);
1534 }
1535
1536
1537 static int index_directory(request_rec *r,
1538                            autoindex_config_rec *autoindex_conf)
1539 {
1540     char *title_name = ap_escape_html(r->pool, r->uri);
1541     char *title_endp;
1542     char *name = r->filename;
1543     apr_finfo_t dirent;
1544     apr_dir_t *thedir;
1545     apr_status_t status;
1546     int num_ent = 0, x;
1547     struct ent *head, *p;
1548     struct ent **ar = NULL;
1549     const char *qstring;
1550     int autoindex_opts = autoindex_conf->opts;
1551     char keyid;
1552     char direction;
1553
1554     if ((status = apr_dir_open(&thedir, name, r->pool)) != APR_SUCCESS) {
1555         ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
1556                     "Can't open directory for index: %s", r->filename);
1557         return HTTP_FORBIDDEN;
1558     }
1559
1560 #if APR_HAS_UNICODE_FS 
1561     r->content_type = "text/html;charset=utf-8";
1562 #else
1563     r->content_type = "text/html";
1564 #endif
1565     ap_update_mtime(r, r->finfo.mtime);
1566     ap_set_last_modified(r);
1567     ap_set_etag(r);
1568
1569     ap_send_http_header(r);
1570
1571     if (r->header_only) {
1572         apr_dir_close(thedir);
1573         return 0;
1574     }
1575
1576     /* Spew HTML preamble */
1577
1578     title_endp = title_name + strlen(title_name) - 1;
1579
1580     while (title_endp > title_name && *title_endp == '/') {
1581         *title_endp-- = '\0';
1582     }
1583
1584     emit_head(r, find_header(autoindex_conf, r),
1585               autoindex_opts & SUPPRESS_PREAMBLE, title_name);
1586
1587     /*
1588      * Figure out what sort of indexing (if any) we're supposed to use.
1589      *
1590      * If no QUERY_STRING was specified or column sorting has been
1591      * explicitly disabled, we use the default specified by the
1592      * IndexOrderDefault directive (if there is one); otherwise,
1593      * we fall back to ascending by name.
1594      */
1595     qstring = r->args;
1596     if ((autoindex_opts & SUPPRESS_COLSORT)
1597         || ((qstring == NULL) || (*qstring == '\0'))) {
1598         qstring = autoindex_conf->default_order;
1599     }
1600     /*
1601      * If there is no specific ordering defined for this directory,
1602      * default to ascending by filename.
1603      */
1604     if ((qstring == NULL) || (*qstring == '\0')) {
1605         keyid = K_NAME;
1606         direction = D_ASCENDING;
1607     }
1608     else {
1609         keyid = *qstring;
1610         ap_getword(r->pool, &qstring, '=');
1611         if (qstring != '\0') {
1612             direction = *qstring;
1613         }
1614         else {
1615             direction = D_ASCENDING;
1616         }
1617     }
1618
1619     /* 
1620      * Since we don't know how many dir. entries there are, put them into a 
1621      * linked list and then arrayificate them so qsort can use them. 
1622      */
1623     head = NULL;
1624     while (apr_dir_read(&dirent, APR_FINFO_DIRENT, thedir) == APR_SUCCESS) {
1625         p = make_autoindex_entry(dirent.name, autoindex_opts,
1626                                  autoindex_conf, r, keyid, direction);
1627         if (p != NULL) {
1628             p->next = head;
1629             head = p;
1630             num_ent++;
1631         }
1632     }
1633     if (num_ent > 0) {
1634         ar = (struct ent **) apr_palloc(r->pool,
1635                                        num_ent * sizeof(struct ent *));
1636         p = head;
1637         x = 0;
1638         while (p) {
1639             ar[x++] = p;
1640             p = p->next;
1641         }
1642
1643         qsort((void *) ar, num_ent, sizeof(struct ent *),
1644               (int (*)(const void *, const void *)) dsortf);
1645     }
1646     output_directories(ar, num_ent, autoindex_conf, r, autoindex_opts, keyid,
1647                        direction);
1648     apr_dir_close(thedir);
1649
1650     if (autoindex_opts & FANCY_INDEXING) {
1651         ap_rputs("<HR>\n", r);
1652     }
1653     emit_tail(r, find_readme(autoindex_conf, r),
1654               autoindex_opts & SUPPRESS_PREAMBLE);
1655
1656     return 0;
1657 }
1658
1659 /* The formal handler... */
1660
1661 static int handle_autoindex(request_rec *r)
1662 {
1663     autoindex_config_rec *d;
1664     int allow_opts;
1665
1666     if(strcmp(r->handler,DIR_MAGIC_TYPE))
1667         return DECLINED;
1668
1669     allow_opts = ap_allow_options(r);
1670
1671     d = (autoindex_config_rec *) ap_get_module_config(r->per_dir_config,
1672                                                       &autoindex_module);
1673
1674     r->allowed |= (1 << M_GET);
1675     if (r->method_number != M_GET) {
1676         return DECLINED;
1677     }
1678
1679     /* OK, nothing easy.  Trot out the heavy artillery... */
1680
1681     if (allow_opts & OPT_INDEXES) {
1682         /* KLUDGE --- make the sub_req lookups happen in the right directory.
1683          * Fixing this in the sub_req_lookup functions themselves is difficult,
1684          * and would probably break virtual includes...
1685          */
1686
1687         if (r->filename[strlen(r->filename) - 1] != '/') {
1688             r->filename = apr_pstrcat(r->pool, r->filename, "/", NULL);
1689         }
1690         return index_directory(r, d);
1691     }
1692     else {
1693         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1694                      "Directory index forbidden by rule: %s", r->filename);
1695         return HTTP_FORBIDDEN;
1696     }
1697 }
1698
1699 static void register_hooks(apr_pool_t *p)
1700 {
1701     ap_hook_handler(handle_autoindex,NULL,NULL,APR_HOOK_MIDDLE);
1702 }
1703
1704 module AP_MODULE_DECLARE_DATA autoindex_module =
1705 {
1706     STANDARD20_MODULE_STUFF,
1707     create_autoindex_config,    /* dir config creater */
1708     merge_autoindex_configs,    /* dir merger --- default is to override */
1709     NULL,                       /* server config */
1710     NULL,                       /* merge server config */
1711     autoindex_cmds,             /* command apr_table_t */
1712     register_hooks              /* register hooks */
1713 };