/* ====================================================================
 * Copyright (c) 1995-1997 The Apache Group.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * 4. The names "Apache Server" and "Apache Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission.
 *
 * 5. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE APACHE GROUP OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Group and was originally based
 * on public domain software written at the National Center for
 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
 * For more information on the Apache Group and the Apache HTTP server
 * project, please see <http://www.apache.org/>.
 *
 */

/*
 * http_dir.c: Handles the on-the-fly html index generation
 * 
 * Rob McCool
 * 3/23/93
 * 
 * Adapted to Apache by rst.
 */

#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_request.h"
#include "http_protocol.h"
#include "http_log.h"
#include "http_main.h"
#include "util_script.h"

module dir_module;

/****************************************************************
 *
 * Handling configuration directives...
 */

#define FANCY_INDEXING 1	/* Indexing options */
#define ICONS_ARE_LINKS 2
#define SCAN_HTML_TITLES 4
#define SUPPRESS_LAST_MOD 8
#define SUPPRESS_SIZE 16
#define SUPPRESS_DESC 32

struct item {
    char *type;
    char *apply_to;
    char *apply_path;
    char *data;
};

typedef struct dir_config_struct {

    char *default_icon;
    char *index_names;
  
    array_header *icon_list, *alt_list, *desc_list, *ign_list;
    array_header *hdr_list, *rdme_list, *opts_list;
  
} dir_config_rec;

char c_by_encoding, c_by_type, c_by_path;

#define BY_ENCODING &c_by_encoding
#define BY_TYPE &c_by_type
#define BY_PATH &c_by_path

void push_item(array_header *arr, char *type, char *to, char *path, char *data)
{
    struct item *p = (struct item *)push_array(arr);

    if (!to) to = "";
    if (!path) path = "";
    
    p->type = type;
    p->data = data ? pstrdup(arr->pool, data): NULL;
    p->apply_path = pstrcat(arr->pool, path, "*", NULL);
    
    if((type == BY_PATH) && (!is_matchexp(to)))
        p->apply_to = pstrcat (arr->pool, "*", to, NULL);
    else if (to)
        p->apply_to = pstrdup (arr->pool, to);
    else
        p->apply_to = NULL;
}

const char *add_alt(cmd_parms *cmd, void *d, char *alt, char *to)
{
    if (cmd->info == BY_PATH)
        if(!strcmp(to,"**DIRECTORY**"))
            to = "^^DIRECTORY^^";

    push_item(((dir_config_rec *)d)->alt_list, cmd->info, to, cmd->path, alt);
    return NULL;
}

const char *add_icon(cmd_parms *cmd, void *d, char *icon, char *to)
{
    char *iconbak = pstrdup (cmd->pool, icon);

    if(icon[0] == '(') {
        char *alt = getword_nc (cmd->pool, &iconbak, ',');
        iconbak[strlen(iconbak) - 1] = '\0'; /* Lose closing paren */
        add_alt(cmd, d, &alt[1], to);
    }
    if(cmd->info == BY_PATH) 
        if(!strcmp(to,"**DIRECTORY**"))
            to = "^^DIRECTORY^^";

    push_item(((dir_config_rec *)d)->icon_list, cmd->info, to, cmd->path,
	      iconbak);
    return NULL;
}

const char *add_desc(cmd_parms *cmd, void *d, char *desc, char *to)
{
    push_item(((dir_config_rec *)d)->desc_list, cmd->info, to, cmd->path,desc);
    return NULL;
}

const char *add_ignore(cmd_parms *cmd, void *d, char *ext) {
    push_item(((dir_config_rec *)d)->ign_list, 0, ext, cmd->path, NULL);
    return NULL;
}

const char *add_header(cmd_parms *cmd, void *d, char *name) {
    if (strchr (name, '/')) {
	return "HeaderName cannot contain a /";
    }
    push_item(((dir_config_rec *)d)->hdr_list, 0, NULL, cmd->path, name);
    return NULL;
}

const char *add_readme(cmd_parms *cmd, void *d, char *name) {
    if (strchr (name, '/')) {
	return "ReadmeName cannot contain a /";
    }
    push_item(((dir_config_rec *)d)->rdme_list, 0, NULL, cmd->path, name);
    return NULL;
}


