You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ma...@apache.org on 2005/08/26 23:33:19 UTC

svn commit: r240348 - in /httpd/mod_mbox/branches/httpd-mbox-if: ./ data/ module-2.0/

Author: maxime
Date: Fri Aug 26 14:33:07 2005
New Revision: 240348

URL: http://svn.apache.org/viewcvs?rev=240348&view=rev
Log:
AJAX browser basics. Backported some fixes by justin from
trunk/. Still some bugs, though. Bug squashing in progress ...

Note: there are still some tabs in the code indentation. I'll make a
one-shot commit for this kind of stuff.

 * module-2.0/mod_mbox_cte.c:
    (mbox_cte_decode_header): added lacks to function's comment. Basics for charset conversion using apr_xlate, but not working yet.

 * module-2.0/mod_mbox_file.c:
    (fetch_context_msgids): set the pointers to NULL by default in
order to avoid false-positives tests later on.

 * module-2.0/mod_mbox.c:
    (mbox_wrap_text): new utility function for basic text
wrapping. Not very good, but better than nothing.

    (get_base_path): added comment.

 * module-2.0/mod_mbox.h: added include for apr_xlate and function
prototype for mbox_wrap_text.

 * module-2.0/mod_mbox_mime.c:
    (mbox_mime_decode_multipart): exclude mail contents from header
lookups. Fixes several weird bugs in gathered informations.

    (mbox_mime_get_body): do not work on m->body directly because
length can grow while decoding Quoted-Printable or Base64.

    (mbox_mime_static_structure): factored </a> markup.

    (mbox_mime_xml_structure): fixed outputted structure incoherence.

 * module-2.0/mod_mbox_index.c:
    (mbox_index_handler): return DECLINED instead of HTTP_NOT_FOUND
when the mbox cache is not available. Backported from r231188. Added
an ID parameter to the body markup.

 * module-2.0/mod_mbox_out.c:
    (mbox_static_boxlist): fixed typo in function's comment. Added ID
parameter to a message's table row (only if needed).

    (mbox_xml_msglist): removed unused month and year attributes. Same
for baseURI, now useless.

    (mbox_static_msglist): added ID parameter to body markup. Small
change to page header. Don't link to current sort mode.

    (mbox_ajax_browser): pass the baseURI to the Javascript context.

    (mbox_static_message_nav): new function displaying message navigation.

    (mbox_static_message): added ID parameter to body markup. Now uses
the new mbox_static_message_nav function. Also display the message
navigation as table footer. Wrap text before displaying it.

    (mbox_xml_message): use CDATA for subject, too. Wrap text.

 * module-2.0/mod-mbox-util.c:
    (scan_dir): skip empty mbox files in order to determine list
info. Backported from r231183.

 * STATUS: updated.

 * configure.ac: fixed typo. Backported from r231182.

 * data/archives.js:
    (getBoxList, getMsgList, getMessage, ...): implementing the AJAX
browser.

 * data/style.css: minor fixes related to the AJAX browser's needs.




Modified:
    httpd/mod_mbox/branches/httpd-mbox-if/STATUS
    httpd/mod_mbox/branches/httpd-mbox-if/configure.ac
    httpd/mod_mbox/branches/httpd-mbox-if/data/archives.js
    httpd/mod_mbox/branches/httpd-mbox-if/data/asf_logo_simple.png
    httpd/mod_mbox/branches/httpd-mbox-if/data/style.css
    httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod-mbox-util.c
    httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox.c
    httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox.h
    httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_cte.c
    httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_file.c
    httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_index.c
    httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_mime.c
    httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_out.c

Modified: httpd/mod_mbox/branches/httpd-mbox-if/STATUS
URL: http://svn.apache.org/viewcvs/httpd/mod_mbox/branches/httpd-mbox-if/STATUS?rev=240348&r1=240347&r2=240348&view=diff
==============================================================================
--- httpd/mod_mbox/branches/httpd-mbox-if/STATUS (original)
+++ httpd/mod_mbox/branches/httpd-mbox-if/STATUS Fri Aug 26 14:33:07 2005
@@ -8,36 +8,32 @@
 
 Release showstoppers:
 
-    * AJAX browsing interface
-    * S/MIME and MIME structure parsing [done]
-    * MIME header parsing [done]
-    * Charset conversions (to UTF-8)
+    * MIME part viewing / downloading
 
 Other known issues / ToDo:
 
-    * MIME part viewing / downloading
-
     * Documentation
       - For developpers : internals
       - For admins      : setup and update
       - For users       : usage, interface description, ...
 
-    * Wrap long lines in mails (to ~90 chars so we don't mess
-      with MUA different configurations). Any ideas on how to do
-      this clean and fast enough ?
-
-    * A QP mail with a '=_N' in it's MIME boundary fails to be parsed
-      by mbox_mime_decode_multipart[1]. Strange, because no QP
-      decoding is done before multipart parsing : only 'JIT' before
-      display.
+    * Use apr_strftime() to format dates instead of apr_rfc822_date()
+
+    * Charset conversions (to UTF-8). APR-Xlate features are nice, but
+      the conversion are easy only from APR v1.1.0. Multibyte
+      characters conversion with APR v0.x is a pain.
 
-      [1] dev@httpd (200302.mbox): <00...@fred>
+    * Better page selector (usefull for 10+ pages archives)
 
-    * Merge Justin changes into httpd-mbox-if branch
-      r231181, r231182, r231183, r231187, r231188
+    * Subject RFC 2047 decoding. See function's comment
+      (mod_mbox_cte.c)
 
 Wish list:
 
     * Better integration with httpd-2.0 build system.
     * Interface internationalization
     * Better XML semantics ?
+    * Use Javascript object-oriented features
+    * When viewing a message, come back to the page we came instead of
+      the first page of the choosen listing (without cookie, if
+      possible)
\ No newline at end of file

Modified: httpd/mod_mbox/branches/httpd-mbox-if/configure.ac
URL: http://svn.apache.org/viewcvs/httpd/mod_mbox/branches/httpd-mbox-if/configure.ac?rev=240348&r1=240347&r2=240348&view=diff
==============================================================================
--- httpd/mod_mbox/branches/httpd-mbox-if/configure.ac (original)
+++ httpd/mod_mbox/branches/httpd-mbox-if/configure.ac Fri Aug 26 14:33:07 2005
@@ -44,6 +44,6 @@
 echo "---"
 echo "Configuration summary for mod_mbox"
 echo ""
-echo "   * Apache Dodules Directory:    $AP_LIBEXECDIR"
+echo "   * Apache Modules Directory:    $AP_LIBEXECDIR"
 echo ""
 echo "---"

