This is mostly a work in progress. I know there are several very very bad parts in the patch, and I hope to smooth it over this weekend. After saying that, I would love some feedback.

Changes:
- Default DB type is now Berkeley DB.
- Only one DBM is created per .mbox file.
- struct with multiple values is serialized into a single DBM Entry
- Directory Index Page. (Set 'MBoxIndex On' for a directory full of .mbox files)
- Show each month's Message count on index page.
- MIME Parsing:
- Multipart Messages display text/plain and text/x-patch inline.
- Decode Quoted Printable and Base64
- List Attachments
- Download Attachments (need a little more work for filenames)


Things to do:
- Determine List information based on the List-id header. (How to signup, etc)
- Display filenames for MIME attachments
- Ability to download a single message RAW (see all of the headers)
- I think there is a bug in at least one place with how buckets are swapped around.
- Try to do streamy HTML Escaping. (Currently does it by flattening the brigade)
- Support CSS/XHTML or re-code it for XSLT.


Demo:
http://tsunami.in.force-elite.com/httpd-dev/

Compare:

http://tsunami.in.force-elite.com/httpd-dev/200411.mbox/[EMAIL PROTECTED]
http://mail-archives.eu.apache.org/mod_mbox/httpd-dev//200411.mbox/[EMAIL 
PROTECTED]

-Paul
Index: mbox_parse.c
===================================================================
--- mbox_parse.c        (revision 156118)
+++ mbox_parse.c        (working copy)
@@ -37,6 +37,8 @@
 #include "apr_dbm.h"
 #include "apr_hash.h"
 #include "apr_date.h"
+#include "apr_lib.h"
+#include <assert.h>
 
 #define PUT_FIELD(r, table, name, db, keyBase) \
     temp = apr_table_get(table, name); \
@@ -45,14 +47,21 @@
 
 #define OPEN_DBM(r, db, flags, suffix, temp, status) \
     temp = apr_pstrcat(r->pool, r->filename, suffix, NULL); \
-    status = apr_dbm_open(&db, temp, flags, APR_OS_DEFAULT, r->pool );
+    status = apr_dbm_open_ex(&db, "DB", temp, flags, APR_OS_DEFAULT, r->pool );
 
-#define MSGID_DBM_SUFFIX "msgdbm"
-#define FROM_DBM_SUFFIX "fromdbm"
-#define SUBJECT_DBM_SUFFIX "subjdbm"
-#define DATE_DBM_SUFFIX "datedbm"
-#define REFERENCE_DBM_SUFFIX "refdbm"
+#define MSGID_DBM_SUFFIX ".msgsum"
 
+typedef struct mb_dbm_data {
+    int location;
+    mbox_cte_e cte;
+    apr_time_t date;
+    const char* from;
+    const char* subject;
+    const char* references;
+    const char* content_type;
+    const char* boundary;
+} mb_dbm_data;
+
 /* Fills the MBOX_BUFF structure with data from the backing file descriptor.
  * Will read up to MBOX_BUFF->maxlen-(remaining in buffer) bytes.
  */
@@ -589,29 +598,131 @@
     if (!m->subject || !*m->subject)
         m->subject = "[No Subject]";
 
-    /* FIXME: Note that apr_parsedate came from server/util.c, and
-     * belongs in a new location in apr or apr-util.
-     */
-    m->date = apr_date_parse_rfc(m->raw_date);
+    if (!m->content_type || !*m->content_type)
+        m->content_type = "text/plain";
+
     m->str_date = (char*)apr_pcalloc(r->pool, APR_RFC822_DATE_LEN);
     apr_rfc822_date(m->str_date, m->date);
 
     /* Parse the references into a table. */
     parse_references(r, m);
+}
 
-    /* Parse the date into an integer. */
-    if (m->raw_location)
+
+#define fetch_cstring(where) \
+    memcpy(&tlen, msgValue.dptr+pos, sizeof(tlen)); \
+    pos += sizeof(tlen); \
+    if (tlen == 0) { \
+        where = NULL; \
+    } \
+    else { \
+        where = apr_pmemdup(pool, msgValue.dptr+pos, tlen); \
+        pos += tlen; \
+    }
+
+#define sstrlen(str) (str ? strlen(str) + 1 : 0)
+static apr_status_t fetch_msgc(apr_pool_t* pool, apr_dbm_t* database, const 
char* key, mb_dbm_data* msgc)
+{
+    apr_datum_t msgKey, msgValue;
+    char *temp;
+    int status;
+    int pos = 0;
+    apr_uint16_t tlen = 0;
+
+    msgKey.dptr = (char*) key;
+    msgKey.dsize = strlen(key) + 1;
+
+    status = apr_dbm_fetch(database, msgKey, &msgValue);
+
+    if (status != APR_SUCCESS || !msgValue.dptr || !msgValue.dsize)
     {
-        m->location = strtol(m->raw_location, &temp, 10);
+        /* TODO: Error out. */
+        return APR_EGENERAL;
+    }
+    
+    memcpy(&msgc->location, msgValue.dptr+pos, sizeof(msgc->location));
+    pos += sizeof(msgc->location);
 
-        /* FIXME: Come up with better error condition. */
-        if (*temp != '\0')
-           m->location = 0;
+    memcpy(&msgc->date, msgValue.dptr+pos, sizeof(msgc->date));
+    pos += sizeof(msgc->date);
+
+    memcpy(&msgc->cte, msgValue.dptr+pos, sizeof(msgc->cte));
+    pos += sizeof(msgc->cte);
+
+    fetch_cstring(msgc->from);
+    fetch_cstring(msgc->subject);
+    fetch_cstring(msgc->references);
+    fetch_cstring(msgc->content_type);
+    fetch_cstring(msgc->boundary);
+
+    return APR_SUCCESS;
+}
+
+#define store_cstring(source) \
+    tlen = sstrlen(source); \
+    memcpy(value+pos, &tlen, sizeof(tlen)); \
+    pos += sizeof(tlen); \
+    memcpy(value+pos, source, tlen); \
+    pos += tlen;
+
+static apr_status_t store_msgc(apr_pool_t* pool, apr_dbm_t* database, const 
char* key, mb_dbm_data* msgc) 
+{
+    apr_datum_t msgKey, msgValue;
+    int vlen;
+    int pos = 0;
+    apr_uint16_t tlen = 0;
+    char* value;
+
+    if (!database || !key || !msgc)
+        return APR_EGENERAL;  
+   
+    msgKey.dptr = (char*) key;
+    /* We add one to the strlen to encompass the term null */
+    msgKey.dsize = strlen(key) + 1;
+    
+    /* We store the entire structure in a single entry. */
+    vlen = sizeof(msgc->location) + sizeof(msgc->date) + sizeof(msgc->cte) + \
+           (sizeof(tlen) * 5) + \
+           sstrlen(msgc->from) + \
+           sstrlen(msgc->subject) + \
+           sstrlen(msgc->references) + \
+           sstrlen(msgc->content_type) + \
+           sstrlen(msgc->boundary);
+
+    value = apr_palloc(pool, vlen);
+
+    memcpy(value+pos, &msgc->location, sizeof(msgc->location));
+    pos += sizeof(msgc->location);
+
+    memcpy(value+pos, &msgc->date, sizeof(msgc->date));
+    pos += sizeof(msgc->date);
+
+    memcpy(value+pos, &msgc->cte, sizeof(msgc->cte));
+    pos += sizeof(msgc->cte);
+    
+    store_cstring(msgc->from);
+    store_cstring(msgc->subject);
+    store_cstring(msgc->references);
+    store_cstring(msgc->content_type);
+    store_cstring(msgc->boundary);
+
+    msgValue.dptr = (char*) value;
+    msgValue.dsize = pos;
+    assert(pos == vlen);
+    return apr_dbm_store(database, msgKey, msgValue);
+}
+
+/* This function is stolen from server/util.c, since we need to be able to run
+ * standalone, without the httpd core... sigh. */
+void ex_ap_str_tolower(char *str)
+{  
+    while (*str) {
+        *str = apr_tolower(*str);
+        ++str;
     }
-    else
-        m->location = 0;
 }
 