const char *add_opts_int(cmd_parms *cmd, void *d, int opts) {
    push_item(((dir_config_rec *)d)->opts_list, (char*)opts, NULL,
	      cmd->path, NULL);
    return NULL;
}

const char *fancy_indexing (cmd_parms *cmd, void *d, int arg)
{
    return add_opts_int (cmd, d, arg? FANCY_INDEXING : 0);
}

const char *add_opts(cmd_parms *cmd, void *d, const char *optstr) {
    char *w;
    int opts = 0;

    while(optstr[0]) {
        w = getword_conf(cmd->pool, &optstr);
        if(!strcasecmp(w,"FancyIndexing"))
            opts |= FANCY_INDEXING;
        else if(!strcasecmp(w,"IconsAreLinks"))
            opts |= ICONS_ARE_LINKS;
        else if(!strcasecmp(w,"ScanHTMLTitles"))
            opts |= SCAN_HTML_TITLES;
        else if(!strcasecmp(w,"SuppressLastModified"))
            opts |= SUPPRESS_LAST_MOD;
        else if(!strcasecmp(w,"SuppressSize"))
            opts |= SUPPRESS_SIZE;
        else if(!strcasecmp(w,"SuppressDescription"))
            opts |= SUPPRESS_DESC;
        else if(!strcasecmp(w,"None"))
            opts = 0;
	else
	    return "Invalid directory indexing option";
    }
    return add_opts_int(cmd, d, opts);
}

#define DIR_CMD_PERMS OR_INDEXES

command_rec dir_cmds[] = {
{ "AddIcon", add_icon, BY_PATH, DIR_CMD_PERMS, ITERATE2,
    "an icon URL followed by one or more filenames" },
{ "AddIconByType", add_icon, BY_TYPE, DIR_CMD_PERMS, ITERATE2,
    "an icon URL followed by one or more MIME types" },
{ "AddIconByEncoding", add_icon, BY_ENCODING, DIR_CMD_PERMS, ITERATE2,
    "an icon URL followed by one or more content encodings" },
{ "AddAlt", add_alt, BY_PATH, DIR_CMD_PERMS, ITERATE2,
    "alternate descriptive text followed by one or more filenames" },
{ "AddAltByType", add_alt, BY_TYPE, DIR_CMD_PERMS, ITERATE2,
    "alternate descriptive text followed by one or more MIME types" },
{ "AddAltByEncoding", add_alt, BY_ENCODING, DIR_CMD_PERMS, ITERATE2,
    "alternate descriptive text followed by one or more content encodings" },
{ "IndexOptions", add_opts, NULL, DIR_CMD_PERMS, RAW_ARGS,
    "one or more index options" },
{ "IndexIgnore", add_ignore, NULL, DIR_CMD_PERMS, ITERATE,
    "one or more file extensions" },
{ "AddDescription", add_desc, BY_PATH, DIR_CMD_PERMS, ITERATE2,
    "Descriptive text followed by one or more filenames" },
{ "HeaderName", add_header, NULL, DIR_CMD_PERMS, TAKE1, "a filename" },
{ "ReadmeName", add_readme, NULL, DIR_CMD_PERMS, TAKE1, "a filename" },
{ "FancyIndexing", fancy_indexing, NULL, DIR_CMD_PERMS, FLAG, NULL },
{ "DefaultIcon", set_string_slot,
    (void*)XtOffsetOf(dir_config_rec, default_icon),
    DIR_CMD_PERMS, TAKE1, "an icon URL"},
{ "DirectoryIndex", set_string_slot,
    (void*)XtOffsetOf(dir_config_rec, index_names),
    DIR_CMD_PERMS, RAW_ARGS, NULL },
{ NULL }
};

void *create_dir_config (pool *p, char *dummy)
{
    dir_config_rec *new =
        (dir_config_rec *) pcalloc (p, sizeof(dir_config_rec));

    new->index_names = NULL;
    new->icon_list = make_array (p, 4, sizeof (struct item));
    new->alt_list = make_array (p, 4, sizeof (struct item));
    new->desc_list = make_array (p, 4, sizeof (struct item));
    new->ign_list = make_array (p, 4, sizeof (struct item));
    new->hdr_list = make_array (p, 4, sizeof (struct item));
    new->rdme_list = make_array (p, 4, sizeof (struct item));
    new->opts_list = make_array (p, 4, sizeof (struct item));
    
    return (void *)new;
}