Modified: httpd/mod_mbox/branches/httpd-mbox-if/data/archives.js
URL: http://svn.apache.org/viewcvs/httpd/mod_mbox/branches/httpd-mbox-if/data/archives.js?rev=240348&r1=240347&r2=240348&view=diff
==============================================================================
--- httpd/mod_mbox/branches/httpd-mbox-if/data/archives.js (original)
+++ httpd/mod_mbox/branches/httpd-mbox-if/data/archives.js Fri Aug 26 14:33:07 2005
@@ -1,11 +1,24 @@
-/** mod_mbox
- */
+/* Copyright 2001-2005 The Apache Software Foundation or its licensors, as
+* applicable.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*     http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
 
-/* onLoad function for boxes list.
- *
- * Change 'Threads - Date - Author' links into a single 'Browser'
- * link.
+/* mod_mbox javascript functions.
  */
+
+/* onLoad function for boxes list : changes 'Threads - Date - Author'
+ * links into a single 'Browser' link. */
 function indexLinks ()
 {
   var i, entries;
@@ -17,28 +30,534 @@
 
   /* Update span.links contents */
   while (entries[i]) {
-    if (entries[i].hasAttribute('class')) {
-      if (entries[i].getAttribute('class') == 'links') {
-	entries[i].innerHTML = '<a href="' +
-	  entries[i].getAttribute('id') +
-	  '.mbox/browser">Browse</a>';
+    if (entries[i].className == 'links') {
+      entries[i].innerHTML = '<a href="' +
+	entries[i].getAttribute('id') +
+	'.mbox/browser">Browse</a>';
+    }
+    i++;
+  }
+
+  return true;
+}
+
+var _mbox = '';       /* Current browsed mailbox */
+var _baseURI = '';    /* Base archives URI */
+var _sort = 'thread'; /* Sort mode defaults to 'thread' */
+var _page = 0;
+
+var _boxlist = null;
+var _msglist = null;
+
+var _default_ctx = 3;
+
+/* onLoad function for browser. Call the message list and box list
+ * creation functions. */
+function loadBrowser (baseURI)
+{
+  _baseURI = baseURI.substring(0, baseURI.lastIndexOf('/')+1);
+  _mbox = baseURI.substring(baseURI.lastIndexOf('/')+1);
+
+  body = document.getElementsByTagName('body')[0];
+  body.innerHTML += '<div id="loading">Loading ...</div>';
+
+  document.getElementsByTagName('h1')[0].innerHTML += ': ' +
+    getMonthName(parseInt(_mbox.substr(4, 2), 10), true) + ' ' +
+    _mbox.substr(0, 4);
+
+  /* Get message list */
+  if (!getMsgList (null, null)) {
+    body.innerHTML += '<table id="msglist">' +
+      '<thead><tr><th>Message list</th></tr></thead>' +
+      '<tbody><tr><td>Unable to load message list for ' +
+      _mbox + ' !</td></tr></tbody></table>';
+  }
+
+  /* Get box list */
+  if (!getBoxList ()) {
+    body.innerHTML += '<table id="boxlist">' +
+      '<thead><tr><th>Box list</th></tr></thead>' +
+      '<tbody><tr><td>Unable to load box list !</td></tr></tbody>' +
+      '</table>';
+  }
+
+  return true;
+}
+
+/* Returns the textual representation of a month number. */
+function getMonthName (num, full)
+{
+  var full_months = new Array("January", "February", "March",
+			      "April", "May", "June", "July",
+			      "August", "September", "October",
+			      "November", "December");
+
+  var short_months = new Array("Jan", "Feb", "Mar", "Apr",
+			       "May", "Jun", "Jul", "Aug",
+			       "Sep", "Oct", "Nov", "Dec");
+
+  if (full) {
+    return full_months[num-1];
+  }
+
+  return short_months[num-1];
+}
+
+/* Returns a XmlHttpObject, or false on error. Don't forget to use
+ * different variable names for XMLHttpRequest object instances since
+ * we are doing asynchronous Javascript and JS does not support
+ * variable scope (local variables). */
+function getHTTPObject ()
+{
+  var xmlhttp = false;
+
+  /*@cc_on
+    @if (@_jscript_version >= 5)
+
+  try {
+    xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
+  } catch (e) {
+    try {
+      xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
+    } catch (E) {
+      xmlhttp = false;
+    }
+  }
+
+    @else
+  xmlhttp = false;
+    @end
+    @*/
+
+  if (!xmlhttp && typeof XMLHttpRequest != 'undefined') {
+    try {
+      xmlhttp = new XMLHttpRequest();
+    } catch (e) {
+      xmlhttp = false;
+    }
+  }
+
+  return xmlhttp;
+}
+
+/* Retreive the message list from server, according to the sort flag
+ * and page number given */
+function getMsgList (sort, page)
+{
+  var msglist_http = getHTTPObject ();
+
+  if (!msglist_http) {
+    return false;
+  }
+
+  if (!sort) {
+    sort = _sort;
+  }
+
+  _sort = sort;
+
+  /* The handler */
+  msglist_http.onreadystatechange = function () {
+    if (msglist_http.readyState == 4) { /* 4 : "complete" */
+      if (msglist_http.status == 200) { /* 200 : OK */
+	_msglist = msglist_http.responseXML.documentElement;
+	var i;
+
+	drawFullMsgList ();
+
+	/* Remove now useless loading message */
+	body.removeChild(document.getElementById('loading'));
       }
     }
+  }
+
+  msglist_http.open('GET', 'ajax/' + sort + '?' + page, true);
+  msglist_http.send(null);
+
+  return true;
+}
+
+function drawFullMsgList ()
+{
+  /* Destroy previous message view if displayed */
+  if (document.getElementById('msgview')) {
+    body.removeChild(document.getElementById('msgview'));
+  }
+
+  /* Get an array of all messages */
+  var msgs = _msglist.getElementsByTagName('message');
+
+  /* Create the destination table */
+  var msglist = document.createElement('table');
+  msglist.setAttribute('id', 'msglist');
+
+  var str = getMsgListHeader ();
+
+  /* Build message list entries */
+  str += '<tbody>';
+
+  i = 0;
+  while (msgs[i]) {
+    str += buildMessageListEntry (msgs[i], i);
     i++;
   }
 
+  str += '</tbody>';
+  msglist.innerHTML = str;
+
+  if (document.getElementById('msglist')) {
+    body.removeChild(document.getElementById('msglist'));
+  }
+
+  body.appendChild(msglist);
+}
+
+
+/* Returns the message list table header, including page selector and
+ * sort links */
+function getMsgListHeader ()
+{
+  /* Fetch current page and page count */
+  var current_page = _msglist.getAttribute('page');
+  var pages = _msglist.getAttribute('pages');
+
+  var str = '<thead><tr><th class="title">Message list</th>';
+
+  /* Page selector */
+  str += '<th class="pages">';
+  for (i = 0; i<pages ; i++) {
+    if (i) {
+      str += ' &middot; ';
+    }
+
+    if (i != current_page) {
+      str += '<a href="browser" onclick="javascript:getMsgList(\'' + _sort + '\', \'' +
+	i + '\'); return false;">' + (i+1) + '</a>';
+    }
+    else {
+      str += (i+1);
+    }
+  }
+  str += '</th>';
+
+  str += '<th class="sort">';
+  if (_sort == 'thread') {
+    str += 'Thread &middot; ' +
+      '<a href="browser" onclick="javascript:getMsgList(\'author\', null); return false;">Author</a>' +
+      ' &middot; ' +
+      '<a href="browser" onclick="javascript:getMsgList(\'date\', null); return false;">Date</a>';
+  }
+  else if (_sort == 'author') {
+    str += '<a href="browser" onclick="javascript:getMsgList(\'thread\', null); return false;">Thread</a>' +
+      ' &middot; Author &middot; ' +
+      '<a href="browser" onclick="javascript:getMsgList(\'date\', null); return false;">Date</a>';
+  }
+  else {
+    str += '<a href="browser" onclick="javascript:getMsgList(\'thread\', null); return false;">Thread</a>' +
+      ' &middot; ' +
+      '<a href="browser" onclick="javascript:getMsgList(\'author\', null); return false;">Author</a>' +
+      ' &middot; Date';
+  }
+
+  str += '</th></tr></thead>';
+
+  return str;
+}
+
+/* Returns a table-formatted message list entry */
+function buildMessageListEntry (msg, msg_num)
+{
+  var linked = parseInt(msg.getAttribute('linked'));
+  var str = '';
+  var i;
+
+  if (linked) {
+    str += '<tr id="' + msg.getAttribute('id') + '">';
+  }
+  else {
+    str += '<tr>';
+  }
+
+  /* The author */
+  str += '<td class="author">';
+  if (linked) {
+    str += msg.getElementsByTagName('from')[0].firstChild.data;
+  }
+  str += '</td>';
+
+  /* The subject */
+  str += '<td class="subject">';
+  for (i=0 ; i<msg.getAttribute('depth') ; i++) {
+    str += '&nbsp;&nbsp;';
+  }
+
+  if (linked) {
+    str += '<a href="ajax/' + msg.getAttribute('id') +
+      '" onclick="javascript:toggleMessage(' + msg_num + '); return false;">';
+  }
+
+  str += msg.getElementsByTagName('subject')[0].firstChild.data;
+
+  if (linked) {
+    str += '</a>';
+  }
+  str += '</td>';
+
+  /* The message date */
+  str += '<td class="date">';
+  if (linked) {
+    str += msg.getElementsByTagName('date')[0].firstChild.data;
+  }
+  str += '</td>';
+
+  str += '</tr>';
+
+  return str;
+}
+
+/* Toggle the view of a message. Change the message list into a
+ * context view, and call message view table creation. */
+function toggleMessage (msg_num)
+{
+  var msgs = _msglist.getElementsByTagName('message');
+  var id = msgs[msg_num].getAttribute('id');
+  var i;
+
+  var min = msg_num - _default_ctx;
+  var max = msg_num + _default_ctx;
+
+  /* Check context id values */
+  if (min < 0) {
+    min = 0;
+  }
+
+  /* Destroy previous message list table */
+  if (document.getElementById('msglist')) {
+    body.removeChild(document.getElementById('msglist'));
+  }
+
+  var msglist = document.createElement('table');
+  msglist.setAttribute('id', 'msglist');
+
+  var str = getMsgListHeader ();
+  str += '<tbody>';
+
+  /* Build list */
+  for (i=min ; i<=max ; i++) {
+    if (msgs[i]) {
+      str += buildMessageListEntry (msgs[i], i);
+    }
+  }
+
+  str += '</tbody></table>';
+  msglist.innerHTML = str;
+  body.appendChild(msglist);
+  document.getElementById(id).setAttribute('class', 'msgactive');
+
+  /* Display a loading box ... */
+  body.innerHTML += '<div id="loading">Loading ...</div>';
+
+  /* Now, display the message */
+  if (document.getElementById('msgview')) {
+    body.removeChild(document.getElementById('msgview'));
+  }
+
+  if (!getMessage (id)) {
+    body.innerHTML += '<table class="js" id="msgview">' +
+      '<thead><tr><th>Message view</th></tr></thead>' +
+      '<tbody><tr><td>Unable to load message ' + id + ' !</td></tr></tbody>' +
+      '</table>';
+  }
+}
+
+function getMessage (id)
+{
+  var message_http = getHTTPObject ();
+
+  if (!message_http) {
+    return false;
+  }
+
+  /* The handler */
+  message_http.onreadystatechange = function () {
+    if (message_http.readyState == 4) { /* 4 : "complete" */
+      if (message_http.status == 200) { /* 200 : OK */
+	var message = message_http.responseXML.documentElement;
+
+	/* Create the destination table */
+	var msgview = document.createElement('table');
+	msgview.setAttribute('id', 'msgview');
+	msgview.setAttribute('class', 'js');
+
+	var str = '<thead><tr><th class="title">Message view</th><th class="nav">' +
+	'<a href="browser" onclick="javascript:closeMessage(); return false;">x</a>' +
+	'</th></tr></thead>';
+	str += '<tbody>';
+
+	str += '<tr class="from"><td class="left">From</td><td class="right">' +
+	message.getElementsByTagName('from')[0].firstChild.data + '</td></tr>';
+	str += '<tr class="subject"><td class="left">Subject</td><td class="right">' +
+	message.getElementsByTagName('subject')[0].firstChild.data + '</td></tr>';
+	str += '<tr class="date"><td class="left">Date</td><td class="right">' +
+	message.getElementsByTagName('date')[0].firstChild.data + '</td></tr>';
+
+	str += '<tr class="contents"><td colspan="2"><pre>' +
+	message.getElementsByTagName('contents')[0].firstChild.data + '</pre></td></tr>';
+
+	/* MIME structure */
+	var mime = message.getElementsByTagName('mime')[0].childNodes;
+
+	str += '<tr class="mime"><td class="left">Mime</td><td class="right"><ul>' +
+	parseMimeStructure (mime) + '</ul></td></tr>';
+
+	str += '<tr class="raw"><td class="left"></td><td class="right">' +
+	'<a href="' + _baseURI + _mbox + '/raw?' + id + '" target="_blank">View raw message</a></td></tr>';
+
+	str += '</tbody>';
+	msgview.innerHTML = str;
+
+	/* Place our msgview just under the msglist */
+	msgview.style.top = document.getElementById('msglist').offsetHeight + 'px';
+	msgview.style.display = 'table';
+
+	/* Remove now useless loading message */
+	body.removeChild(document.getElementById('loading'));
+
+	/* And display the msgview */
+	body.appendChild(msgview);
+	document.documentElement.scrollTop = 0;
+      }
+    }
+  }
+
+  message_http.open('GET', 'ajax/' + id, true);
+  message_http.send(null);
+
   return true;
 }
 
-/* onLoad function for browser.
- *
+function parseMimeStructure (mime)
+{
+  var i = 0;
+  var str = '';
+
+  while (mime[i]) {
+
+    /* If the node is a MIME part, output its entry */
+    if (mime[i].nodeName == "part") {
+      str += '<li>';
+
+      var name = mime[i].getAttribute('name');
+      if (name) {
+	str += 'name';
+      }
+      else {
+	str += 'Unnamed ' + mime[i].getAttribute('ct');
+      }
+
+      str += ' (' + mime[i].getAttribute('cd') + ', ' +
+	mime[i].getAttribute('cte') + ', ' +
+	mime[i].getAttribute('length') + ' bytes)</li>';
+    }
+
+    /* Otherwise it's a MIME multipart, recurse */
+    else if (mime[i].nodeName == "mime") {
+      str += '<ul>' + parseMimeStructure (mime[i].childNodes) + '</ul>';
+    }
+    i++;
+  }
+
+  return str;
+}
+
+function closeMessage ()
+{
+  /* First, destroy the message view window */
+  if (document.getElementById('msgview')) {
+    body.removeChild(document.getElementById('msgview'));
+  }
+
+  /* Next, destroy previous message list table */
+  if (document.getElementById('msglist')) {
+    body.removeChild(document.getElementById('msglist'));
+  }
+
+  /* Finally, redraw full message list */
+  drawFullMsgList ();
+}
+
+/* Retreive and parse the box list.
  */