+
 /* This function will generate the appropriate DBMs for a given mbox file.
  *
  * Currently, there is a DBM generated for each specific header we wish
@@ -621,7 +732,8 @@
 {
     apr_status_t status;
     apr_table_t * table;
-    apr_dbm_t *msgDB, *fromDB, *subjectDB, *dateDB, *refDB;
+    apr_dbm_t *msgDB;
+    apr_pool_t* tpool;
 #ifdef APR_HAS_MMAP
     apr_finfo_t fi;
 #else
@@ -630,6 +742,7 @@
     MBOX_BUFF b;
     long location;
     const char *temp, *msgID;
+    mb_dbm_data msgc;
 
 #ifdef APR_HAS_MMAP
     status = apr_file_name_get(&temp, f);
@@ -662,13 +775,10 @@
     b.len = 0;
 
     OPEN_DBM(r, msgDB, APR_DBM_RWCREATE, MSGID_DBM_SUFFIX, temp, status);
-    OPEN_DBM(r, fromDB, APR_DBM_RWCREATE, FROM_DBM_SUFFIX, temp, status);
-    OPEN_DBM(r, subjectDB, APR_DBM_RWCREATE, SUBJECT_DBM_SUFFIX, temp, status);
-    OPEN_DBM(r, dateDB, APR_DBM_RWCREATE, DATE_DBM_SUFFIX, temp, status);
-    OPEN_DBM(r, refDB, APR_DBM_RWCREATE, REFERENCE_DBM_SUFFIX, temp, status);
 
     mbox_fillbuf(&b);
 
+    apr_pool_create(&tpool, r->pool);
     /* When we reach the end of the file, b is NULL.  */
     while (b.b)
     {
@@ -689,28 +799,82 @@
             location = b.totalread - b.len + b.b - b.rb;
 #endif
             msgID = apr_table_get(table, "Message-ID");
-            if (msgID)
-            {
-                put_field_int(msgDB, msgID, location, r->pool);
-                PUT_FIELD(r, table, "From", fromDB, msgID);
-                PUT_FIELD(r, table, "Subject", subjectDB, msgID);
-                PUT_FIELD(r, table, "Date", dateDB, msgID);
-                PUT_FIELD(r, table, "References", refDB, msgID);
+            if (msgID) {
+                msgc.location = location;
+                msgc.from = apr_table_get(table, "From");
+                msgc.subject = apr_table_get(table, "Subject");
+                temp = apr_table_get(table, "Date");
+                if (temp) {
+                    msgc.date = apr_date_parse_rfc(temp);
+                }
+                else {
+                    msgc.date = 0;
+                }
+                msgc.references = apr_table_get(table, "References");
+
+                temp = apr_table_get(table, "Content-Transfer-Encoding");
+                if (temp) {
+                    char* p = apr_pstrdup(tpool, temp);
+                    msgc.cte = parse_cte_header(p);
+                }
+                else {
+                    msgc.cte = CTE_NONE;
+                }
+
+                temp = apr_table_get(table, "Content-Type");
+                if (temp) {
+                    char* p;
+                    temp = apr_pstrdup(tpool, temp);
+                    msgc.boundary = strcasestr(temp, "boundary=");
+                    if (msgc.boundary) {
+                        msgc.boundary += sizeof("boundary=") - 1;
+                        if (msgc.boundary[0] == '"') {
+                            ++msgc.boundary;
+                            if (p = strstr(msgc.boundary, "\"")) {
+                                *p = '\0';
+                            }
+                        }
+                        else {
+                            if (p = strstr(msgc.boundary, ";")) {
+                                *p = '\0';
+                            }
+                        }
+                    }
+                    p = strstr(temp, ";");
+                    if (p) {
+                        *p = '\0';
+                    }
+                    msgc.content_type = temp;
+                    /* Some old clients only sent 'text', 
+                     * instead of 'text/plain'. Lets try to be nice to them */
+                    if (!strcasecmp(msgc.content_type, "text")) {
+                        msgc.content_type = "text/plain";
+                    }
+                    else {
+                        /* Normalize the Content-Type */
+                        ex_ap_str_tolower((char*)msgc.content_type);
+                    }
+                }
+                else {
+                    msgc.content_type = NULL;
+                    msgc.boundary = NULL;
+                }
+
+                store_msgc(tpool, msgDB, msgID, &msgc);
             }
         }
-        else
+        else {
             skipLine(&b);
+        }
+        apr_pool_clear(tpool);
     }
 
+    apr_pool_destroy(tpool);
     apr_dbm_close(msgDB);
-    apr_dbm_close(fromDB);
-    apr_dbm_close(subjectDB);
-    apr_dbm_close(dateDB);
-    apr_dbm_close(refDB);
 #ifdef APR_HAS_MMAP 
     apr_mmap_delete(b.mm);
 #else
-/* If we aren't using MMAP, we relied on the open file passed in. */
+    /* If we aren't using MMAP, we relied on the open file passed in. */
 #endif
     return OK;
 }
