1 /* ====================================================================
2 * Copyright (c) 1995-1999 The Apache Group. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in
13 * the documentation and/or other materials provided with the
16 * 3. All advertising materials mentioning features or use of this
17 * software must display the following acknowledgment:
18 * "This product includes software developed by the Apache Group
19 * for use in the Apache HTTP server project (http://www.apache.org/)."
21 * 4. The names "Apache Server" and "Apache Group" must not be used to
22 * endorse or promote products derived from this software without
23 * prior written permission. For written permission, please contact
26 * 5. Products derived from this software may not be called "Apache"
27 * nor may "Apache" appear in their names without prior written
28 * permission of the Apache Group.
30 * 6. Redistributions of any form whatsoever must retain the following
32 * "This product includes software developed by the Apache Group
33 * for use in the Apache HTTP server project (http://www.apache.org/)."
35 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
36 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
37 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
38 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
41 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
42 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
43 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
44 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
46 * OF THE POSSIBILITY OF SUCH DAMAGE.
47 * ====================================================================
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Group and was originally based
51 * on public domain software written at the National Center for
52 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
53 * For more information on the Apache Group and the Apache HTTP server
54 * project, please see <http://www.apache.org/>.
59 * mod_autoindex.c: Handles the on-the-fly html index generation
64 * Adapted to Apache by rst.
68 #include "http_config.h"
69 #include "http_core.h"
70 #include "http_request.h"
71 #include "http_protocol.h"
73 #include "http_main.h"
74 #include "util_script.h"
75 #include "apr_fnmatch.h"
77 module MODULE_VAR_EXPORT autoindex_module;
79 /****************************************************************
81 * Handling configuration directives...
86 #define FRONT_MATTER 1
89 #define FANCY_INDEXING 1 /* Indexing options */
90 #define ICONS_ARE_LINKS 2
91 #define SCAN_HTML_TITLES 4
92 #define SUPPRESS_LAST_MOD 8
93 #define SUPPRESS_SIZE 16
94 #define SUPPRESS_DESC 32
95 #define SUPPRESS_PREAMBLE 64
96 #define SUPPRESS_COLSORT 128
97 #define NO_OPTIONS 256
107 * Define keys for sorting.
109 #define K_NAME 'N' /* Sort by file name (default) */
110 #define K_LAST_MOD 'M' /* Last modification date */
111 #define K_SIZE 'S' /* Size (absolute, not as displayed) */
112 #define K_DESC 'D' /* Description */
114 #define D_ASCENDING 'A'
115 #define D_DESCENDING 'D'
118 * These are the dimensions of the default icons supplied with Apache.
120 #define DEFAULT_ICON_WIDTH 20
121 #define DEFAULT_ICON_HEIGHT 22
124 * Other default dimensions.
126 #define DEFAULT_NAME_WIDTH 23
135 typedef struct ai_desc_t {
142 typedef struct autoindex_config_struct {
146 int incremented_opts;
147 int decremented_opts;
154 ap_array_header_t *icon_list;
155 ap_array_header_t *alt_list;
156 ap_array_header_t *desc_list;
157 ap_array_header_t *ign_list;
158 ap_array_header_t *hdr_list;
159 ap_array_header_t *rdme_list;
161 } autoindex_config_rec;
163 static char c_by_encoding, c_by_type, c_by_path;
165 #define BY_ENCODING &c_by_encoding
166 #define BY_TYPE &c_by_type
167 #define BY_PATH &c_by_path
170 * Return true if the specified string refers to the parent directory (i.e.,
171 * matches ".." or "../"). Hopefully this one call is significantly less
172 * expensive than multiple strcmp() calls.
174 static ap_inline int is_parent(const char *name)
177 * Now, IFF the first two bytes are dots, and the third byte is either
178 * EOS (\0) or a slash followed by EOS, we have a match.
180 if (((name[0] == '.') && (name[1] == '.'))
181 && ((name[2] == '\0')
182 || ((name[2] == '/') && (name[3] == '\0')))) {
189 * This routine puts the standard HTML header at the top of the index page.
190 * We include the DOCTYPE because we may be using features therefrom (i.e.,
191 * HEIGHT and WIDTH attributes on the icons if we're FancyIndexing).
193 static void emit_preamble(request_rec *r, char *title)
195 ap_rvputs(r, DOCTYPE_HTML_3_2,
196 "<HTML>\n <HEAD>\n <TITLE>Index of ", title,
197 "</TITLE>\n </HEAD>\n <BODY>\n", NULL);
200 static void push_item(ap_array_header_t *arr, char *type, char *to, char *path,
203 struct item *p = (struct item *) ap_push_array(arr);
213 p->data = data ? ap_pstrdup(arr->cont, data) : NULL;
214 p->apply_path = ap_pstrcat(arr->cont, path, "*", NULL);
216 if ((type == BY_PATH) && (!ap_is_matchexp(to))) {
217 p->apply_to = ap_pstrcat(arr->cont, "*", to, NULL);
220 p->apply_to = ap_pstrdup(arr->cont, to);
227 static const char *add_alt(cmd_parms *cmd, void *d, char *alt, char *to)
229 if (cmd->info == BY_PATH) {
230 if (!strcmp(to, "**DIRECTORY**")) {
231 to = "^^DIRECTORY^^";
234 if (cmd->info == BY_ENCODING) {
238 push_item(((autoindex_config_rec *) d)->alt_list, cmd->info, to,
243 static const char *add_icon(cmd_parms *cmd, void *d, char *icon, char *to)
245 char *iconbak = ap_pstrdup(cmd->pool, icon);
247 if (icon[0] == '(') {
249 char *cl = strchr(iconbak, ')');
252 return "missing closing paren";
254 alt = ap_getword_nc(cmd->pool, &iconbak, ',');
255 *cl = '\0'; /* Lose closing paren */
256 add_alt(cmd, d, &alt[1], to);
258 if (cmd->info == BY_PATH) {
259 if (!strcmp(to, "**DIRECTORY**")) {
260 to = "^^DIRECTORY^^";
263 if (cmd->info == BY_ENCODING) {
267 push_item(((autoindex_config_rec *) d)->icon_list, cmd->info, to,
273 * Add description text for a filename pattern. If the pattern has
274 * wildcards already (or we need to add them), add leading and
275 * trailing wildcards to it to ensure substring processing. If the
276 * pattern contains a '/' anywhere, force wildcard matching mode,
277 * add a slash to the prefix so that "bar/bletch" won't be matched
278 * by "foobar/bletch", and make a note that there's a delimiter;
279 * the matching routine simplifies to just the actual filename
280 * whenever it can. This allows definitions in parent directories
281 * to be made for files in subordinate ones using relative paths.
285 * Absent a strcasestr() function, we have to force wildcards on
286 * systems for which "AAA" and "aaa" mean the same file.
288 #ifdef CASE_BLIND_FILESYSTEM
289 #define WILDCARDS_REQUIRED 1
291 #define WILDCARDS_REQUIRED 0
294 static const char *add_desc(cmd_parms *cmd, void *d, char *desc, char *to)
296 autoindex_config_rec *dcfg = (autoindex_config_rec *) d;
297 ai_desc_t *desc_entry;
300 desc_entry = (ai_desc_t *) ap_push_array(dcfg->desc_list);
301 desc_entry->full_path = (strchr(to, '/') == NULL) ? 0 : 1;
302 desc_entry->wildcards = (WILDCARDS_REQUIRED
303 || desc_entry->full_path
304 || ap_is_fnmatch(to));
305 if (desc_entry->wildcards) {
306 prefix = desc_entry->full_path ? "*/" : "*";
307 desc_entry->pattern = ap_pstrcat(dcfg->desc_list->cont,
308 prefix, to, "*", NULL);
311 desc_entry->pattern = ap_pstrdup(dcfg->desc_list->cont, to);
313 desc_entry->description = ap_pstrdup(dcfg->desc_list->cont, desc);
317 static const char *add_ignore(cmd_parms *cmd, void *d, char *ext)
319 push_item(((autoindex_config_rec *) d)->ign_list, 0, ext, cmd->path, NULL);
323 static const char *add_header(cmd_parms *cmd, void *d, char *name)
325 push_item(((autoindex_config_rec *) d)->hdr_list, 0, NULL, cmd->path,
330 static const char *add_readme(cmd_parms *cmd, void *d, char *name)
332 push_item(((autoindex_config_rec *) d)->rdme_list, 0, NULL, cmd->path,
337 /* A legacy directive, FancyIndexing is superseded by the IndexOptions
338 * keyword. But for compatibility..
340 static const char *fancy_indexing(cmd_parms *cmd, void *d, int arg)
344 autoindex_config_rec *cfg;
346 cfg = (autoindex_config_rec *) d;
348 if (curopts & NO_OPTIONS) {
349 return "FancyIndexing directive conflicts with existing "
352 newopts = (arg ? (curopts | FANCY_INDEXING) : (curopts & ~FANCY_INDEXING));
357 static const char *add_opts(cmd_parms *cmd, void *d, const char *optstr)
364 autoindex_config_rec *d_cfg = (autoindex_config_rec *) d;
367 opts_add = d_cfg->incremented_opts;
368 opts_remove = d_cfg->decremented_opts;
372 w = ap_getword_conf(cmd->pool, &optstr);
373 if ((*w == '+') || (*w == '-')) {
379 if (!strcasecmp(w, "FancyIndexing")) {
380 option = FANCY_INDEXING;
382 else if (!strcasecmp(w, "IconsAreLinks")) {
383 option = ICONS_ARE_LINKS;
385 else if (!strcasecmp(w, "ScanHTMLTitles")) {
386 option = SCAN_HTML_TITLES;
388 else if (!strcasecmp(w, "SuppressLastModified")) {
389 option = SUPPRESS_LAST_MOD;
391 else if (!strcasecmp(w, "SuppressSize")) {
392 option = SUPPRESS_SIZE;
394 else if (!strcasecmp(w, "SuppressDescription")) {
395 option = SUPPRESS_DESC;
397 else if (!strcasecmp(w, "SuppressHTMLPreamble")) {
398 option = SUPPRESS_PREAMBLE;
400 else if (!strcasecmp(w, "SuppressColumnSorting")) {
401 option = SUPPRESS_COLSORT;
403 else if (!strcasecmp(w, "None")) {
404 if (action != '\0') {
405 return "Cannot combine '+' or '-' with 'None' keyword";
411 else if (!strcasecmp(w, "IconWidth")) {
413 d_cfg->icon_width = DEFAULT_ICON_WIDTH;
416 d_cfg->icon_width = 0;
419 else if (!strncasecmp(w, "IconWidth=", 10)) {
421 return "Cannot combine '-' with IconWidth=n";
423 d_cfg->icon_width = atoi(&w[10]);
425 else if (!strcasecmp(w, "IconHeight")) {
427 d_cfg->icon_height = DEFAULT_ICON_HEIGHT;
430 d_cfg->icon_height = 0;
433 else if (!strncasecmp(w, "IconHeight=", 11)) {
435 return "Cannot combine '-' with IconHeight=n";
437 d_cfg->icon_height = atoi(&w[11]);
439 else if (!strcasecmp(w, "NameWidth")) {
441 return "NameWidth with no value may only appear as "
444 d_cfg->name_width = DEFAULT_NAME_WIDTH;
445 d_cfg->name_adjust = K_NOADJUST;
447 else if (!strncasecmp(w, "NameWidth=", 10)) {
449 return "Cannot combine '-' with NameWidth=n";
452 d_cfg->name_adjust = K_ADJUST;
455 int width = atoi(&w[10]);
458 return "NameWidth value must be greater than 5";
460 d_cfg->name_width = width;
461 d_cfg->name_adjust = K_NOADJUST;
465 return "Invalid directory indexing option";
467 if (action == '\0') {
472 else if (action == '+') {
474 opts_remove &= ~option;
477 opts_remove |= option;
481 if ((opts & NO_OPTIONS) && (opts & ~NO_OPTIONS)) {
482 return "Cannot combine other IndexOptions keywords with 'None'";
484 d_cfg->incremented_opts = opts_add;
485 d_cfg->decremented_opts = opts_remove;
490 static const char *set_default_order(cmd_parms *cmd, void *m, char *direction,
494 autoindex_config_rec *d_cfg = (autoindex_config_rec *) m;
496 ap_cpystrn(temp, "k=d", sizeof(temp));
497 if (!strcasecmp(direction, "Ascending")) {
498 temp[2] = D_ASCENDING;
500 else if (!strcasecmp(direction, "Descending")) {
501 temp[2] = D_DESCENDING;
504 return "First keyword must be 'Ascending' or 'Descending'";
507 if (!strcasecmp(key, "Name")) {
510 else if (!strcasecmp(key, "Date")) {
511 temp[0] = K_LAST_MOD;
513 else if (!strcasecmp(key, "Size")) {
516 else if (!strcasecmp(key, "Description")) {
520 return "Second keyword must be 'Name', 'Date', 'Size', or "
524 if (d_cfg->default_order == NULL) {
525 d_cfg->default_order = ap_palloc(cmd->pool, 4);
526 d_cfg->default_order[3] = '\0';
528 ap_cpystrn(d_cfg->default_order, temp, sizeof(temp));
532 #define DIR_CMD_PERMS OR_INDEXES
534 static const command_rec autoindex_cmds[] =
536 {"AddIcon", add_icon, BY_PATH, DIR_CMD_PERMS, ITERATE2,
537 "an icon URL followed by one or more filenames"},
538 {"AddIconByType", add_icon, BY_TYPE, DIR_CMD_PERMS, ITERATE2,
539 "an icon URL followed by one or more MIME types"},
540 {"AddIconByEncoding", add_icon, BY_ENCODING, DIR_CMD_PERMS, ITERATE2,
541 "an icon URL followed by one or more content encodings"},
542 {"AddAlt", add_alt, BY_PATH, DIR_CMD_PERMS, ITERATE2,
543 "alternate descriptive text followed by one or more filenames"},
544 {"AddAltByType", add_alt, BY_TYPE, DIR_CMD_PERMS, ITERATE2,
545 "alternate descriptive text followed by one or more MIME types"},
546 {"AddAltByEncoding", add_alt, BY_ENCODING, DIR_CMD_PERMS, ITERATE2,
547 "alternate descriptive text followed by one or more content encodings"},
548 {"IndexOptions", add_opts, NULL, DIR_CMD_PERMS, RAW_ARGS,
549 "one or more index options"},
550 {"IndexOrderDefault", set_default_order, NULL, DIR_CMD_PERMS, TAKE2,
551 "{Ascending,Descending} {Name,Size,Description,Date}"},
552 {"IndexIgnore", add_ignore, NULL, DIR_CMD_PERMS, ITERATE,
553 "one or more file extensions"},
554 {"AddDescription", add_desc, BY_PATH, DIR_CMD_PERMS, ITERATE2,
555 "Descriptive text followed by one or more filenames"},
556 {"HeaderName", add_header, NULL, DIR_CMD_PERMS, TAKE1, "a filename"},
557 {"ReadmeName", add_readme, NULL, DIR_CMD_PERMS, TAKE1, "a filename"},
558 {"FancyIndexing", fancy_indexing, NULL, DIR_CMD_PERMS, FLAG,
559 "Limited to 'on' or 'off' (superseded by IndexOptions FancyIndexing)"},
560 {"DefaultIcon", ap_set_string_slot,
561 (void *) XtOffsetOf(autoindex_config_rec, default_icon),
562 DIR_CMD_PERMS, TAKE1, "an icon URL"},
566 static void *create_autoindex_config(ap_context_t *p, char *dummy)
568 autoindex_config_rec *new =
569 (autoindex_config_rec *) ap_pcalloc(p, sizeof(autoindex_config_rec));
572 new->icon_height = 0;
573 new->name_width = DEFAULT_NAME_WIDTH;
574 new->name_adjust = K_UNSET;
575 new->icon_list = ap_make_array(p, 4, sizeof(struct item));
576 new->alt_list = ap_make_array(p, 4, sizeof(struct item));
577 new->desc_list = ap_make_array(p, 4, sizeof(ai_desc_t));
578 new->ign_list = ap_make_array(p, 4, sizeof(struct item));
579 new->hdr_list = ap_make_array(p, 4, sizeof(struct item));
580 new->rdme_list = ap_make_array(p, 4, sizeof(struct item));
582 new->incremented_opts = 0;
583 new->decremented_opts = 0;
584 new->default_order = NULL;
589 static void *merge_autoindex_configs(ap_context_t *p, void *basev, void *addv)
591 autoindex_config_rec *new;
592 autoindex_config_rec *base = (autoindex_config_rec *) basev;
593 autoindex_config_rec *add = (autoindex_config_rec *) addv;
595 new = (autoindex_config_rec *) ap_pcalloc(p, sizeof(autoindex_config_rec));
596 new->default_icon = add->default_icon ? add->default_icon
597 : base->default_icon;
598 new->icon_height = add->icon_height ? add->icon_height : base->icon_height;
599 new->icon_width = add->icon_width ? add->icon_width : base->icon_width;
601 new->alt_list = ap_append_arrays(p, add->alt_list, base->alt_list);
602 new->ign_list = ap_append_arrays(p, add->ign_list, base->ign_list);
603 new->hdr_list = ap_append_arrays(p, add->hdr_list, base->hdr_list);
604 new->desc_list = ap_append_arrays(p, add->desc_list, base->desc_list);
605 new->icon_list = ap_append_arrays(p, add->icon_list, base->icon_list);
606 new->rdme_list = ap_append_arrays(p, add->rdme_list, base->rdme_list);
607 if (add->opts & NO_OPTIONS) {
609 * If the current directory says 'no options' then we also
610 * clear any incremental mods from being inheritable further down.
612 new->opts = NO_OPTIONS;
613 new->incremented_opts = 0;
614 new->decremented_opts = 0;
618 * If there were any nonincremental options selected for
619 * this directory, they dominate and we don't inherit *anything.*
620 * Contrariwise, we *do* inherit if the only settings here are
623 if (add->opts == 0) {
624 new->incremented_opts = (base->incremented_opts
625 | add->incremented_opts)
626 & ~add->decremented_opts;
627 new->decremented_opts = (base->decremented_opts
628 | add->decremented_opts);
630 * We may have incremental settings, so make sure we don't
631 * inadvertently inherit an IndexOptions None from above.
633 new->opts = (base->opts & ~NO_OPTIONS);
637 * There are local nonincremental settings, which clear
638 * all inheritance from above. They *are* the new base settings.
640 new->opts = add->opts;;
643 * We're guaranteed that there'll be no overlap between
644 * the add-options and the remove-options.
646 new->opts |= new->incremented_opts;
647 new->opts &= ~new->decremented_opts;
650 * Inherit the NameWidth settings if there aren't any specific to
651 * the new location; otherwise we'll end up using the defaults set in the
652 * config-rec creation routine.
654 if (add->name_adjust == K_UNSET) {
655 new->name_width = base->name_width;
656 new->name_adjust = base->name_adjust;
659 new->name_width = add->name_width;
660 new->name_adjust = add->name_adjust;
663 new->default_order = (add->default_order != NULL)
664 ? add->default_order : base->default_order;
668 /****************************************************************
670 * Looking things up in config entries...
673 /* Structure used to hold entries when we're actually building an index */
687 static char *find_item(request_rec *r, ap_array_header_t *list, int path_only)
689 const char *content_type = r->content_type;
690 const char *content_encoding = r->content_encoding;
691 char *path = r->filename;
693 struct item *items = (struct item *) list->elts;
696 for (i = 0; i < list->nelts; ++i) {
697 struct item *p = &items[i];
699 /* Special cased for ^^DIRECTORY^^ and ^^BLANKICON^^ */
700 if ((path[0] == '^') || (!ap_strcmp_match(path, p->apply_path))) {
701 if (!*(p->apply_to)) {
704 else if (p->type == BY_PATH || path[0] == '^') {
705 if (!ap_strcmp_match(path, p->apply_to)) {
709 else if (!path_only) {
710 if (!content_encoding) {
711 if (p->type == BY_TYPE) {
713 && !ap_strcasecmp_match(content_type,
720 if (p->type == BY_ENCODING) {
721 if (!ap_strcasecmp_match(content_encoding,
733 #define find_icon(d,p,t) find_item(p,d->icon_list,t)
734 #define find_alt(d,p,t) find_item(p,d->alt_list,t)
735 #define find_header(d,p) find_item(p,d->hdr_list,0)
736 #define find_readme(d,p) find_item(p,d->rdme_list,0)
738 static char *find_default_icon(autoindex_config_rec *d, char *bogus_name)
742 /* Bleah. I tried to clean up find_item, and it lead to this bit
743 * of ugliness. Note that the fields initialized are precisely
744 * those that find_item looks at...
747 r.filename = bogus_name;
748 r.content_type = r.content_encoding = NULL;
750 return find_item(&r, d->icon_list, 1);
754 * Look through the list of pattern/description pairs and return the first one
755 * if any) that matches the filename in the request. If multiple patterns
756 * match, only the first one is used; since the order in the array is the
757 * same as the order in which directives were processed, earlier matching
758 * directives will dominate.
761 #ifdef CASE_BLIND_FILESYSTEM
762 #define MATCH_FLAGS FNM_CASE_BLIND
764 #define MATCH_FLAGS 0
767 static char *find_desc(autoindex_config_rec *dcfg, request_rec *r)
770 ai_desc_t *list = (ai_desc_t *) dcfg->desc_list->elts;
771 const char *filename_full = r->filename;
772 const char *filename_only;
773 const char *filename;
776 * If the filename includes a path, extract just the name itself
777 * for the simple matches.
779 if ((filename_only = strrchr(filename_full, '/')) == NULL) {
780 filename_only = filename_full;
785 for (i = 0; i < dcfg->desc_list->nelts; ++i) {
786 ai_desc_t *tuple = &list[i];
790 * Only use the full-path filename if the pattern contains '/'s.
792 filename = (tuple->full_path) ? filename_full : filename_only;
794 * Make the comparison using the cheapest method; only do
795 * wildcard checking if we must.
797 if (tuple->wildcards) {
798 found = (ap_fnmatch(tuple->pattern, filename, MATCH_FLAGS) == 0);
801 found = (strstr(filename, tuple->pattern) != NULL);
804 return tuple->description;
810 static int ignore_entry(autoindex_config_rec *d, char *path)
812 ap_array_header_t *list = d->ign_list;
813 struct item *items = (struct item *) list->elts;
817 if ((tt = strrchr(path, '/')) == NULL) {
824 for (i = 0; i < list->nelts; ++i) {
825 struct item *p = &items[i];
828 if ((ap = strrchr(p->apply_to, '/')) == NULL) {
835 #ifndef CASE_BLIND_FILESYSTEM
836 if (!ap_strcmp_match(path, p->apply_path)
837 && !ap_strcmp_match(tt, ap)) {
840 #else /* !CASE_BLIND_FILESYSTEM */
842 * On some platforms, the match must be case-blind. This is really
843 * a factor of the filesystem involved, but we can't detect that
844 * reliably - so we have to granularise at the OS level.
846 if (!ap_strcasecmp_match(path, p->apply_path)
847 && !ap_strcasecmp_match(tt, ap)) {
850 #endif /* !CASE_BLIND_FILESYSTEM */
855 /*****************************************************************
857 * Actually generating output
861 * Elements of the emitted document:
863 * Emitted unless SUPPRESS_PREAMBLE is set AND ap_run_sub_req
864 * succeeds for the (content_type == text/html) header file.
866 * Emitted if found (and able).
868 * Emitted if a header file is NOT emitted.
872 * Emitted if FANCY_INDEXING is set.
874 * Emitted if found (and able).
876 * Emitted if ServerSignature is not Off AND a readme file
879 * Emitted unless SUPPRESS_PREAMBLE is set AND ap_run_sub_req
880 * succeeds for the (content_type == text/html) readme file.
885 * emit a plain text file
887 static void do_emit_plain(request_rec *r, ap_file_t *f)
889 char buf[IOBUFSIZE + 1];
894 ap_rputs("<PRE>\n", r);
897 n = sizeof(char) * IOBUFSIZE;
898 stat = ap_read(f, buf, &n);
900 while (stat != APR_SUCCESS && stat == EINTR);
901 if (n == -1 || n == 0) {
907 for (i = c; i < n; i++) {
908 if (buf[i] == '<' || buf[i] == '>' || buf[i] == '&') {
914 ap_rputs(&buf[c], r);
918 else if (ch == '>') {
921 else if (ch == '&') {
922 ap_rputs("&", r);
927 ap_rputs("</PRE>\n", r);
931 * Handle the preamble through the H1 tag line, inclusive. Locate
932 * the file with a subrequests. Process text/html documents by actually
933 * running the subrequest; text/xxx documents get copied verbatim,
934 * and any other content type is ignored. This means that a non-text
935 * document (such as HEADER.gif) might get multiviewed as the result
936 * instead of a text document, meaning nothing will be displayed, but
939 static void emit_head(request_rec *r, char *header_fname, int suppress_amble,
943 request_rec *rr = NULL;
948 * If there's a header file, send a subrequest to look for it. If it's
949 * found and a text file, handle it -- otherwise fall through and
950 * pretend there's nothing there.
952 if ((header_fname != NULL)
953 && (rr = ap_sub_req_lookup_uri(header_fname, r))
954 && (rr->status == HTTP_OK)
955 && (rr->filename != NULL)
956 && S_ISREG(rr->finfo.st_mode)) {
958 * Check for the two specific cases we allow: text/html and
959 * text/anything-else. The former is allowed to be processed for
962 if (rr->content_type != NULL) {
963 if (!strcasecmp(ap_field_noparam(r->pool, rr->content_type),
965 /* Hope everything will work... */
969 if (! suppress_amble) {
970 emit_preamble(r, title);
973 * If there's a problem running the subrequest, display the
974 * preamble if we didn't do it before -- the header file
975 * didn't get displayed.
977 if (ap_run_sub_req(rr) != OK) {
979 emit_amble = suppress_amble;
983 else if (!strncasecmp("text/", rr->content_type, 5)) {
985 * If we can open the file, prefix it with the preamble
986 * regardless; since we'll be sending a <PRE> block around
987 * the file's contents, any HTML header it had won't end up
990 if (ap_open(&f, rr->filename, APR_READ | APR_BUFFERED,
991 APR_OS_DEFAULT, r->pool) == APR_SUCCESS) {
992 emit_preamble(r, title);
1003 emit_preamble(r, title);
1006 ap_rvputs(r, "<H1>Index of ", title, "</H1>\n", NULL);
1009 ap_destroy_sub_req(rr);
1015 * Handle the Readme file through the postamble, inclusive. Locate
1016 * the file with a subrequests. Process text/html documents by actually
1017 * running the subrequest; text/xxx documents get copied verbatim,
1018 * and any other content type is ignored. This means that a non-text
1019 * document (such as FOOTER.gif) might get multiviewed as the result
1020 * instead of a text document, meaning nothing will be displayed, but
1023 static void emit_tail(request_rec *r, char *readme_fname, int suppress_amble)
1025 ap_file_t *f = NULL;
1026 request_rec *rr = NULL;
1027 int suppress_post = 0;
1028 int suppress_sig = 0;
1031 * If there's a readme file, send a subrequest to look for it. If it's
1032 * found and a text file, handle it -- otherwise fall through and
1033 * pretend there's nothing there.
1035 if ((readme_fname != NULL)
1036 && (rr = ap_sub_req_lookup_uri(readme_fname, r))
1037 && (rr->status == HTTP_OK)
1038 && (rr->filename != NULL)
1039 && S_ISREG(rr->finfo.st_mode)) {
1041 * Check for the two specific cases we allow: text/html and
1042 * text/anything-else. The former is allowed to be processed for
1045 if (rr->content_type != NULL) {
1046 if (!strcasecmp(ap_field_noparam(r->pool, rr->content_type),
1048 if (ap_run_sub_req(rr) == OK) {
1051 suppress_post = suppress_amble;
1054 else if (!strncasecmp("text/", rr->content_type, 5)) {
1056 * If we can open the file, suppress the signature.
1058 if (ap_open(&f, rr->filename, APR_READ | APR_BUFFERED,
1059 APR_OS_DEFAULT, r->pool) == APR_SUCCESS) {
1060 do_emit_plain(r, f);
1068 if (!suppress_sig) {
1069 ap_rputs(ap_psignature("", r), r);
1071 if (!suppress_post) {
1072 ap_rputs("</BODY></HTML>\n", r);
1075 ap_destroy_sub_req(rr);
1080 static char *find_title(request_rec *r)
1082 char titlebuf[MAX_STRING_LEN], *find = "<TITLE>";
1083 ap_file_t *thefile = NULL;
1087 if (r->status != HTTP_OK) {
1090 if ((r->content_type != NULL)
1091 && (!strcasecmp(ap_field_noparam(r->pool, r->content_type),
1093 || !strcmp(r->content_type, INCLUDES_MAGIC_TYPE))
1094 && !r->content_encoding) {
1095 if (ap_open(&thefile, r->filename, APR_READ | APR_BUFFERED,
1096 APR_OS_DEFAULT, r->pool) != APR_SUCCESS) {
1099 n = sizeof(char) * (MAX_STRING_LEN - 1);
1100 ap_read(thefile, titlebuf, &n);
1106 for (x = 0, p = 0; titlebuf[x]; x++) {
1107 if (ap_toupper(titlebuf[x]) == find[p]) {
1109 if ((p = ap_ind(&titlebuf[++x], '<')) != -1) {
1110 titlebuf[x + p] = '\0';
1112 /* Scan for line breaks for Tanmoy's secretary */
1113 for (y = x; titlebuf[y]; y++) {
1114 if ((titlebuf[y] == CR) || (titlebuf[y] == LF)) {
1124 return ap_pstrdup(r->pool, &titlebuf[x]);
1136 static struct ent *make_autoindex_entry(char *name, int autoindex_opts,
1137 autoindex_config_rec *d,
1138 request_rec *r, char keyid,
1143 if ((name[0] == '.') && (!name[1])) {
1147 if (ignore_entry(d, ap_make_full_path(r->pool, r->filename, name))) {
1151 p = (struct ent *) ap_pcalloc(r->pool, sizeof(struct ent));
1152 p->name = ap_pstrdup(r->pool, name);
1158 p->key = ap_toupper(keyid);
1159 p->ascending = (ap_toupper(direction) == D_ASCENDING);
1161 if (autoindex_opts & FANCY_INDEXING) {
1162 request_rec *rr = ap_sub_req_lookup_file(name, r);
1164 if (rr->finfo.st_mode != 0) {
1165 p->lm = rr->finfo.st_mtime;
1166 if (S_ISDIR(rr->finfo.st_mode)) {
1167 if (!(p->icon = find_icon(d, rr, 1))) {
1168 p->icon = find_default_icon(d, "^^DIRECTORY^^");
1170 if (!(p->alt = find_alt(d, rr, 1))) {
1174 p->name = ap_pstrcat(r->pool, name, "/", NULL);
1177 p->icon = find_icon(d, rr, 0);
1178 p->alt = find_alt(d, rr, 0);
1179 p->size = rr->finfo.st_size;
1183 p->desc = find_desc(d, rr);
1185 if ((!p->desc) && (autoindex_opts & SCAN_HTML_TITLES)) {
1186 p->desc = ap_pstrdup(r->pool, find_title(rr));
1189 ap_destroy_sub_req(rr);
1192 * We don't need to take any special action for the file size key. If
1193 * we did, it would go here.
1195 if (keyid == K_LAST_MOD) {
1203 static char *terminate_description(autoindex_config_rec *d, char *desc,
1209 if (autoindex_opts & SUPPRESS_LAST_MOD) {
1212 if (autoindex_opts & SUPPRESS_SIZE) {
1216 for (x = 0; desc[x] && (maxsize > 0 || desc[x]=='<'); x++) {
1217 if (desc[x] == '<') {
1218 while (desc[x] != '>') {
1226 else if (desc[x] == '&') {
1227 /* entities like ä count as one character */
1229 for ( ; desc[x] != ';'; ++x) {
1230 if (desc[x] == '\0') {
1240 if (!maxsize && desc[x] != '\0') {
1241 desc[x - 1] = '>'; /* Grump. */
1242 desc[x] = '\0'; /* Double Grump! */
1248 * Emit the anchor for the specified field. If a field is the key for the
1249 * current request, the link changes its meaning to reverse the order when
1250 * selected again. Non-active fields always start in ascending order.
1252 static void emit_link(request_rec *r, char *anchor, char fname, char curkey,
1253 char curdirection, int nosort)
1263 reverse = ((curkey == fname) && (curdirection == D_ASCENDING));
1264 qvalue[3] = reverse ? D_DESCENDING : D_ASCENDING;
1265 ap_rvputs(r, "<A HREF=\"", qvalue, "\">", anchor, "</A>", NULL);
1268 ap_rputs(anchor, r);
1272 static void output_directories(struct ent **ar, int n,
1273 autoindex_config_rec *d, request_rec *r,
1274 int autoindex_opts, char keyid, char direction)
1277 char *name = r->uri;
1279 int static_columns = (autoindex_opts & SUPPRESS_COLSORT);
1280 ap_context_t *scratch;
1285 ap_create_context(&scratch, r->pool);
1286 if (name[0] == '\0') {
1290 name_width = d->name_width;
1291 if (d->name_adjust == K_ADJUST) {
1292 for (x = 0; x < n; x++) {
1293 int t = strlen(ar[x]->name);
1294 if (t > name_width) {
1299 name_scratch = ap_palloc(r->pool, name_width + 1);
1300 pad_scratch = ap_palloc(r->pool, name_width + 1);
1301 memset(pad_scratch, ' ', name_width);
1302 pad_scratch[name_width] = '\0';
1304 if (autoindex_opts & FANCY_INDEXING) {
1305 ap_rputs("<PRE>", r);
1306 if ((tp = find_default_icon(d, "^^BLANKICON^^"))) {
1307 ap_rvputs(r, "<IMG SRC=\"", ap_escape_html(scratch, tp),
1308 "\" ALT=\" \"", NULL);
1309 if (d->icon_width && d->icon_height) {
1313 " HEIGHT=\"%d\" WIDTH=\"%d\"",
1320 emit_link(r, "Name", K_NAME, keyid, direction, static_columns);
1321 ap_rputs(pad_scratch + 4, r);
1323 * Emit the guaranteed-at-least-one-space-between-columns byte.
1326 if (!(autoindex_opts & SUPPRESS_LAST_MOD)) {
1327 emit_link(r, "Last modified", K_LAST_MOD, keyid, direction,
1331 if (!(autoindex_opts & SUPPRESS_SIZE)) {
1332 emit_link(r, "Size", K_SIZE, keyid, direction, static_columns);
1335 if (!(autoindex_opts & SUPPRESS_DESC)) {
1336 emit_link(r, "Description", K_DESC, keyid, direction,
1339 ap_rputs("\n<HR>\n", r);
1342 ap_rputs("<UL>", r);
1345 for (x = 0; x < n; x++) {
1346 char *anchor, *t, *t2;
1349 ap_clear_pool(scratch);
1351 if (is_parent(ar[x]->name)) {
1352 t = ap_make_full_path(scratch, name, "../");
1357 t2 = "Parent Directory";
1358 anchor = ap_escape_html(scratch, ap_os_escape_path(scratch, t, 0));
1363 anchor = ap_escape_html(scratch, ap_os_escape_path(scratch, t, 0));
1366 if (autoindex_opts & FANCY_INDEXING) {
1367 if (autoindex_opts & ICONS_ARE_LINKS) {
1368 ap_rvputs(r, "<A HREF=\"", anchor, "\">", NULL);
1370 if ((ar[x]->icon) || d->default_icon) {
1371 ap_rvputs(r, "<IMG SRC=\"",
1372 ap_escape_html(scratch,
1373 ar[x]->icon ? ar[x]->icon
1375 "\" ALT=\"[", (ar[x]->alt ? ar[x]->alt : " "),
1377 if (d->icon_width && d->icon_height) {
1378 ap_rprintf(r, " HEIGHT=\"%d\" WIDTH=\"%d\"",
1379 d->icon_height, d->icon_width);
1383 if (autoindex_opts & ICONS_ARE_LINKS) {
1384 ap_rputs("</A>", r);
1387 nwidth = strlen(t2);
1388 if (nwidth > name_width) {
1389 memcpy(name_scratch, t2, name_width - 3);
1390 name_scratch[name_width - 3] = '.';
1391 name_scratch[name_width - 2] = '.';
1392 name_scratch[name_width - 1] = '>';
1393 name_scratch[name_width] = 0;
1395 nwidth = name_width;
1397 ap_rvputs(r, " <A HREF=\"", anchor, "\">",
1398 ap_escape_html(scratch, t2), "</A>", pad_scratch + nwidth,
1401 * The blank before the storm.. er, before the next field.
1404 if (!(autoindex_opts & SUPPRESS_LAST_MOD)) {
1405 if (ar[x]->lm != -1) {
1406 char time_str[MAX_STRING_LEN];
1407 struct tm *ts = localtime(&ar[x]->lm);
1408 strftime(time_str, MAX_STRING_LEN, "%d-%b-%Y %H:%M ", ts);
1409 ap_rputs(time_str, r);
1412 /*Length="22-Feb-1998 23:42 " (see 4 lines above) */
1416 if (!(autoindex_opts & SUPPRESS_SIZE)) {
1417 ap_send_size(ar[x]->size, r);
1420 if (!(autoindex_opts & SUPPRESS_DESC)) {
1422 ap_rputs(terminate_description(d, ar[x]->desc,
1423 autoindex_opts), r);
1428 ap_rvputs(r, "<LI><A HREF=\"", anchor, "\"> ", t2,
1433 if (autoindex_opts & FANCY_INDEXING) {
1434 ap_rputs("</PRE>", r);
1437 ap_rputs("</UL>", r);
1442 * Compare two file entries according to the sort criteria. The return
1443 * is essentially a signum function value.
1446 static int dsortf(struct ent **e1, struct ent **e2)
1453 * First, see if either of the entries is for the parent directory.
1454 * If so, that *always* sorts lower than anything else.
1456 if (is_parent((*e1)->name)) {
1459 if (is_parent((*e2)->name)) {
1463 * All of our comparisons will be of the c1 entry against the c2 one,
1464 * so assign them appropriately to take care of the ordering.
1466 if ((*e1)->ascending) {
1476 if (c1->lm > c2->lm) {
1479 else if (c1->lm < c2->lm) {
1484 if (c1->size > c2->size) {
1487 else if (c1->size < c2->size) {
1492 result = strcmp(c1->desc ? c1->desc : "", c2->desc ? c2->desc : "");
1498 return strcmp(c1->name, c2->name);
1502 static int index_directory(request_rec *r,
1503 autoindex_config_rec *autoindex_conf)
1505 char *title_name = ap_escape_html(r->pool, r->uri);
1507 char *name = r->filename;
1512 struct ent *head, *p;
1513 struct ent **ar = NULL;
1514 const char *qstring;
1515 int autoindex_opts = autoindex_conf->opts;
1519 if ((status = ap_opendir(&d, name, r->pool)) != APR_SUCCESS) {
1520 ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r,
1521 "Can't open directory for index: %s", r->filename);
1522 return HTTP_FORBIDDEN;
1525 r->content_type = "text/html";
1527 ap_send_http_header(r);
1529 if (r->header_only) {
1534 /* Spew HTML preamble */
1536 title_endp = title_name + strlen(title_name) - 1;
1538 while (title_endp > title_name && *title_endp == '/') {
1539 *title_endp-- = '\0';
1542 emit_head(r, find_header(autoindex_conf, r),
1543 autoindex_opts & SUPPRESS_PREAMBLE, title_name);
1546 * Figure out what sort of indexing (if any) we're supposed to use.
1548 * If no QUERY_STRING was specified or column sorting has been
1549 * explicitly disabled, we use the default specified by the
1550 * IndexOrderDefault directive (if there is one); otherwise,
1551 * we fall back to ascending by name.
1554 if ((autoindex_opts & SUPPRESS_COLSORT)
1555 || ((qstring == NULL) || (*qstring == '\0'))) {
1556 qstring = autoindex_conf->default_order;
1559 * If there is no specific ordering defined for this directory,
1560 * default to ascending by filename.
1562 if ((qstring == NULL) || (*qstring == '\0')) {
1564 direction = D_ASCENDING;
1568 ap_getword(r->pool, &qstring, '=');
1569 if (qstring != '\0') {
1570 direction = *qstring;
1573 direction = D_ASCENDING;
1578 * Since we don't know how many dir. entries there are, put them into a
1579 * linked list and then arrayificate them so qsort can use them.
1582 while (ap_readdir(d) == APR_SUCCESS) {
1584 ap_get_dir_filename(&d_name, d);
1585 p = make_autoindex_entry(d_name, autoindex_opts,
1586 autoindex_conf, r, keyid, direction);
1594 ar = (struct ent **) ap_palloc(r->pool,
1595 num_ent * sizeof(struct ent *));
1603 qsort((void *) ar, num_ent, sizeof(struct ent *),
1604 (int (*)(const void *, const void *)) dsortf);
1606 output_directories(ar, num_ent, autoindex_conf, r, autoindex_opts, keyid,
1610 if (autoindex_opts & FANCY_INDEXING) {
1611 ap_rputs("<HR>\n", r);
1613 emit_tail(r, find_readme(autoindex_conf, r),
1614 autoindex_opts & SUPPRESS_PREAMBLE);
1619 /* The formal handler... */
1621 static int handle_autoindex(request_rec *r)
1623 autoindex_config_rec *d;
1624 int allow_opts = ap_allow_options(r);
1626 d = (autoindex_config_rec *) ap_get_module_config(r->per_dir_config,
1629 r->allowed |= (1 << M_GET);
1630 if (r->method_number != M_GET) {
1634 /* OK, nothing easy. Trot out the heavy artillery... */
1636 if (allow_opts & OPT_INDEXES) {
1637 /* KLUDGE --- make the sub_req lookups happen in the right directory.
1638 * Fixing this in the sub_req_lookup functions themselves is difficult,
1639 * and would probably break virtual includes...
1642 if (r->filename[strlen(r->filename) - 1] != '/') {
1643 r->filename = ap_pstrcat(r->pool, r->filename, "/", NULL);
1645 return index_directory(r, d);
1648 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1649 "Directory index forbidden by rule: %s", r->filename);
1650 return HTTP_FORBIDDEN;
1655 static const handler_rec autoindex_handlers[] =
1657 {DIR_MAGIC_TYPE, handle_autoindex},
1661 module MODULE_VAR_EXPORT autoindex_module =
1663 STANDARD20_MODULE_STUFF,
1664 create_autoindex_config, /* dir config creater */
1665 merge_autoindex_configs, /* dir merger --- default is to override */
1666 NULL, /* server config */
1667 NULL, /* merge server config */
1668 autoindex_cmds, /* command ap_table_t */
1669 autoindex_handlers, /* handlers */
1670 NULL /* register hooks */