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 */
};