@@ -722,9 +886,11 @@
 {
     apr_status_t status;
     MBOX_LIST *head, *keys;
-    apr_dbm_t *msgDB, *fromDB, *subjectDB, *dateDB, *refDB;
+    apr_dbm_t *msgDB;
     apr_datum_t msgKey;
     char *temp;
+    mb_dbm_data msgc;
+    apr_pool_t* tpool;
     Message *curMsg;
 
     OPEN_DBM(r, msgDB, APR_DBM_READONLY, MSGID_DBM_SUFFIX, temp, status);
@@ -732,16 +898,12 @@
     if (status != APR_SUCCESS)
         return NULL;
 
-    OPEN_DBM(r, fromDB, APR_DBM_READONLY, FROM_DBM_SUFFIX, temp, status);
-    OPEN_DBM(r, subjectDB, APR_DBM_READONLY, SUBJECT_DBM_SUFFIX, temp, status);
-    OPEN_DBM(r, dateDB, APR_DBM_READONLY, DATE_DBM_SUFFIX, temp, status);
-    OPEN_DBM(r, refDB, APR_DBM_READONLY, REFERENCE_DBM_SUFFIX, temp, status);
-
     /* APR SDBM iteration is badly broken.  You can't skip around during
      * an iteration.  Fixing this would be nice.
      */
     keys = 0;
     head = 0;
+    apr_pool_create(&tpool, r->pool);
     status = apr_dbm_firstkey(msgDB, &msgKey);
     while (msgKey.dptr != 0 && status == APR_SUCCESS)
     {
@@ -751,13 +913,21 @@
         /* FIXME: When we evolve to MD5 hashes, switch this */
         curMsg->msgID = apr_pstrndup(r->pool, msgKey.dptr, msgKey.dsize);
 
-        /* Load these fields from their respective DBMs */
-        curMsg->from = get_field_value(r, fromDB, curMsg->msgID, NULL);
-        curMsg->subject = get_field_value(r, subjectDB, curMsg->msgID, NULL);
-        curMsg->raw_date = get_field_value(r, dateDB, curMsg->msgID, NULL);
-        curMsg->raw_ref = get_field_value(r, refDB, curMsg->msgID, NULL);
-        curMsg->raw_location = get_field_value(r, msgDB, curMsg->msgID, NULL);
+        status = fetch_msgc(tpool, msgDB, curMsg->msgID, &msgc);
 
+        if (status != APR_SUCCESS)
+            break;
+
+        curMsg->from = apr_pstrdup(r->pool, msgc.from);
+        curMsg->subject = apr_pstrdup(r->pool, msgc.subject);
+        curMsg->content_type = apr_pstrdup(r->pool, msgc.content_type);
+        curMsg->boundary = apr_pstrdup(r->pool, msgc.boundary);
+        curMsg->date = msgc.date;
+        curMsg->raw_ref = apr_pstrdup(r->pool, msgc.references);
+        curMsg->location = msgc.location;
+        curMsg->cte = msgc.cte;
+        apr_pool_clear(tpool);
+
         /* Normalize the message and perform tweaks on it */
         normalize_message(r, curMsg);
 
@@ -767,11 +937,8 @@
         status = apr_dbm_nextkey(msgDB, &msgKey);
     }
 
+    apr_pool_destroy(tpool);
     apr_dbm_close(msgDB);
-    apr_dbm_close(fromDB);
-    apr_dbm_close(subjectDB);
-    apr_dbm_close(dateDB);
-    apr_dbm_close(refDB);
 
     return head;
 }
@@ -783,10 +950,11 @@
 Message* fetch_index(request_rec *r, apr_file_t *f, const char *msgID)
 {
     apr_status_t status;
-    apr_dbm_t *msgDB, *fromDB, *subjectDB, *dateDB, *refDB;
+    apr_dbm_t *msgDB;
     apr_datum_t msgKey;
     char *temp;
     Message *curMsg = NULL;
+    mb_dbm_data msgc;
 
     /* If the message ID passed in is blank. */
     if (!msgID || *msgID == '\0')
@@ -797,11 +965,6 @@
     if (status != APR_SUCCESS)
         return NULL;
 
-    OPEN_DBM(r, fromDB, APR_DBM_READONLY, FROM_DBM_SUFFIX, temp, status);
-    OPEN_DBM(r, subjectDB, APR_DBM_READONLY, SUBJECT_DBM_SUFFIX, temp, status);
-    OPEN_DBM(r, dateDB, APR_DBM_READONLY, DATE_DBM_SUFFIX, temp, status);
-    OPEN_DBM(r, refDB, APR_DBM_READONLY, REFERENCE_DBM_SUFFIX, temp, status);
-
     msgKey.dptr = (char*)msgID;
     /* We add one to the strlen to encompass the term null */
     msgKey.dsize = strlen(msgID) + 1;
@@ -812,21 +975,110 @@
     /* FIXME: When we evolve to MD5 hashes, switch this */
     curMsg->msgID = apr_pstrndup(r->pool, msgKey.dptr, msgKey.dsize);
 
-    /* Load these fields from their respective DBMs */
-    curMsg->from = get_field_value(r, fromDB, curMsg->msgID, NULL);
-    curMsg->subject = get_field_value(r, subjectDB, curMsg->msgID, NULL);
-    curMsg->raw_date = get_field_value(r, dateDB, curMsg->msgID, NULL);
-    curMsg->raw_ref = get_field_value(r, refDB, curMsg->msgID, NULL);
-    curMsg->raw_location = get_field_value(r, msgDB, curMsg->msgID, NULL);
+    status = fetch_msgc(r->pool, msgDB, curMsg->msgID, &msgc);
+    if (status != APR_SUCCESS)
+        return NULL;
 
+    curMsg->from = apr_pstrdup(r->pool, msgc.from);
+    curMsg->subject = apr_pstrdup(r->pool, msgc.subject);
+    curMsg->content_type = apr_pstrdup(r->pool, msgc.content_type);
+    curMsg->boundary = apr_pstrdup(r->pool, msgc.boundary);
+    curMsg->date = msgc.date;
+    curMsg->raw_ref = apr_pstrdup(r->pool, msgc.references);
+    curMsg->location = msgc.location;
+    curMsg->cte = msgc.cte;
+
     /* Normalize the message and perform tweaks on it */
     normalize_message(r, curMsg);
 
     apr_dbm_close(msgDB);
-    apr_dbm_close(fromDB);
-    apr_dbm_close(subjectDB);
-    apr_dbm_close(dateDB);
-    apr_dbm_close(refDB);
 
     return curMsg;
 }