-function loadBrowser ()
+function getBoxList ()
 {
-  var body;
+  var boxlist_http = getHTTPObject ();
+
+  if (!boxlist_http) {
+    return false;
+  }
+
+  /* The handler */
+  boxlist_http.onreadystatechange = function () {
+    if (boxlist_http.readyState == 4) { /* 4 : "complete" */
+      if (boxlist_http.status == 200) { /* 200 : OK */
+	_boxlist = boxlist_http.responseXML.documentElement;
+
+	/* Get an array of all mbox entries */
+	var boxes = _boxlist.getElementsByTagName('mbox');
 
-  body = document.getElementsByTagName('body');
-  body[0].innerHTML = body[0].innerHTML + '<p id="loading">Loading ...</p>';
+	/* Create the destination table */
+	var boxlist = document.createElement('table');
+	boxlist.setAttribute('id', 'boxlist');
+
+	var str = '<thead><tr><th colspan="2">Box list</th></tr></thead>';
+	str += '<tbody>';
+
+	/* Parse boxes array */
+	var i = 0;
+	while (boxes[i]) {
+	  str += buildBoxListEntry (boxes[i]);
+	  i++;
+	}
+
+	str += '</tbody>';
+	boxlist.innerHTML = str;
+	body.appendChild(boxlist);
+      }
+    }
+  }
+
+  boxlist_http.open('GET', 'ajax/boxlist', true);
+  boxlist_http.send(null);
 
   return true;
+}
+
+function buildBoxListEntry (box)
+{
+  var id = box.getAttribute('id');
+  var str = '';
+
+  /* If the mbox id is the same as the one who called the
+     browser, set the entry as active */
+  if (id == _mbox) {
+    str += '<tr id="boxactive">';
+  }
+  else {
+    str += '<tr>';
+  }
+
+  /* Build link (_baseURI/id/browser) */
+  str += '<td class="box"><a href="' + _baseURI + id + '/browser">';
+
+  /* Display month name (short text) and year. The decimal
+     base (10) is passed to parseInt in order to avoid octal
+     parsing due to leading 0. */
+  str += getMonthName(parseInt(id.substr(4, 2), 10), false) +
+    ' ' + id.substr(0, 4) + '</a></td>';
+
+  /* Finally end the entry with the message count */
+  str += '<td class="msgcount">' + box.getAttribute('count') +
+    '</td></tr>';
+
+  return str;
 }