void *merge_dir_configs (pool *p, void *basev, void *addv)
{
    dir_config_rec *new=(dir_config_rec*)pcalloc (p, sizeof(dir_config_rec));
    dir_config_rec *base = (dir_config_rec *)basev;
    dir_config_rec *add = (dir_config_rec *)addv;

    new->default_icon = add->default_icon?add->default_icon:base->default_icon;
    new->index_names = add->index_names? add->index_names: base->index_names;

    new->alt_list = append_arrays (p, add->alt_list, base->alt_list);
    new->ign_list = append_arrays (p, add->ign_list, base->ign_list);
    new->hdr_list = append_arrays (p, add->hdr_list, base->hdr_list);
    new->desc_list = append_arrays (p, add->desc_list, base->desc_list);
    new->icon_list = append_arrays (p, add->icon_list, base->icon_list);
    new->rdme_list = append_arrays (p, add->rdme_list, base->rdme_list);
    new->opts_list = append_arrays (p, add->opts_list, base->opts_list);
    
    return new;
}

/****************************************************************
 *
 * Looking things up in config entries...
 */

/* Structure used to hold entries when we're actually building an index */

struct ent {
    char *name;
    char *icon;
    char *alt;
    char *desc;
    size_t size;
    time_t lm;
    struct ent *next;
};

char *find_item(request_rec *r, array_header *list, int path_only) {
    char *content_type = r->content_type;
    char *content_encoding = r->content_encoding;
    char *path = r->filename;

    struct item *items = (struct item *)list->elts;
    int i;

    for (i = 0; i < list->nelts; ++i) {
        struct item *p = &items[i];
      
        /* Special cased for ^^DIRECTORY^^ and ^^BLANKICON^^ */
        if((path[0] == '^') || (!strcmp_match(path,p->apply_path))) {
            if(!*(p->apply_to))
                return p->data;
            else if(p->type == BY_PATH || path[0] == '^') {
                if(!strcmp_match(path,p->apply_to))
                    return p->data;
            } else if(!path_only) {
                if(!content_encoding) {
                    if(p->type == BY_TYPE) {
                        if(content_type && !strcmp_match(content_type,p->apply_to))
                            return p->data;
                    }
                } else {
                    if(p->type == BY_ENCODING) {
                        if(!strcmp_match(content_encoding,p->apply_to))
                            return p->data;
                    }
                }
            }
        }
    }
    return NULL;
}

#define find_icon(d,p,t) find_item(p,d->icon_list,t)
#define find_alt(d,p,t) find_item(p,d->alt_list,t)
#define find_desc(d,p) find_item(p,d->desc_list,0)
#define find_header(d,p) find_item(p,d->hdr_list,0)
#define find_readme(d,p) find_item(p,d->rdme_list,0)

char *find_default_icon (dir_config_rec *d, char *bogus_name)
{
    request_rec r;

    /* Bleah.  I tried to clean up find_item, and it lead to this bit
     * of ugliness.   Note that the fields initialized are precisely
     * those that find_item looks at...
     */
    
    r.filename = bogus_name;
    r.content_type = r.content_encoding = NULL;

    return find_item (&r, d->icon_list, 1);
}

int ignore_entry(dir_config_rec *d, char *path) {
    array_header *list = d->ign_list;
    struct item *items = (struct item *)list->elts;
    char *tt;
    int i;

    if((tt=strrchr(path,'/')) == NULL)
      tt=path;
    else {
      tt++;
    }

    for (i = 0; i < list->nelts; ++i) {
        struct item *p = &items[i];
	char *ap;

	if((ap=strrchr(p->apply_to,'/')) == NULL)
	  ap=p->apply_to;
	else
	  ap++;

        if(!strcmp_match(path,p->apply_path) && !strcmp_match(tt,ap))
	   return 1;
    }
    return 0;
}

int find_opts(dir_config_rec *d, request_rec *r) {
    char *path = r->filename;
    array_header *list = d->opts_list;
    struct item *items = (struct item *)list->elts;
    int i;

    for (i = 0; i < list->nelts; ++i) {
        struct item *p = &items[i];
	
        if(!strcmp_match(path,p->apply_path))
            return (int)p->type;
    }
    return 0;
}