+
+
+int mbox_msg_count(request_rec *r, char* path)
+{
+    apr_dbm_t *msgDB;
+    int status;
+    int count = 0;
+    apr_datum_t msgKey;
+    char* temp;
+
+    temp = apr_pstrcat(r->pool, r->filename, path, MSGID_DBM_SUFFIX, NULL);
+
+    status = apr_dbm_open_ex(&msgDB, "DB", temp, APR_DBM_READONLY,
+                             APR_OS_DEFAULT, r->pool);
+
+    if (status != APR_SUCCESS)
+        return 0;
+
+    /* FIXME: I think most DBMs have a faster method than
+     *        iterating the keys..
+     */
+    status = apr_dbm_firstkey(msgDB, &msgKey);
+    while (msgKey.dptr != 0 && status == APR_SUCCESS)
+    {
+        count++;
+        status = apr_dbm_nextkey(msgDB, &msgKey);
+    }
+
+    apr_dbm_close(msgDB);
+
+    return count;
+}
+
+/**
+ * List of all C-T-E Types found on httpd-dev and FreeBSD-current:
+ *
+ * Content-Transfer-Encoding:      8bit
+ * Content-Transfer-Encoding:  7bit
+ * Content-Transfer-Encoding: 7BIT
+ * Content-Transfer-Encoding: 7Bit
+ * Content-Transfer-Encoding: 7bit
+ * Content-Transfer-Encoding: 8-bit
+ * Content-Transfer-Encoding: 8BIT
+ * Content-Transfer-Encoding: 8Bit
+ * Content-Transfer-Encoding: 8bit
+ * Content-Transfer-Encoding: BASE64
+ * Content-Transfer-Encoding: BINARY
+ * Content-Transfer-Encoding: Base64
+ * Content-Transfer-Encoding: QUOTED-PRINTABLE
+ * Content-Transfer-Encoding: Quoted-Printable
+ * Content-Transfer-Encoding: base64
+ * Content-Transfer-Encoding: binary
+ * Content-Transfer-Encoding: none
+ * Content-Transfer-Encoding: quoted-printable
+ * Content-Transfer-Encoding: x-uuencode
+ * Content-Transfer-Encoding:7bit
+ * Content-Transfer-Encoding:quoted-printable
+ *
+ * This is why we have RFCs.
+ */
+
+mbox_cte_e parse_cte_header(char* src) 
+{
+    ex_ap_str_tolower(src);
+    if (strstr(src, "bi")) {
+        if (strchr(src, '7')) {
+            return CTE_7BIT;
+        }
+        else if (strchr(src, '8')) {
+            return CTE_8BIT;
+        }
+        else if (strchr(src, 'y')) {
+            return CTE_BINARY;
+        }
+    }
+    else if (strchr(src, '6')) {
+        return CTE_BASE64;
+    }
+    else if (strchr(src, 'q')) {
+        return CTE_QP;
+    }
+    else if (strchr(src, 'u')) {
+        return CTE_UUENCODE;
+    }
+
+    return CTE_NONE;
+}
Index: build-dso
===================================================================
--- build-dso   (revision 156118)
+++ build-dso   (working copy)
@@ -88,8 +88,9 @@
 
 build_helpers()
 {
+    $apxs_loc -c -p -o libcommon.a $common_files
     for prg in $helper_programs; do
-      $apxs_loc -c -p -o $prg $prg.c $common_files
+      $apxs_loc -c -p -o $prg $prg.c .libs/libcommon.a
       retval=$?
       if test $retval -ne 0; then
         exit $retval
Index: mbox_parse.h
===================================================================
--- mbox_parse.h        (revision 156118)
+++ mbox_parse.h        (working copy)
@@ -78,6 +78,21 @@
     void * value;
 };
 
+/*
+ * All possible Content-Transfer-Encodings.
+ */
+typedef enum {
+    CTE_NONE = 0,
+    CTE_7BIT = 1,
+    CTE_8BIT = 2,
+    CTE_UUENCODE = 3,
+    CTE_BINARY = 4,
+    CTE_QP = 5,
+    CTE_BASE64 = 6,
+} mbox_cte_e;
+
+mbox_cte_e parse_cte_header(char* src);
+
 /* The following is based on Jamie Zawinski's description of the Netscape 3.x
  * threading algorithm at <http://www.jwz.org/doc/threading.html>.
  */
@@ -98,13 +113,14 @@
     char * from;
     char * str_from;
     char * subject;
+    char * content_type;
+    char * boundary;
     apr_time_t date;
     char * str_date;
-    char * raw_date;
     apr_table_t *references;
     char * raw_ref;
     apr_off_t location;
-    char * raw_location;
+    mbox_cte_e cte;
 };
 
 /* The threading information about a message. */
@@ -146,4 +162,7 @@
  */
 Message* fetch_index(request_rec *r, apr_file_t * f, const char * msgID);
 
+/* Get A total message count for a file. */
+int mbox_msg_count(request_rec *r, char* path);
+
 #endif
Index: mod_mbox.c
===================================================================
--- mod_mbox.c  (revision 156118)
+++ mod_mbox.c  (working copy)
@@ -75,6 +75,10 @@
 
 #define MBOX_OUT_MSG_FILTER "mbox-out-message-filter"
 #define MBOX_OUT_INDEX_FILTER "mbox-out-index-filter"
+#define MBOX_BASE64_FILTER "mbox-out-base64-filter"
+#define MBOX_QP_FILTER "mbox-out-quoted-printable-filter"
+#define MBOX_MPART_FILTER "mbox-out-multipart-filter"
+#define MBOX_HTML_FILTER "mbox-out-html-filter"
 
 #define MBOX_PREV 0
 #define MBOX_NEXT 1
@@ -87,6 +91,10 @@
     int sent;
 } mbox_filter_ctx;
 
+typedef struct dir_cfg {
+    int enabled;
+} dir_cfg;
+
 /* Declare ourselves so the configuration routines can find and know us.
  * We'll fill it in at the end of the module.
  */
@@ -382,6 +390,464 @@
     return HTTP_NOT_FOUND;
 }
 