Modified: httpd/mod_mbox/branches/httpd-mbox-if/data/asf_logo_simple.png
URL: http://svn.apache.org/viewcvs/httpd/mod_mbox/branches/httpd-mbox-if/data/asf_logo_simple.png?rev=240348&r1=240347&r2=240348&view=diff
==============================================================================
Binary files - no diff available.

Modified: httpd/mod_mbox/branches/httpd-mbox-if/data/style.css
URL: http://svn.apache.org/viewcvs/httpd/mod_mbox/branches/httpd-mbox-if/data/style.css?rev=240348&r1=240347&r2=240348&view=diff
==============================================================================
--- httpd/mod_mbox/branches/httpd-mbox-if/data/style.css (original)
+++ httpd/mod_mbox/branches/httpd-mbox-if/data/style.css Fri Aug 26 14:33:07 2005
@@ -1,3 +1,5 @@
+html { margin: 0; padding: 0; }
+
 body
 {
   margin: 0;
@@ -14,8 +16,8 @@
 h1
 {
   font-size: xx-large;
-  margin: 0.6em 2%;
-  padding: 0 0 0.2em 0;
+  margin: 0.5em 2%;
+  padding: 0;
 }
 
 p#lastupdated
@@ -28,10 +30,19 @@
   font-style: italic;
 }
 
-p#loading
+div#loading
 {
+  border: 2px #900 solid;
+  background: #ccc;
   text-align: center;
   font-style: italic;
+
+  position: absolute;
+  top: 65%;
+  left: 30%;
+  right: 30%;
+
+  padding: 2em;
 }
 
 table
@@ -44,7 +55,7 @@
   border-top: none;
 }
 
-table thead th
+table thead th, table tfoot th
 {
   font-size: 110%;
   font-weight: bold;
@@ -55,8 +66,8 @@
   color: white;
 }
 
-table thead th a { color: white; }
-table td { padding: 0.3em 0.5em; }
+table thead th a, table tfoot th a { color: white; }
+table td { padding: 0.2em 0.5em; }
 
 table#grid
 {
@@ -88,7 +99,7 @@
 
 table.year tr td.date     { background: #ddd; width: 20%;
                             text-align: center; padding-right: 1em;
-                            font-weight: bold; }
+                            font-weight: bold; white-space: nowrap; }
 table.year tr td.links    { background: #eee; width: 70%; }
 table.year tr td.msgcount { background: #ddd; width: 10%; text-align: center; }
 
@@ -102,7 +113,7 @@
 table#boxlist
 {
   position: absolute;
-  top: 7em;
+  top: 6em;
   left: 2%;
   right: 88%;
   width: 10%;
@@ -119,9 +130,6 @@
   border-bottom: 1px #888 solid;
 }
 
-table#boxlist tr.newyear  { border-top: 1px #888 solid; border-bottom: 1px #888 solid;
-                            text-align: center; font-weight: bold; background: white; }
-
 table#boxlist td.msgcount { text-align: right; }
 table#boxlist td.box      { white-space: nowrap; }
 table#boxlist td.box a    { display: block;    }
@@ -131,9 +139,9 @@
 table#msglist
 {
   position: absolute;
-  top: 7em;
+  top: 6em;
   margin: 0 2% 2em auto;
-  width: 80%;
+  width: 82%;
 }
 
 table#msglist thead th.title { text-align: left; white-space: nowrap; }
@@ -148,14 +156,11 @@
                               text-align: right; font-style: italic;
                               white-space: nowrap; }
 
