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