+static apr_status_t handle_line(request_rec* r, int len, char* line) 
+{
+
+    if (len > 0) {
+        ap_rputs(line, r);
+    }
+
+    ap_rputc(LF, r);
+
+    return APR_SUCCESS;
+}
+
+/*
+ * The char64 macro and `mime_decode_b64' routine are taken from
+ * metamail 2.7, which is copyright (c) 1991 Bell Communications
+ * Research, Inc. (Bellcore).  The following license applies to all
+ * code below this point:
+ *
+ * Permission to use, copy, modify, and distribute this material 
+ * for any purpose and without fee is hereby granted, provided 
+ * that the above copyright notice and this permission notice 
+ * appear in all copies, and that the name of Bellcore not be 
+ * used in advertising or publicity pertaining to this 
+ * material without the specific, prior written permission 
+ * of an authorized representative of Bellcore.  BELLCORE 
+ * MAKES NO REPRESENTATIONS ABOUT THE ACCURACY OR SUITABILITY 
+ * OF THIS MATERIAL FOR ANY PURPOSE.  IT IS PROVIDED "AS IS", 
+ * WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
+ */
+
+static char index_64[128] = {
+    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,-1,
+    -1,-1,-1,-1, -1,-1,-1,-1, -1,-1,-1,62, -1,-1,-1,63,
+    52,53,54,55, 56,57,58,59, 60,61,-1,-1, -1,-1,-1,-1,
+    -1, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14,
+    15,16,17,18, 19,20,21,22, 23,24,25,-1, -1,-1,-1,-1,
+    -1,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
+    41,42,43,44, 45,46,47,48, 49,50,51,-1, -1,-1,-1,-1
+};
+
+#define char64(c)  (((c) < 0 || (c) > 127) ? -1 : index_64[(c)])
+
+apr_size_t
+mime_decode_b64 (char *src)
+{
+  char *dst;
+  int c1, c2, c3, c4;
+  int newline = 1, DataDone = 0;
+  apr_size_t len = 0;
+
+  dst = src;
+  while ((c1 = *src++) != '\0')
+    {
+      if (isspace(c1)) {
+       if (c1 == '\n') {
+         newline = 1;
+       } else {
+         newline = 0;
+       }
+       continue;
+      }
+      if (DataDone) continue;
+      newline = 0;
+      do {
+       c2 = *src++;
+      } while (c2 != '\0' && isspace(c2));
+      do {
+       c3 = *src++;
+      } while (c3 != '\0' && isspace(c3));
+      do {
+       c4 = *src++;
+      } while (c4 != '\0' && isspace(c4));
+      if (c2 == '\0' || c3 == '\0' || c4 == '\0')
+       {
+          /* Premature EOF. Should return an Error? */
+         return;
+        }
+      if (c1 == '=' || c2 == '=') {
+       DataDone=1;
+       continue;
+      }
+      c1 = char64(c1);
+      c2 = char64(c2);
+      *dst++ = (c1<<2) | ((c2&0x30)>>4);
+      len++;
+      if (c3 == '=')
+       DataDone = 1;
+      else
+       {
+         c3 = char64(c3);
+         *dst++ = ((c2&0XF) << 4) | ((c3&0x3C) >> 2);
+          len++;
+         if (c4 == '=')
+           DataDone = 1;
+          else
+           {
+             c4 = char64(c4);
+             *dst++ = ((c3&0x03) <<6) | c4;
+              len++;
+            }
+        }
+    }
+  *dst = '\0';
+  return len;
+}
+
+int
+hex2dec_char(ch)
+     char ch;
+{
+  if (isdigit(ch))
+    return ch-'0';
+  else if (isupper(ch))
+    return ch-'A'+10;
+  else
+    return ch-'a'+10;
+}
+
+/* mime_decode_qp: convert the preamble of MSG from a quoted-printable
+ *     encoding to raw text.
+ */
+apr_size_t
+mime_decode_qp (char* p)
+{
+  unsigned char *src, *dst;
+  apr_size_t len = 0;
+
+  dst = src = p;
+  while (*src != '\0')
+    {
+      if (*src == '=')
+       {
+         if (*++src == '\n')
+           {
+             ++src;
+             continue;
+           }
+         else
+           {
+             int hi, lo;
+             hi = hex2dec_char(*src++);
+             lo = hex2dec_char(*src);
+             *dst = hi*16 + lo;
+              len++;
+           }
+       }
+      else
+       *dst = *src;
+      ++dst, ++src;
+      len++;
+    }
+}
+
+/** End metamail 2.7 code **/
+
+static const char* cte_e_to_char(mbox_cte_e cte)
+{
+    switch(cte) {
+    case CTE_NONE:
+        return "None";
+    case CTE_7BIT:
+        return "7-Bit";
+    case CTE_8BIT:
+        return "8-Bit";
+    case CTE_UUENCODE:
+        return "uuencode";
+    case CTE_BINARY:
+        return "Binary";
+    case CTE_QP:
+        return "Quoted Printable";
+    case CTE_BASE64:
+        return "Base64";
+    default:
+        return "Unknown CTE";
+    }
+}
+
+static apr_status_t cte_filter(ap_filter_t *f, apr_bucket_brigade *bb, 
+                    mbox_cte_e cte, int noescape)
+{
+    apr_status_t rv = 0;
+    apr_size_t len;
+    apr_off_t off;
+    int seen_eos = 0;
+    apr_bucket* eos;
+    apr_bucket* e;
+    char* p;
+
+    eos = APR_BRIGADE_LAST(bb);
+
+    if (APR_BUCKET_IS_EOS(eos)) {
+        seen_eos = 1;
+    }
+
+    apr_brigade_length(bb, 1, &off);
+
+    /* FIXME: This could be done without a pflatten. */
+    rv = apr_brigade_pflatten(bb, &p, &len, f->r->pool);
+
+    if (rv == APR_SUCCESS) {
+
+        p[len] = '\0';
+
+        if (cte == CTE_BASE64) {
+            len = mime_decode_b64(p);
+        }
+        else if (cte == CTE_QP) {
+            len = mime_decode_qp(p);
+        }
+
+        if (noescape) {
+            e = apr_bucket_pool_create(p, len, f->r->pool,
+                                       f->c->bucket_alloc);
+        }
+        else {
+            p = ap_escape_html(f->r->pool, p);
+
+            e = apr_bucket_pool_create(p, strlen(p), f->r->pool,
+                                       f->c->bucket_alloc);
+        }
+
+        apr_brigade_cleanup(bb);
+
+        if (seen_eos) {
+            eos = apr_bucket_eos_create(f->c->bucket_alloc);
+            APR_BRIGADE_INSERT_HEAD(bb, eos);
+        }
+
+        APR_BRIGADE_INSERT_HEAD(bb, e);
+    }
+
+    return ap_pass_brigade(f->next, bb);
+}
+
+static apr_status_t mbox_base64_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+    return cte_filter(f, bb, CTE_BASE64, 0);
+}
+
+static apr_status_t mbox_qp_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+    return cte_filter(f, bb, CTE_QP, 0);
+}
+
+typedef struct
+{
+    int get_part;
+    int mp_count;
+    int status;
+    Message *m;
+    char* bound;
+    apr_bucket_brigade *bb;
+    apr_bucket_brigade *tbb;
+    char buf[HUGE_STRING_LEN+1];
+} mbox_mpartf_ctx;
+
+static apr_status_t mbox_mpart_filter(ap_filter_t *f, apr_bucket_brigade *bb)
+{
+    mbox_mpartf_ctx* ctx = f->ctx;
+    int seen_eos = 0;
+    apr_bucket* e;
+    apr_bucket* eos;
+    apr_size_t len;
+    char* d;
+    char* ct;
+    mbox_cte_e ccte = CTE_NONE;
+    apr_status_t rv = APR_SUCCESS;
+
+    eos = APR_BRIGADE_LAST(bb);
+
+    if (APR_BUCKET_IS_EOS(eos)) {
+        seen_eos = 1;
+    }
+
+    do {
+        apr_brigade_cleanup(ctx->bb);
+
+        /* FIXME: The Brigade could end in the middle of a line. */
+        apr_brigade_split_line(ctx->bb, bb, APR_BLOCK_READ, HUGE_STRING_LEN);
+
+        if (APR_BRIGADE_EMPTY(ctx->bb)) {
+            /* End of the Source Brigade.. */
+            if (seen_eos) {
+                eos = apr_bucket_eos_create(f->c->bucket_alloc);
+                APR_BRIGADE_INSERT_TAIL(ctx->bb, eos);
+                rv = ap_pass_brigade(f->next, ctx->bb);
+            }
+
+            break;
+        }
+
+        len = HUGE_STRING_LEN;
+
+        apr_brigade_flatten(ctx->bb, ctx->buf, &len);
+
+        ctx->buf[len+1] = '\0';
+
+        /* FIXME: We Don't care about meta buckets? */
+        apr_brigade_cleanup(ctx->bb);
+
+        if (ctx->status == 1) {
+            d = strstr(ctx->buf, ctx->bound);
+            if (d) {
+                char* tmp = d + strlen(ctx->bound);
+                /* Check for the end of the entire multipart email. */
+                if (strlen(tmp) >= 2 && tmp[0] == '-' && tmp[1] == '-') {
+                    ctx->status = 0;
+                }
+                else {
+                    /* Goto the next line, and look for a content type */
+                    ctx->status = 2;
+                    ct = "multipart/broken";
+                }
+
+                if (ctx->get_part != 0 && ctx->mp_count == ctx->get_part) {
+                    ap_set_content_type(f->r, apr_pstrdup(f->r->pool, ct));
+                    rv = cte_filter(f, ctx->tbb, ccte, 1);
+                }
+                else if (ctx->get_part == 0){
+                    rv = cte_filter(f, ctx->tbb, ccte, 0);
+                }
+                apr_brigade_cleanup(ctx->tbb);
+                /* Reset C-T-E */
+                ccte = CTE_NONE;
+                continue;
+            }
+            else {
+                if (ctx->get_part != 0 && ctx->mp_count != ctx->get_part) {
+                    continue;
+                }
+                else if (ctx->get_part != 0  && ctx->get_part == 
ctx->mp_count) {
+                    /* this is the correct section! */
+                }
+                else if (strcmp(ct, "text/plain") != 0 && 
+                    strcmp(ct, "text/x-patch") != 0) {
+                    continue;
+                }
+
+                e = apr_bucket_heap_create(ctx->buf, len, 
+                                           NULL, f->c->bucket_alloc);
+                APR_BRIGADE_INSERT_TAIL(ctx->tbb, e);
+                continue;
+            }
+        }
+        else if (ctx->status == 2) {
+            if (len == 1 && strcmp(ctx->buf, "\n")) {
+                if (!strcmp(ct, "multipart/broken")) {
+                    /* Unable to find a Content Type header. */
+                    ctx->status = 1;
+                    ctx->mp_count++;
+                    continue;
+                }
+                else if (!strcmp(ct, "text/plain")) {
+                    ctx->status = 1;
+                    ctx->mp_count++;
+                    continue;
+                }
+                else {
+                    if (ctx->get_part) {
+                        ctx->status = 1;
+                        ctx->mp_count++;
+                        continue;
+                    }
+
+                    d = apr_psprintf(f->r->pool,
+                                   "<hr/>Attachment "
+                                   "<a href='%s/%d'>#%d</a> (%s) (%s)<hr/>", 
+                                   f->r->uri, ctx->mp_count+1, ctx->mp_count, 
+                                   ct, cte_e_to_char(ccte));
+
+                    e = apr_bucket_pool_create(d, strlen(d), 
+                                            f->r->pool, f->c->bucket_alloc);
+                    APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
+                    ctx->status = 1;
+                    ctx->mp_count++;
+                    rv = ap_pass_brigade(f->next, ctx->bb);
+                    continue;
+                }
+            }
+            else {
+                /* FIXME: Handle Content-Disposition */
+                if (!strncasecmp(ctx->buf, "Content-Type: ",
+                                 strlen("Content-Type: "))) {
+                    char* tmp = ctx->buf + strlen("Content-Type: ");
+                    char* p = strstr(tmp, ";");
+                    if (p) {
+                        *p = '\0';
+                        /* FIXME: Handle the name= param. */
+                    }
+                    else {
+                        p = tmp;
+                        while(*p != '\0') {
+                            if (isspace(*p)) {
+                                *p = '\0';
+                                break;
+                            }
+                            *p++;
+                        }
+                    }
+                    ct = apr_pstrdup(f->r->pool, tmp);
+                    continue;
+                }
+
+                if (!strncasecmp(ctx->buf, "Content-Transfer-Encoding:", 
+                                 strlen("Content-Transfer-Encoding:"))) {
+
+                    ccte = parse_cte_header(ctx->buf);
+                    continue;
+                }
+            }
+        }
+    } while(rv == APR_SUCCESS);
+
+    return rv;
+}
+
+static apr_status_t mbox_html_filter(ap_filter_t *f, 
+    apr_bucket_brigade *bb)
+{
+    const char *buf = 0;
+    apr_size_t bytes = 0;
+    char* p;
+    apr_bucket *b;
+    apr_bucket *e;
+    apr_bucket_brigade *nbb = f->ctx;
+    p = ap_escape_html(f->r->pool, p);
+
+    if (!f->ctx) {
+        nbb = f->ctx = apr_brigade_create(f->r->pool, f->c->bucket_alloc);
+    }
+    else {
+        apr_brigade_destroy(nbb);
+    }
+
+    for (b = APR_BRIGADE_FIRST(bb);
+         b != APR_BRIGADE_SENTINEL(bb); b = APR_BUCKET_NEXT(b)) {
+        if (APR_BUCKET_IS_METADATA(b)) {
+            apr_bucket_copy(b, &e);
+            APR_BRIGADE_INSERT_TAIL(nbb, e);
+        }
+        else if (apr_bucket_read(b, &buf, &bytes, APR_BLOCK_READ)
+                 == APR_SUCCESS) {
+
+            /* FIXME: create a pool to reuse */
+
+            p = ap_escape_html(f->r->pool,
+                               apr_pstrmemdup(f->r->pool, buf, bytes));
+            e = apr_bucket_pool_create(p, strlen(p), 
+                                       f->r->pool, f->c->bucket_alloc);
+            APR_BRIGADE_INSERT_TAIL(nbb, e);
+        }
+    }
+    apr_brigade_destroy(bb);
+
+    return ap_pass_brigade(f->next, nbb);
+}
+
 /* This function will take the message id from the virtual namespace, and
  * print out the cached headers of the message and the complete body
  * of the message.
@@ -392,11 +858,16 @@
 {
     MBOX_BUFF b;
     char *msgID;
-    char buf[HUGE_STRING_LEN+1], line[HUGE_STRING_LEN+1];
-    int len;
+    char *bound;
+    char buf[HUGE_STRING_LEN+1];
+    char line[HUGE_STRING_LEN+1]; 
+    int len = 0;
+    int multipart = 0;
     apr_status_t status;
     mbox_filter_ctx *ctx;
+    mbox_mpartf_ctx* mctx;
     Message *m;
+    mbox_cte_e ccte = CTE_NONE;
 
     /* msgID should be the part of the URI that Apache could not resolve
      * on its own.  Grab it and skip over the expected /.
@@ -404,6 +875,20 @@
     msgID = r->path_info;
     msgID++;
 
+    bound = strrchr(msgID, '/');
+    if (bound) {
+        *bound = '\0'; 
+        *bound++;
+        len = atoi(bound);
+        /* We don't support mime messages with more than 32 parts */
+        if (len < 1 || len > 32) {
+            len = 0;
+        }
+    }
+    else {
+        len = 0;
+    }
+
     m = fetch_index(r, f, msgID);
 
     if (!m)