/*****************************************************************
 *
 * Actually generating output
 */


int insert_readme(char *name, char *readme_fname, int rule, request_rec *r) {
    char *fn;
    FILE *f;
    struct stat finfo;
    int plaintext=0;
    request_rec *rr;

    /* XXX: this is a load of crap, it needs to do a full sub_req_lookup_uri */
    fn = make_full_path(r->pool, name, readme_fname);
    fn = pstrcat(r->pool, fn, ".html", NULL);
    if(stat(fn,&finfo) == -1) {
        /* A brief fake multiviews search for README.html */
        fn[strlen(fn)-5] = '\0';
        if(stat(fn,&finfo) == -1)
            return 0;
        plaintext=1;
        if(rule) rputs("<HR>\n", r);
        rputs("<PRE>\n", r);
    }
    else if (rule) rputs("<HR>\n", r);
    /* XXX: when the above is rewritten properly, this necessary security
     * check will be redundant. -djg */
    rr = sub_req_lookup_file (fn, r);
    if (rr->status != HTTP_OK) {
	destroy_sub_req (rr);
	return 0;
    }
    destroy_sub_req (rr);
    if(!(f = pfopen(r->pool,fn,"r")))
        return 0;
    if (!plaintext)
	send_fd(f, r);
    else
    {
	char buf[IOBUFSIZE+1];
	int i, n, c, ch;
	while (!feof(f))
	{
	    do n = fread(buf, sizeof(char), IOBUFSIZE, f);
	    while (n == -1 && ferror(f) && errno == EINTR);
	    if (n == -1 || n == 0) break;
	    buf[n] = '\0';
	    c = 0;
	    while (c < n)
	    {
		for (i=c; i < n; i++)
		    if (buf[i] == '<' || buf[i] == '>' || buf[i] == '&') break;
		ch = buf[i];
		buf[i] = '\0';
		rputs(&buf[c], r);
		if (ch == '<') rputs("&lt;", r);
		else if (ch == '>') rputs("&gt;", r);
		else if (ch == '&') rputs("&amp;", r);
		c = i + 1;
	    }
	}
    }
    pfclose(r->pool, f);
    if(plaintext)
        rputs("</PRE>\n", r);
    return 1;
}


char *find_title(request_rec *r) {
    char titlebuf[MAX_STRING_LEN], *find = "<TITLE>";
    FILE *thefile = NULL;
    int x,y,n,p;

    if (r->status != HTTP_OK) {
	return NULL;
    }
    if (r->content_type && !strcmp(r->content_type,"text/html") && !r->content_encoding) {
        if(!(thefile = pfopen(r->pool, r->filename,"r")))
            return NULL;
        n = fread(titlebuf,sizeof(char),MAX_STRING_LEN - 1,thefile);
        titlebuf[n] = '\0';
        for(x=0,p=0;titlebuf[x];x++) {
            if(toupper(titlebuf[x]) == find[p]) {
                if(!find[++p]) {
                    if((p = ind(&titlebuf[++x],'<')) != -1)
                        titlebuf[x+p] = '\0';
                    /* Scan for line breaks for Tanmoy's secretary */
                    for(y=x;titlebuf[y];y++)
                        if((titlebuf[y] == CR) || (titlebuf[y] == LF))
                            titlebuf[y] = ' ';
		    pfclose (r->pool, thefile);
                    return pstrdup(r->pool, &titlebuf[x]);
                }
            } else p=0;
        }
	pfclose(r->pool, thefile);
    }
    return NULL;
}