-table#msglist tr#msgactive
-{
-  border-top: 1px #888 solid;
-}
+table#msglist tr.msgactive  { border-top: 1px #888 solid; }
 
-table#msglist tr#msgactive td.author,
-table#msglist tr#msgactive td.subject,
-table#msglist tr#msgactive td.date { background: white; }
+table#msglist tr.msgactive td.author,
+table#msglist tr.msgactive td.subject,
+table#msglist tr.msgactive td.date { background: white; }
 
 table#msglist tr:hover td.author  { background: #eee;  }
 table#msglist tr:hover td.subject { background: white; }
@@ -163,16 +168,27 @@
 
 /** Message view
    */
-table#msgview
+table.static#msgview
 {
   position: absolute;
-  top: 7em;
+  top: 6em;
   margin: 0 2% 2em 2%;
   width: 96%;
 }
 
-table#msgview thead th.title { text-align: left; white-space: nowrap; }
-table#msgview thead th.nav   { text-align: right; padding-right: 0.3em; }
+table.js#msgview
+{
+  position: absolute;
+  top: 0;
+  margin: 5em 2% 2em auto;
+  width: 82%;
+  display: none;
+}
+
+table#msgview thead th.title,
+table#msgview tfoot th.title { text-align: left; white-space: nowrap; }
+table#msgview thead th.nav,
+table#msgview tfoot th.nav   { text-align: right; padding-right: 0.3em; }
 
 table#msgview tr td.left   { background: #ddd; width: 10%;
                              text-align: right; padding-right: 1em; font-weight: bold; }
@@ -180,7 +196,7 @@
 
 table#msgview tr.raw td.right { font-family: sans-serif; }
 
-table#msgview tr.contents  { border-top: 1px #888 solid; }
+table#msgview tr.contents  { border-top: 1px #888 solid; background: white; }
 table#msgview tr.contents pre { white-space: pre-wrap; }
 
 table#msgview tr.mime { vertical-align: top; }

Modified: httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod-mbox-util.c
URL: http://svn.apache.org/viewcvs/httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod-mbox-util.c?rev=240348&r1=240347&r2=240348&view=diff
==============================================================================
--- httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod-mbox-util.c (original)
+++ httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod-mbox-util.c Fri Aug 26 14:33:07 2005
@@ -128,7 +128,7 @@
     apr_dir_t *dir;
     apr_finfo_t finfo;
     apr_array_header_t* files;
-    char* file;
+    char* file = NULL;
     char* ml;
     char* domain;
     char* list;
@@ -178,7 +178,14 @@
                         files->nelts);
     }
 