@@ -411,6 +896,16 @@
 
     status = apr_file_seek(f, APR_SET, &m->location);
 
+    if (!strncmp(m->content_type,"multipart/", strlen("multipart/"))) {
+        multipart = 1;
+        if (m->boundary) {
+            bound = apr_pstrcat(r->pool, "--", m->boundary, NULL);
+        }
+        else {
+            bound = NULL;
+        }
+    }
+
     buf[0] = '\0';
     b.sb = b.rb = b.b = buf;
     b.fd = f;
@@ -419,10 +914,41 @@
     b.totalread = 0;
     mbox_fillbuf(&b);
 
-    ctx = (mbox_filter_ctx*) apr_pcalloc(r->pool, sizeof(mbox_filter_ctx));
-    ctx->m = m;
-    ap_add_output_filter(MBOX_OUT_MSG_FILTER, ctx, r, r->connection);
+    /* If the entire message is Base64 or Q-P, it cannot be a multipart. */
+    if (m->cte == CTE_BASE64) {
+        ap_add_output_filter(MBOX_BASE64_FILTER, NULL, r, r->connection);
+    }
+    else if (m->cte == CTE_QP) {
+        ap_add_output_filter(MBOX_QP_FILTER, NULL, r, r->connection);
+    }
+    else if (multipart) {
 
+        mctx = apr_palloc(r->pool, sizeof(mbox_mpartf_ctx));
+        mctx->m = m;
+        mctx->get_part = len;
+        mctx->tbb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+        mctx->bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
+        mctx->status = multipart;
+        mctx->bound = bound;
+        mctx->buf[HUGE_STRING_LEN] = '\0';
+        mctx->mp_count = 0;
+        ap_add_output_filter(MBOX_MPART_FILTER, mctx, r, r->connection);
+    }
+    else {
+        /* Just Escape the HTML */
+        ap_add_output_filter(MBOX_HTML_FILTER, NULL, r, r->connection);
+    }
+
+    if (!(multipart && mctx->get_part != 0)) {
+        ctx = (mbox_filter_ctx*) apr_pcalloc(r->pool, sizeof(mbox_filter_ctx));
+        ctx->m = m;
+        ap_add_output_filter(MBOX_OUT_MSG_FILTER, ctx, r, r->connection);
+    }
+/*
+    if ((r->proto_num >= 1001) && !r->main && !r->prev)
+        r->chunked = 1;
+*/
+
     /* When we reach the end of the file, b.b is NULL.  */
     while (b.b)
     {
@@ -431,36 +957,19 @@
         
         if (!b.b || 
             (line[0] == 'F' && line[1] == 'r' && 
-            line[2] == 'o' && line[3] == 'm' && line[4] == ' '))
+            line[2] == 'o' && line[3] == 'm' && line[4] == ' ')) {
             break;
-        else
-        {
-            if (len < 0)
-                return HTTP_INTERNAL_SERVER_ERROR;
-
-            if (len > 0)
-            {
-                /* This could and should easily be moved to a filter. */
-                if (line[0] == '>' )
-                {
-                    /* Hide sendmail funniness by removing the > */
-                    if (line[1] == 'F' && line[2] == 'r' && 
-                        line[3] == 'o' && line[4] == 'm' && line[5] == ' ')
-                        memmove(line, line + 1, len - 1);
-                    else
-                        ap_rputs("<I>", r);
-                }
-                    
-                ap_rputs(ap_escape_html(r->pool, line), r);
-
-                if (line[0] == '>')
-                    ap_rputs("</I>", r);
+        }
+        else {
+            if (len < 0) {
+                /* FIXME: Log This Condition! */
+                status = HTTP_INTERNAL_SERVER_ERROR;
+                break;
             }
 
-            ap_rputc(LF, r);
+            handle_line(r, len, line);
         }
     }
-
     return status;
 }
 