struct ent *make_dir_entry(char *name, int dir_opts,
			   dir_config_rec *d, request_rec *r)
{
    struct ent *p;

    if((name[0] == '.') && (!name[1]))
        return(NULL);

    if (ignore_entry(d, make_full_path (r->pool, r->filename, name)))
        return(NULL);

    p=(struct ent *)pcalloc(r->pool, sizeof(struct ent));
    p->name = pstrdup (r->pool, name);
    p->size = -1;
    p->icon = NULL;
    p->alt = NULL;
    p->desc = NULL;
    p->lm = -1;

    if(dir_opts & FANCY_INDEXING) {
        request_rec *rr = sub_req_lookup_file (name, r);
	
	if (rr->finfo.st_mode != 0) {
            p->lm = rr->finfo.st_mtime;
            if(S_ISDIR(rr->finfo.st_mode)) {
                if(!(p->icon = find_icon(d,rr,1)))
                    p->icon = find_default_icon(d,"^^DIRECTORY^^");
                if(!(p->alt = find_alt(d,rr,1)))
                    p->alt = "DIR";
                p->size = -1;
		p->name = pstrcat (r->pool, name, "/", NULL);
            }
            else {
                p->icon = find_icon(d, rr, 0);
                p->alt = find_alt(d, rr, 0);
                p->size = rr->finfo.st_size;
            }
        }
	
        p->desc = find_desc(d, rr);
	
        if((!p->desc) && (dir_opts & SCAN_HTML_TITLES))
            p->desc = pstrdup (r->pool, find_title(rr));

	destroy_sub_req (rr);
    }
    return(p);
}

char *terminate_description(dir_config_rec *d, char *desc, int dir_opts) {
    int maxsize = 23;
    register int x;
    
    if(dir_opts & SUPPRESS_LAST_MOD) maxsize += 17;
    if(dir_opts & SUPPRESS_SIZE) maxsize += 7;

    for(x=0;desc[x] && maxsize;x++) {
        if(desc[x] == '<') {
            while(desc[x] != '>') {
                if(!desc[x]) {
                    maxsize = 0;
                    break;
                }
                ++x;
            }
        }
        else --maxsize;
    }
    if(!maxsize) {
        desc[x-1] = '>';	/* Grump. */
	desc[x] = '\0';		/* Double Grump! */
    }
    return desc;
}

void output_directories(struct ent **ar, int n,
			dir_config_rec *d, request_rec *r, int dir_opts)
{
    int x, len;
    char *name = r->uri;
    char *tp;
    pool *scratch = make_sub_pool (r->pool);
    
    if(name[0] == '\0') name = "/";

    if(dir_opts & FANCY_INDEXING) {
        rputs("<PRE>", r);
        if((tp = find_default_icon(d,"^^BLANKICON^^")))
            rvputs(r, "<IMG SRC=\"", escape_html(scratch, tp),
		   "\" ALT=\"     \"> ", NULL);
        rputs("Name                   ", r);
        if(!(dir_opts & SUPPRESS_LAST_MOD))
            rputs("Last modified     ", r);
        if(!(dir_opts & SUPPRESS_SIZE))
            rputs("Size  ", r);
        if(!(dir_opts & SUPPRESS_DESC))
            rputs("Description", r);
        rputs("\n<HR>\n", r);
    }
    else {
        rputs("<UL>", r);
    }
        
    for(x=0;x<n;x++) {
	char *anchor = NULL, *t = NULL, *t2 = NULL;
	
	clear_pool (scratch);
	
        if((!strcmp(ar[x]->name,"../")) || (!strcmp(ar[x]->name,".."))) {
            char *t = make_full_path (scratch, name, "../");
            getparents(t);
            if(t[0] == '\0') t = "/";
	    anchor = pstrcat (scratch, "<A HREF=\"",
			      escape_html(scratch, os_escape_path(scratch, t, 0)),
			      "\">", NULL);
	    t2 = "Parent Directory</A>       ";
        }
        else {
	    t = ar[x]->name;
	    len = strlen(t);
            if(len > 23) {
		t2 = pstrdup(scratch, t);
		t2[21] = '.';
		t2[22] = '.';
                t2[23] = '\0';
		t2 = escape_html(scratch, t2);
		t2 = pstrcat(scratch, t2, "</A>", NULL);
            } else 
	    {
		char buff[24]="                       ";
		t2 = escape_html(scratch, t);
		buff[23-len] = '\0';
		t2 = pstrcat(scratch, t2, "</A>", buff, NULL);
	    }
	    anchor = pstrcat (scratch, "<A HREF=\"",
			      escape_html(scratch, os_escape_path(scratch, t, 0)),
			      "\">", NULL);
        }

        if(dir_opts & FANCY_INDEXING) {
            if(dir_opts & ICONS_ARE_LINKS)
                rputs(anchor, r);
            if((ar[x]->icon) || d->default_icon) {
                rvputs(r, "<IMG SRC=\"", 
		       escape_html(scratch, ar[x]->icon ?
				   ar[x]->icon : d->default_icon),
		       "\" ALT=\"[", (ar[x]->alt ? ar[x]->alt : "   "),
		       "]\">", NULL);
            }
            if(dir_opts & ICONS_ARE_LINKS) 
                rputs("</A>", r);

            rvputs(r," ", anchor, t2, NULL);
            if(!(dir_opts & SUPPRESS_LAST_MOD)) {
                if(ar[x]->lm != -1) {
		    char time[MAX_STRING_LEN];
                    struct tm *ts = localtime(&ar[x]->lm);
                    strftime(time,MAX_STRING_LEN,"%d-%b-%y %H:%M  ",ts);
		    rputs(time, r);
                }
                else {
                    rputs("                 ", r);
                }
            }
            if(!(dir_opts & SUPPRESS_SIZE)) {
                send_size(ar[x]->size,r);
                rputs("  ", r);
            }
            if(!(dir_opts & SUPPRESS_DESC)) {
                if(ar[x]->desc) {
                    rputs(terminate_description(d, ar[x]->desc, dir_opts), r);
                }
            }
        }
        else
            rvputs(r, "<LI> ", anchor," ", t2, NULL);
        rputc('\n', r);
    }
    if(dir_opts & FANCY_INDEXING) {
        rputs("</PRE>", r);
    }
    else {
        rputs("</UL>", r);
    }
}


