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