@@ -496,6 +1005,8 @@
 
     if (!ctx->sent)
     {
+        apr_table_unset(f->r->headers_out, "Content-Length");
+
         header = apr_psprintf(f->r->pool,
             DOCTYPE_HTML_4_0T 
             "<HTML>\n<HEAD>\n<TITLE>%s</TITLE>\n</HEAD>\n<BODY>"
@@ -570,6 +1081,8 @@
 
     if (!ctx->sent)
     {
+        apr_table_unset(f->r->headers_out, "Content-Length");
+
         temp = URI_ESCAPE_OR_BLANK(f->r->pool, m->msgID);
         header = apr_psprintf(f->r->pool,
             DOCTYPE_HTML_4_0T 
@@ -580,6 +1093,9 @@
             "<STRONG>From:</STRONG> %s<BR>\n"
             "<STRONG>Subject:</STRONG> %s<BR>\n" 
             "<STRONG>Date:</STRONG> %s<BR>\n"
+            "<STRONG>Content-Type:</STRONG> %s<BR>\n"
+            "<STRONG>Boundary:</STRONG> %s<BR>\n"
+            "<STRONG>C-T-E:</STRONG> %s<BR>\n"
             "<A HREF=\"%s/prev?%s\">Prev</A> "
             "<A HREF=\"%s/next?%s\">Next</A> "
             "<A HREF=\"%s/prev-thread?%s\">Prev by Thread</A> "
@@ -590,6 +1106,9 @@
             ESCAPE_OR_BLANK(f->r->pool, m->from),
             ESCAPE_OR_BLANK(f->r->pool, m->subject),
             ESCAPE_OR_BLANK(f->r->pool, m->str_date),
+            ESCAPE_OR_BLANK(f->r->pool, m->content_type),
+            ESCAPE_OR_BLANK(f->r->pool, m->boundary),
+            cte_e_to_char(m->cte),
             ctx->baseURI, temp,
             ctx->baseURI, temp,
             ctx->baseURI, temp,
@@ -721,25 +1240,180 @@
     return status; 
 }
 
+static int show_index_file_info(request_rec *r, char *path) 
+{
+    int count = mbox_msg_count(r, path);
+    ap_rprintf(r, "<tr><td>%.2s/%.4s</td><td>"
+               "<a href=\"%s/threads.html\">Threads</a> "
+               "<a href=\"%s/index.html\">Date</a> "
+               "<a href=\"%s/authors.html\">Authors</a></td>"
+               "<td>%d</td></tr>\n",
+               path+4, path, path, path, path, count);
+    return OK;
+}
+
+static int file_alphasort(const void *fn1, const void *fn2)
+{
+    /* reverse order */
+    return strcmp(*(char**)fn2, *(char**)fn1);
+}
+
+static int generate_mbox_index(request_rec *r) 
+{
+   apr_status_t rv = APR_SUCCESS;
+   char* file;
+   apr_dir_t *dir;
+   apr_finfo_t finfo;
+   apr_array_header_t* files;
+   int i;
+
+   ap_set_content_type(r, "text/html; charset=utf-8");
+
+    rv = apr_dir_open(&dir, r->filename, r->pool);
+    if (rv != APR_SUCCESS) {
+        return rv;
+    }
+
+    files = apr_array_make(r->pool, 0, sizeof(char *));
+
+    while (apr_dir_read(&finfo, APR_FINFO_NAME, dir) == APR_SUCCESS) {
+        if (apr_fnmatch("*.mbox", finfo.name, 0) == APR_SUCCESS) {
+               *(const char **)apr_array_push(files) = \
+                        apr_pstrdup(r->pool, finfo.name);
+        }
+    }
+
+    apr_dir_close(dir);
+
+    if (files->nelts != 0) {
+        qsort((void *) files->elts, files->nelts,
+              sizeof(char *), file_alphasort);
+    }
+
+    ap_rputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+              DOCTYPE_XHTML_1_0T
+              "<html xmlns=\"http://www.w3.org/1999/xhtml\"; xml:lang=\"en\">\n"
+              "<head>\n<title>", r);
+    ap_rputs(r->filename, r);
+    ap_rputs("</title>\n</head>\n"
+             "<body\n bgcolor=\"#FFFFFF\" text=\"#000000\" "
+             "link=\"#0000FF\" vlink=\"#000080\" alink=\"#FF0000\">\n"
+             "<h2>", r);
+    if (files->nelts != 0) {
+        /**
+         * The First File in the list should be the newest. 
+         * Use this to determine the List Name.
+         */
+        file = ((char**)files->elts)[0];
+        ap_rputs(file, r);
+    }
+    ap_rputs("</h2>\n<table width=\"100%\">\n", r);
+    ap_rputs("<tr><th align=\"left\" width=\"15%\">Date</th>"
+             "<th align=\"left\" width=\"85%\">Sorted 
by</th><th>Messages</th></tr>", r);
+
+    if (files->nelts != 0) {
+
+        for (i = 0; i < files->nelts; i++) {
+               file = ((char**)files->elts)[i];
+               show_index_file_info(r, file);
+               if (i+1 < files->nelts) {
+                   if(((char**)files->elts)[i][3] != 
((char**)files->elts)[i+1][3]) {
+                       ap_rputs("<tr><td colspan='3'><hr/></td></tr>", r);
+                   }
+               }
+        }
+    }
+    else {
+        ap_rputs("<tr><td colspan=\"2\">"
+                 "No messages have been archived for this list."
+                 "</td></tr>", r);
+    }
+
+    ap_rputs("</table>\n</body>\n</html>", r);
+
+    return OK;
+}
+
+static int mboxindex_handler(request_rec *r)
+{
+    dir_cfg *conf;
+
+    if (strcmp(r->handler, DIR_MAGIC_TYPE)) {
+        return DECLINED;
+    }
+
+    conf = ap_get_module_config(r->per_dir_config, &mbox_module);
+
+    if (!conf->enabled) {
+        return DECLINED;
+    }
+    
+    return generate_mbox_index(r);
+}
+
 static void mbox_register_hooks(apr_pool_t *p)
 {
     ap_hook_handler(mbox_handler, NULL, NULL, APR_HOOK_MIDDLE);
+    ap_hook_handler(mboxindex_handler, NULL, NULL, APR_HOOK_FIRST);
 
-    ap_register_output_filter(MBOX_OUT_INDEX_FILTER, mbox_out_index_filter,  
+    ap_register_output_filter(MBOX_OUT_INDEX_FILTER, mbox_out_index_filter,
         NULL,
         AP_FTYPE_RESOURCE);
-    ap_register_output_filter(MBOX_OUT_MSG_FILTER, mbox_out_message_filter, 
+    ap_register_output_filter(MBOX_OUT_MSG_FILTER, mbox_out_message_filter,
         NULL,
         AP_FTYPE_RESOURCE);
+    ap_register_output_filter(MBOX_MPART_FILTER, mbox_mpart_filter,
+        NULL,
+        AP_FTYPE_RESOURCE);
+    ap_register_output_filter(MBOX_BASE64_FILTER, mbox_base64_filter,
+        NULL,
+        AP_FTYPE_RESOURCE);
+    ap_register_output_filter(MBOX_QP_FILTER, mbox_qp_filter,
+        NULL,
+        AP_FTYPE_RESOURCE);
+    ap_register_output_filter(MBOX_HTML_FILTER, mbox_html_filter,
+        NULL,
+        AP_FTYPE_RESOURCE);
 }
 
+static void *mbox_create_dir_config(apr_pool_t * p, char *x)
+{
+    dir_cfg *conf = apr_pcalloc(p, sizeof(dir_cfg));
+
+    conf->enabled = 0;
+
+    return conf;
+}
+
+static void *mbox_merge_dir_config(apr_pool_t *p, void *basev, void *addv)
+{
+    dir_cfg *from = basev;
+    dir_cfg *merge = addv;
+    dir_cfg *to = apr_palloc(p, sizeof(dir_cfg));
+
+    if (merge->enabled == 1) {
+        to->enabled = 1;
+    }
+    else {
+        to->enabled = from->enabled;
+    }
+    return to;
+}
+
+static const command_rec mbox_cmds[] ={
+    AP_INIT_FLAG("mboxindex", ap_set_flag_slot,
+                 (void *)APR_OFFSETOF(dir_cfg, enabled), OR_INDEXES,
+                "Enable mod_mbox to create directory listings of .mbox 
files."),
+    {NULL}
+};
+
 module mbox_module =
 {
     STANDARD20_MODULE_STUFF,
-    NULL,                      /* per-directory config creator */
-    NULL,                      /* dir config merger */
+    mbox_create_dir_config,    /* per-directory config creator */
+    mbox_merge_dir_config,     /* dir config merger */
     NULL,                      /* server config creator */
     NULL,                      /* server config merger */
-    NULL,                      /* command table */
+    mbox_cmds,                 /* command table */
     mbox_register_hooks        /* set up other request processing hooks */
 };

Reply via email to