int dsortf(struct ent **s1,struct ent **s2)
{
    return(strcmp((*s1)->name,(*s2)->name));
}

    
int index_directory(request_rec *r, dir_config_rec *dir_conf)
{
    char *title_name = escape_html(r->pool, r->uri);
    char *title_endp;
    char *name = r->filename;
    
    DIR *d;
    struct DIR_TYPE *dstruct;
    int num_ent=0,x;
    struct ent *head,*p;
    struct ent **ar = NULL;
    char *tmp;
    int dir_opts = find_opts(dir_conf, r);

    if(!(d=opendir(name))) {
        log_reason ("Can't open directory for index", r->filename, r);
        return HTTP_FORBIDDEN;
    }

    r->content_type = "text/html";
    
    send_http_header(r);

    if (r->header_only) {
	closedir (d);
	return 0;
    }
    hard_timeout("send directory", r);

    /* Spew HTML preamble */
    
    title_endp = title_name + strlen(title_name) - 1;

    while (title_endp > title_name && *title_endp == '/')
	*title_endp-- = '\0';
    
    rvputs
	(
	    r,
	    "<HTML><HEAD>\n<TITLE>Index of ",
	    title_name,
	    "</TITLE>\n</HEAD><BODY>\n",
	    NULL
	);

    if((!(tmp = find_header(dir_conf,r))) || (!(insert_readme(name,tmp,0,r))))
        rvputs(r, "<H1>Index of ", title_name, "</H1>\n", NULL);

    /* 
     * Since we don't know how many dir. entries there are, put them into a 
     * linked list and then arrayificate them so qsort can use them. 
     */
    head=NULL;
    while((dstruct=readdir(d))) {
        if((p = make_dir_entry(dstruct->d_name, dir_opts, dir_conf, r))) {
            p->next=head;
            head=p;
            num_ent++;
        }
    }
    if (num_ent > 0) {
        ar=(struct ent **) palloc(r->pool, num_ent*sizeof(struct ent *));
        p=head;
        x=0;
        while(p) {
            ar[x++]=p;
            p = p->next;
        }
    
        qsort((void *)ar,num_ent,sizeof(struct ent *),
#ifdef ULTRIX_BRAIN_DEATH
	      (int (*))dsortf);
#else
	      (int (*)(const void *,const void *))dsortf);
#endif
    }
    output_directories(ar, num_ent, dir_conf, r, dir_opts);
    closedir(d);

    if (dir_opts & FANCY_INDEXING)
        if((tmp = find_readme(dir_conf, r)))
            insert_readme(name,tmp,1,r);
    else {
        rputs("</UL>", r);
    }

    rputs ("</BODY></HTML>\n", r);

    kill_timeout(r);
    return 0;
}