-    file = ((char**)files->elts)[0];
+    /* Look for first non-empty file. */
+    for (i = 0; i < files->nelts; i++) {
+      file = ((char**)files->elts)[i];
+      rv = apr_stat(&finfo, file, APR_FINFO_SIZE, mpool);
+      if (rv == APR_SUCCESS && finfo.size > 0) {
+        break;
+      }
+    }
 
     if (verbose) {
         apr_file_printf(errfile, "Scaning %s for Mailing List info" NL,

Modified: httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox.c
URL: http://svn.apache.org/viewcvs/httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox.c?rev=240348&r1=240347&r2=240348&view=diff
==============================================================================
--- httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox.c (original)
+++ httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox.c Fri Aug 26 14:33:07 2005
@@ -132,6 +132,33 @@
     return to;
 }
 
+/* Wrap text to MBOX_WRAP_TO. Changes passed string. */
+char *mbox_wrap_text(char *str)
+{
+    int i, pos;
+
+    if (!str || (strlen(str) < MBOX_WRAP_TO))
+      return str;
+
+    for (i=0, pos=0; i<strlen(str); i++, pos++) {
+        /* Reset the position counter if we pass a newline character */
+      if (str[i] == '\n') {
+	  pos = 0;
+      }
+
+      /* If the position counter is after the wrap limit, wrap text at
+	 first space available */
+      if ((pos >= MBOX_WRAP_TO) &&
+	  ((str[i] == ' ') || (str[i] == '\t'))) {
+	  str[i] = '\n';
+	  pos = 0;
+      }
+    }
+
+    return str;
+}
+
+/* Returns the archives base path */
 char *get_base_path(request_rec *r)
 {
     char *baseURI, *temp;

Modified: httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox.h
URL: http://svn.apache.org/viewcvs/httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox.h?rev=240348&r1=240347&r2=240348&view=diff
==============================================================================
--- httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox.h (original)
+++ httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox.h Fri Aug 26 14:33:07 2005
@@ -24,12 +24,13 @@
 #include "http_protocol.h"
 #include "http_request.h"
 #include "util_script.h"
-#include "apr_date.h"
 
+#include "apr_date.h"
 #include "apr_strings.h"
 #include "apr_dbm.h"
 #include "apr_hash.h"
 #include "apr_fnmatch.h"
+#include "apr_xlate.h"
 
 #include <stdio.h>
 #include <ctype.h>
@@ -65,6 +66,8 @@
 #define MBOX_OUTPUT_STATIC 0
 #define MBOX_OUTPUT_AJAX   1
 
+#define MBOX_WRAP_TO 90
+
 typedef struct mbox_dir_cfg {
     int enabled;
     int antispam;
@@ -130,6 +133,7 @@
 void mbox_mime_display_xml_structure(request_rec *r, mbox_mime_message_t *m);
 
 /* Utility functions */
+char *mbox_wrap_text(char *str);
 char *get_base_path(request_rec *r);
 char *get_base_uri(request_rec *r);
 

Modified: httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_cte.c
URL: http://svn.apache.org/viewcvs/httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_cte.c?rev=240348&r1=240347&r2=240348&view=diff
==============================================================================
--- httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_cte.c (original)
+++ httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_cte.c Fri Aug 26 14:33:07 2005
@@ -254,11 +254,17 @@
  *
  * These headers complies to the following syntax :
  * =?charset?mode?data?= rest
+ *
+ * FIXME: does not support multiple blocks.
+ *        possible mis-use of decoding functions.
  */
 char *mbox_cte_decode_header(apr_pool_t *p, char *src)
 {
+    apr_xlate_t *xlate;
     apr_size_t len;
+
     char *charset, *mode, *data, *rest;
+    int i;
 
     /* Look for the end bound */
     rest = strstr(src, "?=");
@@ -311,6 +317,34 @@
     else if ((*mode == 'b') || (*mode == 'B')) {
         len = mbox_cte_decode_b64(data);
 	data[len] = 0;
+    }
+
+    /* Convert charset to uppercase */
+    for (i=0; i<strlen(charset); i++) {
+        charset[i] = toupper(charset[i]);
+    }
+
+    /* Charset conversion */
+    if (apr_xlate_open(&xlate, "UTF-8", charset, p) == APR_SUCCESS) {
+      apr_size_t inbytes_left, outbytes_left;
+      apr_size_t outbuf_len = strlen(data);
+
+      char *new_data;
+
+      /* Allocate some memory for our resulting data, and initialize
+	 counters. */
+      new_data = apr_palloc(p, outbuf_len);
+      inbytes_left = strlen(data);
+      outbytes_left = strlen(data);
+
+      /* Convert */
+      //      apr_xlate_conv_buffer(xlate, data, &inbytes_left,
+      //		    new_data, &outbytes_left);
+
+      //      new_data[outbuf_len - outbytes_left] = 0;
+      //      data = new_data;
+
+      apr_xlate_close(xlate);
     }
 
     return apr_psprintf(p, "%s%s", data, rest);

Modified: httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_file.c
URL: http://svn.apache.org/viewcvs/httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_file.c?rev=240348&r1=240347&r2=240348&view=diff
==============================================================================
--- httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_file.c (original)
+++ httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_file.c Fri Aug 26 14:33:07 2005
@@ -164,6 +164,8 @@
 
     char **context = apr_palloc(r->pool, 4*sizeof(char *));
 
+    memset(context, 0, 4*sizeof(char *));
+
     /* First, set the MBOX_PREV and MBOX_NEXT IDs */
     head = mbox_load_index(r, f, NULL);
     head = mbox_sort_list(head, MBOX_SORT_DATE);

Modified: httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_index.c
URL: http://svn.apache.org/viewcvs/httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_index.c?rev=240348&r1=240347&r2=240348&view=diff
==============================================================================
--- httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_index.c (original)
+++ httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_index.c Fri Aug 26 14:33:07 2005
@@ -109,10 +109,10 @@
     /* Open mbox cache */
     rv = mbox_cache_get(&mli, r->filename, r->pool);
     if (rv != APR_SUCCESS) {
-        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
+        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r,
                        "mod_mbox: Can't open directory cache '%s' for index",
                        r->filename);
-        return HTTP_FORBIDDEN;
+        return DECLINED;
     }
 
     ap_set_content_type(r, "text/html; charset=utf-8");
@@ -144,7 +144,7 @@
 
     ap_rputs(" </head>\n\n", r);
 
-    ap_rputs(" <body onload=\"indexLinks ();\">\n", r);
+    ap_rputs(" <body id=\"archives\" onload=\"indexLinks ();\">\n", r);
     ap_rputs("  <h1>Mailing list archives</h1>\n\n", r);
 
     /* Output header and list information */

Modified: httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_mime.c
URL: http://svn.apache.org/viewcvs/httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_mime.c?rev=240348&r1=240347&r2=240348&view=diff
==============================================================================
--- httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_mime.c (original)
+++ httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_mime.c Fri Aug 26 14:33:07 2005
@@ -28,6 +28,13 @@
 {
     mbox_mime_message_t *mail;
     char *tmp = NULL, *k = NULL, *end_bound = NULL;
+    char *headers_bound = NULL;
+
+    /* Locate the end of part headers */
+    headers_bound = strstr(body, "\n\n");
+    if (!headers_bound) {
+      return NULL;
+    }
 
     /* If no Content-Type is provided, it means that we are parsing a
        sub-part of the multipart message. The Content-Type header
@@ -46,7 +53,7 @@
 
 	/* Isolate the Content-Type string (between 'Content-Type: '
 	   and ';' or end of line */
-	if (k) {
+	if (k && k < headers_bound) {
 	    *k = 0;
 	}
 	else {
@@ -66,7 +73,7 @@
 
 	/* If available, get MIME part name */
 	tmp = strstr(body, "name=\"");
-	if (tmp) {
+	if (tmp && tmp < headers_bound) {
 	    tmp += strlen("name=\"");
 	    k = tmp;
 
@@ -87,9 +94,9 @@
 
     /* Now we have a Content-Type. Look for other useful header information */
 
-    /* Check Content-Disposition */
+    /* Check Content-Disposition if the match is within the headers */
     tmp = strstr(body, "Content-Disposition: ");
-    if (tmp) {
+    if (tmp && tmp < headers_bound) {
 	tmp += strlen("Content-Disposition: ");
 	k = tmp;
 
@@ -113,7 +120,7 @@
     if (cte == CTE_NONE)
       {
 	  tmp = strstr(body, "Content-Transfer-Encoding: ");
-	  if (tmp) {
+	  if (tmp && tmp < headers_bound) {
 	      tmp += strlen("Content-Transfer-Encoding: ");
 	      k = tmp;
 
@@ -235,7 +242,7 @@
 	}
 
 	/* Finally reset the end-body pointer. */
-	*k = '\n';
+	//	*tmp = '-';
       }
 
     /* If the parsed body is not multipart or is a MIME part, the body
@@ -264,11 +271,17 @@
 	char *new_body = NULL;
 
         if (m->cte == CTE_BASE64) {
-            m->body_len = mbox_cte_decode_b64(m->body);
+	    new_body = apr_pstrndup(p, m->body, m->body_len);
+            m->body_len = mbox_cte_decode_b64(new_body);
+
+	    m->body = new_body;
 	    m->body[m->body_len] = 0;
 	}
 	else if (m->cte == CTE_QP) {
+  	    new_body = apr_pstrndup(p, m->body, m->body_len);
             m->body_len = mbox_cte_decode_qp(m->body);
+
+	    m->body = new_body;
 	    m->body[m->body_len] = 0;
 	}
 
@@ -302,13 +315,13 @@
     ap_rprintf(r, "<li><a href=\"%s\">", link);
 
     if (m->content_name) {
-        ap_rprintf(r, "%s</a>", m->content_name);
+        ap_rprintf(r, "%s", m->content_name);
     }
     else {
-        ap_rprintf(r, "Unnamed %s</a>", m->content_type);
+        ap_rprintf(r, "Unnamed %s", m->content_type);
     }
 
-    ap_rprintf(r, " (%s, %s, %u bytes)</li>\n", m->content_disposition,
+    ap_rprintf(r, "</a> (%s, %s, %u bytes)</li>\n", m->content_disposition,
 	       mbox_cte_to_char(m->cte), m->body_len);
 
     if (!m->sub) {
@@ -318,7 +331,7 @@
     for (i=0 ; i<m->sub_count ; i++) {
         ap_rputs("<ul>\n", r);
 	mbox_mime_display_static_structure(r, m->sub[i],
-					   apr_psprintf(r->pool, "%s%d/", link, i));
+					   apr_psprintf(r->pool, "%s/%d", link, i+1));
 	ap_rputs("</ul>\n", r);
     }
 }
@@ -347,9 +360,9 @@
         return;
     }
 
+    ap_rputs("<mime>\n", r);
     for (i=0 ; i<m->sub_count ; i++) {
-        ap_rputs("<mime>\n", r);
-	mbox_mime_display_xml_structure(r, m->sub[i]);
-	ap_rputs("</mime>\n", r);
+        mbox_mime_display_xml_structure(r, m->sub[i]);
     }
+    ap_rputs("</mime>\n", r);
 }

Modified: httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_out.c
URL: http://svn.apache.org/viewcvs/httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_out.c?rev=240348&r1=240347&r2=240348&view=diff
==============================================================================
--- httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_out.c (original)
+++ httpd/mod_mbox/branches/httpd-mbox-if/module-2.0/mod_mbox_out.c Fri Aug 26 14:33:07 2005
@@ -87,7 +87,7 @@
     return APR_SUCCESS;
 }
 
-/* Outputs an XML list of available mailboxes */
+/* Outputs a statix XHTML list of available mailboxes */
 apr_status_t mbox_static_boxlist(request_rec *r)
 {
     apr_status_t rv = APR_SUCCESS;
@@ -290,17 +290,16 @@
         from = email_antispam(from);
     }
 
-    ap_rputs("   <tr>\n", r);
-
     /* Message author */
     if (linked) {
+        ap_rprintf(r, "   <tr id=\"%s\">\n", m->msgID);
         ap_rprintf(r, "    <td class=\"author\">%s</td>\n", from);
     }
     else {
+        ap_rputs("   <tr>\n", r);
         ap_rputs("    <td class=\"author\"></td>\n", r);
     }
 
-
     /* Subject, linked or not */
     ap_rputs("     <td class=\"subject\">", r);
     for (i=0 ; i < depth ; i++) {
@@ -414,10 +413,7 @@
     int count = 0;         /* Message count */
     int i = 0;
 
-    char *baseURI;
-
     conf = ap_get_module_config(r->per_dir_config, &mbox_module);
-    baseURI = get_base_uri(r);
 
     /* Fetch page number if present. Otherwise, assume page #1 */
     if (r->args && strcmp(r->args, ""))
@@ -456,10 +452,8 @@
 
     /* Send page header */
     ap_rputs("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n", r);
-    ap_rprintf(r, "<index month=\"%.2s\" year=\"%.4s\" page=\"%d\" pages=\"%d\">\n",
-	       baseURI + (strlen(baseURI) - strlen(".mbox") - 2),
-	       baseURI + (strlen(baseURI) - strlen(".mbox") - 6),
-	       current_page + 1, pages);
+    ap_rprintf(r, "<index page=\"%d\" pages=\"%d\">\n",
+	       current_page, pages);
 
     /* For date and author sorts */
     if (sortFlags != MBOX_SORT_THREAD) {
@@ -607,26 +601,41 @@
     }
 
     ap_rputs(" </head>\n\n", r);
-    ap_rputs(" <body>\n", r);
-
-    ap_rputs("  <h1>Mailing list archives</a></h1>\n\n", r);
+    ap_rputs(" <body id=\"archives\">\n", r);
 
-    ap_rputs("  <table id=\"msglist\">\n", r);
-    ap_rprintf(r, "   <thead><tr><th class=\"title\">Message list : %s %.4s</th>",
+    ap_rprintf(r, "  <h1>Mailing list archives: %s %.4s</a></h1>\n\n",
 	       mbox_months[atoi(apr_pstrndup(r->pool, baseURI +
 					     (strlen(baseURI) -
 					      strlen(".mbox") - 2), 2)) - 1][1],
 	       baseURI + (strlen(baseURI) - strlen(".mbox") - 6));
 
+    ap_rputs("  <table id=\"msglist\">\n", r);
+    ap_rputs("   <thead><tr><th class=\"title\">Message list</th>", r);
+
     ap_rputs("<th class=\"pages\">", r);
     mbox_static_msglist_page_selector(r, baseURI, pages, current_page);
     ap_rputs("</th>", r);
 
-    ap_rprintf(r, "<th class=\"sort\">"
-	       "<a href=\"%s/thread\">Thread</a> &middot; "
-	       "<a href=\"%s/author\">Author</a> &middot; "
-	       "<a href=\"%s/date\">Date</a></th></tr></thead>\n",
-	       baseURI, baseURI, baseURI);
+    ap_rputs("<th class=\"sort\">", r);
+    if (sortFlags == MBOX_SORT_THREAD) {
+        ap_rprintf(r, "Thread &middot; "
+		   "<a href=\"%s/author\">Author</a> &middot; "
+		   "<a href=\"%s/date\">Date</a>",
+		   baseURI, baseURI);
+    }
+    else if (sortFlags == MBOX_SORT_AUTHOR) {
+        ap_rprintf(r, "<a href=\"%s/thread\">Thread</a> &middot; "
+		   "Author &middot; "
+		   "<a href=\"%s/date\">Date</a>",
+		   baseURI, baseURI);
+    }
+    else {
+        ap_rprintf(r, "<a href=\"%s/thread\">Thread</a> &middot; "
+		   "<a href=\"%s/author\">Author</a> &middot; "
+		   "Date",
+		   baseURI, baseURI);
+    }
+    ap_rputs("</th></tr></thead>\n", r);
 
     ap_rputs("   <tbody>\n", r);
 
@@ -684,8 +693,10 @@
 apr_status_t mbox_ajax_browser(request_rec *r)
 {
     mbox_dir_cfg_t *conf;
+    char *baseURI;
 
     conf = ap_get_module_config(r->per_dir_config, &mbox_module);
+    baseURI = get_base_uri(r);
 
     ap_set_content_type(r, "text/html; charset=utf-8");
 
@@ -711,7 +722,7 @@
 
     ap_rputs(" </head>\n\n", r);
 
-    ap_rputs(" <body onload=\"javascript:loadBrowser ();\">\n", r);
+    ap_rprintf(r, " <body id=\"archives\" onload=\"javascript:loadBrowser ('%s');\">\n", baseURI);
     ap_rputs("  <h1>Mailing list archives</h1>\n\n", r);
 
     /* Output a small notice if no MboxScriptPath configuration
@@ -766,6 +777,62 @@
     return OK;
 }
 
+void mbox_static_message_nav(request_rec *r, char **context,
+			     char *baseURI, char *msgID)
+{
+    ap_rputs("    <th class=\"nav\">", r);
+
+    /* Date navigation */
+    if (context[0]) {
+        ap_rprintf(r, "<a href=\"%s/%s\" "
+		   "title=\"Previous by date\">&laquo;</a>",
+		   baseURI, context[0]);
+    }
+    else {
+        ap_rputs("&laquo;", r);
+    }
+
+    ap_rprintf(r, " <a href=\"%s/date#%s\" "
+	       "title=\"View messages sorted by date\">Date</a> ",
+	       baseURI, msgID);
+
+    if (context[1]) {
+        ap_rprintf(r, "<a href=\"%s/%s\" "
+		   "title=\"Next by date\">&raquo;</a>",
+		   baseURI, context[1]);
+    }
+    else {
+        ap_rputs("&raquo;", r);
+    }
+
+    ap_rputs(" &middot; ", r);
+
+    /* Thread navigation */
+    if (context[2]) {
+        ap_rprintf(r, "<a href=\"%s/%s\" "
+		   "title=\"Previous by thread\">&laquo;</a>",
+		   baseURI, context[2]);
+    }
+    else {
+        ap_rputs("&laquo;", r);
+    }
+
+    ap_rprintf(r, " <a href=\"%s/thread#%s\" "
+	       "title=\"View messages sorted by thread\">Thread</a> ",
+	       baseURI, msgID);
+
+    if (context[3]) {
+        ap_rprintf(r, "<a href=\"%s/%s\" "
+		   "title=\"Next by thread\">&raquo;</a>",
+		   baseURI, context[3]);
+    }
+    else {
+        ap_rputs("&raquo;", r);
+    }
+
+    ap_rputs("</th>\n", r);
+}
+
 /* Display a static XHTML mail */
 apr_status_t mbox_static_message(request_rec *r, apr_file_t *f)
 {
@@ -803,7 +870,7 @@
     }
 
     ap_rputs(" </head>\n\n", r);
-    ap_rputs(" <body>\n", r);
+    ap_rputs(" <body id=\"archives\">\n", r);
     ap_rputs("  <h1>Mailing list archives</h1>\n\n", r);
 
     /* Display context message list */
@@ -813,66 +880,26 @@
         from = email_antispam(from);
     }
 
-    ap_rputs("  <table id=\"msgview\">\n", r);
+    ap_rputs("  <table class=\"static\" id=\"msgview\">\n", r);
 
     context = fetch_context_msgids(r, f, m->msgID);
-    ap_rputs("   <thead>\n"
-	     "   <tr>\n"
-	     "    <th class=\"title\">Message view</th>\n"
-	     "    <th class=\"nav\">", r);
-
-    /* Date navigation */
-    if (context[0]) {
-        ap_rprintf(r, "<a href=\"%s/%s\" "
-		   "title=\"Previous by date\">&laquo;</a>",
-		   baseURI, context[0]);
-    }
-    else {
-        ap_rputs("&laquo;", r);
-    }
-
-    ap_rprintf(r, " <a href=\"%s/date\" "
-	       "title=\"View messages sorted by date\">Date</a> ",
-	       baseURI);
-
-    if (context[1]) {
-        ap_rprintf(r, "<a href=\"%s/%s\" "
-		   "title=\"Next by date\">&raquo;</a>",
-		   baseURI, context[1]);
-    }
-    else {
-        ap_rputs("&raquo;", r);
-    }
 
-    ap_rputs(" &middot; ", r);
-
-    /* Thread navigation */
-    if (context[2]) {
-        ap_rprintf(r, "<a href=\"%s/%s\" "
-		   "title=\"Previous by thread\">&laquo;</a>",
-		   baseURI, context[2]);
-    }
-    else {
-        ap_rputs("&laquo;", r);
-    }
-
-    ap_rprintf(r, " <a href=\"%s/thread\" "
-	       "title=\"View messages sorted by thread\">Thread</a> ",
-	       baseURI);
-
-    if (context[3]) {
-        ap_rprintf(r, "<a href=\"%s/%s\" "
-		   "title=\"Next by thread\">&raquo;</a>",
-		   baseURI, context[3]);
-    }
-    else {
-        ap_rputs("&raquo;", r);
-    }
-
-    ap_rputs("</th>\n"
-	     "   </tr>\n"
+    /* Top navigation */
+    ap_rputs("   <thead>\n"
+	     "    <tr>\n"
+	     "    <th class=\"title\">Message view</th>\n", r);
+    mbox_static_message_nav(r, context, baseURI, m->msgID);
+    ap_rputs("   </tr>\n"
 	     "   </thead>\n\n", r);
 
+    /* Bottom navigation */
+    ap_rputs("   <tfoot>\n"
+	     "    <tr>\n"
+	     "    <th class=\"title\"><a href=\"#archives\">Top</a></th>\n", r);
+    mbox_static_message_nav(r, context, baseURI, m->msgID);
+    ap_rputs("   </tr>\n"
+	     "   </tfoot>\n\n", r);
+
     /* Headers */
     ap_rputs("   <tbody>\n", r);
     ap_rprintf(r, "   <tr class=\"from\">\n"
@@ -890,14 +917,9 @@
 	       "    <td class=\"right\">%s</td>\n"
 	       "   </tr>\n", ESCAPE_OR_BLANK(r->pool, m->str_date));
 
-    ap_rprintf(r, "   <tr class=\"raw\">\n"
-	       "    <td class=\"left\"></td>\n"
-	       "    <td class=\"right\"><a href=\"%s/raw?%s\">View raw message</a></td>\n"
-	       "   </tr>\n", baseURI, URI_ESCAPE_OR_BLANK(r->pool, m->msgID));
-
     /* Message body */
     ap_rputs("   <tr class=\"contents\"><td colspan=\"2\"><pre>\n", r);
-    ap_rprintf(r, "%s", mbox_mime_get_body(r->pool, m->mime_msg));
+    ap_rprintf(r, "%s", mbox_wrap_text(mbox_mime_get_body(r->pool, m->mime_msg)));
     ap_rputs("</pre></td></tr>\n", r);
 
     /* MIME structure */
@@ -905,9 +927,15 @@
 	     "    <td class=\"left\">Mime</td>\n"
 	     "    <td class=\"right\">\n<ul>\n", r);
     mbox_mime_display_static_structure(r, m->mime_msg,
-				       apr_psprintf(r->pool, "%s/%s/",
+				       apr_psprintf(r->pool, "%s/%s",
 						    baseURI, m->msgID));
     ap_rputs("</ul>\n</td>\n</tr>\n", r);
+
+    ap_rprintf(r, "   <tr class=\"raw\">\n"
+	       "    <td class=\"left\"></td>\n"
+	       "    <td class=\"right\"><a href=\"%s/raw?%s\">View raw message</a></td>\n"
+	       "   </tr>\n", baseURI, URI_ESCAPE_OR_BLANK(r->pool, m->msgID));
+
     ap_rputs("   </tbody>\n", r);
     ap_rputs("  </table>\n", r);
 
@@ -944,7 +972,7 @@
 
     ap_rprintf(r, "<mail id=\"%s\">\n"
 	       " <from><![CDATA[%s]]></from>\n"
-	       " <subject>%s</subject>\n"
+	       " <subject><![CDATA[%s]]></subject>\n"
 	       " <date>%s</date>\n"
 	       " <contents><![CDATA[",
 	       URI_ESCAPE_OR_BLANK(r->pool, m->msgID),
@@ -952,7 +980,7 @@
 	       ESCAPE_OR_BLANK(r->pool, m->subject),
 	       ESCAPE_OR_BLANK(r->pool, m->str_date));
 
-    ap_rprintf(r, "%s", mbox_mime_get_body(r->pool, m->mime_msg));
+    ap_rprintf(r, "%s", mbox_wrap_text(mbox_mime_get_body(r->pool, m->mime_msg)));
     ap_rputs("]]></contents>\n", r);
     ap_rputs(" <mime>\n", r);
     mbox_mime_display_xml_structure(r, m->mime_msg);