/* The formal handler... */

int handle_dir (request_rec *r)
{
    dir_config_rec *d =
      (dir_config_rec *)get_module_config (r->per_dir_config, &dir_module);
    const char *names_ptr = d->index_names ? d->index_names : DEFAULT_INDEX;
    int allow_opts = allow_options (r);
    int error_notfound = 0;

    if (r->uri[0] == '\0' || r->uri[strlen(r->uri)-1] != '/') {
	char* ifile;
	if (r->args != NULL)
        	ifile = pstrcat (r->pool, escape_uri(r->pool, r->uri),
			"/", "?", r->args, NULL);
	else
        	ifile = pstrcat (r->pool, escape_uri(r->pool, r->uri),
			 "/", NULL);

	table_set (r->headers_out, "Location",
		   construct_url(r->pool, ifile, r->server)); 
	return HTTP_MOVED_PERMANENTLY;
    }

    /* KLUDGE --- make the sub_req lookups happen in the right directory.
     * Fixing this in the sub_req_lookup functions themselves is difficult,
     * and would probably break virtual includes...
     */

    r->filename = pstrcat (r->pool, r->filename, "/", NULL);
    
    while (*names_ptr) {
          
	char *name_ptr = getword_conf (r->pool, &names_ptr);
	request_rec *rr = sub_req_lookup_uri (name_ptr, r);
           
	if (rr->status == HTTP_OK && rr->finfo.st_mode != 0) {
	    char* new_uri = escape_uri(r->pool, rr->uri);

	    if (rr->args != NULL)
		new_uri = pstrcat(r->pool, new_uri, "?", rr->args, NULL);
	    else if (r->args != NULL)
		new_uri = pstrcat(r->pool, new_uri, "?", r->args, NULL);
	
	    destroy_sub_req (rr);
	    internal_redirect (new_uri, r);
	    return OK;
	}

	/* If the request returned a redirect, propagate it to the client */

	if (is_HTTP_REDIRECT(rr->status) ||
	    (rr->status == HTTP_NOT_ACCEPTABLE && *names_ptr == '\0')) {

	    error_notfound = rr->status;
	    r->notes = overlay_tables(r->pool, r->notes, rr->notes);
	    r->headers_out = overlay_tables(r->pool, r->headers_out,
	                                            rr->headers_out);
	    r->err_headers_out = overlay_tables(r->pool, r->err_headers_out,
	                                                rr->err_headers_out);
	    destroy_sub_req(rr);
	    return error_notfound;
	}
            
	/* If the request returned something other than 404 (or 200),
	 * it means the module encountered some sort of problem. To be
	 * secure, we should return the error, rather than create
	 * along a (possibly unsafe) directory index.
	 *
	 * So we store the error, and if none of the listed files
	 * exist, we return the last error response we got, instead
	 * of a directory listing.
	 */
	if (rr->status && rr->status != HTTP_NOT_FOUND && rr->status != HTTP_OK)
	    error_notfound = rr->status;

        destroy_sub_req (rr);
    }

    if (error_notfound)
	return error_notfound;

    if (r->method_number != M_GET) return NOT_IMPLEMENTED;
    
    /* OK, nothing easy.  Trot out the heavy artillery... */

    if (allow_opts & OPT_INDEXES) 
        return index_directory (r, d);
    else {
        log_reason ("Directory index forbidden by rule", r->filename, r);
        return HTTP_FORBIDDEN;
    }
}


handler_rec dir_handlers[] = {
{ DIR_MAGIC_TYPE, handle_dir },
{ NULL }
};

module dir_module = {
   STANDARD_MODULE_STUFF,
   NULL,			/* initializer */
   create_dir_config,		/* dir config creater */
   merge_dir_configs,		/* dir merger --- default is to override */
   NULL,			/* server config */
   NULL,			/* merge server config */
   dir_cmds,			/* command table */
   dir_handlers,		/* handlers */
   NULL,			/* filename translation */
   NULL,			/* check_user_id */
   NULL,			/* check auth */
   NULL,			/* check access */
   NULL,			/* type_checker */
   NULL,			/* fixups */
   NULL,			/* logger */
   NULL				/* header parser */
};
