Bug 479214 - Gloda JS mime emitter needs to deal with character set and encoding issues. revised v2 convert startAttachment/writeBody/write signatures on nsIMimeEmitter. r=neil,sr=bienvenu.
authorAndrew Sutherland <asutherland@asutherland.org>
Sun, 08 Mar 2009 15:25:01 -0700
changeset 2160 99320ed86776308bad01657cd5655caaf2ede04a
parent 2159 694a773f7a8a9526b97422fd45e3de9d30ce6366
child 2161 36423d81646ff720872cd053fb41457282cdcc64
push idunknown
push userunknown
push dateunknown
reviewersneil, bienvenu
bugs479214
Bug 479214 - Gloda JS mime emitter needs to deal with character set and encoding issues. revised v2 convert startAttachment/writeBody/write signatures on nsIMimeEmitter. r=neil,sr=bienvenu.
mailnews/db/gloda/components/jsmimeemitter.js
mailnews/db/gloda/modules/indexer.js
mailnews/db/gloda/test/resources/glodaTestHelper.js
mailnews/db/gloda/test/resources/messageGenerator.js
mailnews/db/gloda/test/unit/test_intl.js
mailnews/db/gloda/test/unit/test_query_messages.js
mailnews/mime/emitters/src/nsMimeBaseEmitter.cpp
mailnews/mime/emitters/src/nsMimeBaseEmitter.h
mailnews/mime/emitters/src/nsMimeHtmlEmitter.cpp
mailnews/mime/emitters/src/nsMimeHtmlEmitter.h
mailnews/mime/emitters/src/nsMimePlainEmitter.cpp
mailnews/mime/emitters/src/nsMimePlainEmitter.h
mailnews/mime/emitters/src/nsMimeRawEmitter.cpp
mailnews/mime/emitters/src/nsMimeRawEmitter.h
mailnews/mime/emitters/src/nsMimeXmlEmitter.cpp
mailnews/mime/emitters/src/nsMimeXmlEmitter.h
mailnews/mime/public/nsIMimeEmitter.idl
mailnews/mime/src/mimemoz2.cpp
mailnews/mime/src/nsStreamConverter.cpp
--- a/mailnews/db/gloda/components/jsmimeemitter.js
+++ b/mailnews/db/gloda/components/jsmimeemitter.js
@@ -375,18 +375,17 @@ MimeMessageEmitter.prototype = {
     if (this._parentMsg.partName == "")
       this._curPart.partName = "1";
     else
       this._curPart.partName = this._curMsg.partName + ".1";
     this._placePart(this._curPart);
   },
   
   writeBody: function mime_emitter_writeBody(aBuf, aSize, aOutAmountWritten) {
-    if (this._curBodyPart)
-      this._curBodyPart.body += aBuf;
+    this._curBodyPart.body += aBuf;
   },
   
   endBody: function mime_emitter_endBody() {
     this._messageStack.pop();
     this._parentMsg = this._messageStack[this._messageStack.length - 1];
   },
   
   // ----- Generic Write (confusing)
--- a/mailnews/db/gloda/modules/indexer.js
+++ b/mailnews/db/gloda/modules/indexer.js
@@ -2328,20 +2328,22 @@ var GlodaIndexer = {
   
   _indexMessage: function gloda_indexMessage(aMsgHdr, aCallbackHandle) {
     this._log.debug("*** Indexing message: " + aMsgHdr.messageKey + " : " +
                     aMsgHdr.subject);
     MsgHdrToMimeMessage(aMsgHdr, aCallbackHandle.callbackThis,
         aCallbackHandle.callback);
     let [,aMimeMsg] = yield this.kWorkAsync;
 
-    if (aMimeMsg)
-      this._log.debug("  * Got Mime Message!");
-    else
-      this._log.debug("  * Did not get body!");
+    if (this._unitTestSuperVerbose) {
+      if (aMimeMsg)
+        this._log.debug("  * Got Mime " + aMimeMsg.prettyString());
+      else
+        this._log.debug("  * NO MIME MESSAGE!!!\n");
+    }
 
     // -- Find/create the conversation the message belongs to.
     // Our invariant is that all messages that exist in the database belong to
     //  a conversation.
     
     // - See if any of the ancestors exist and have a conversationID...
     // (references are ordered from old [0] to new [n-1])
     let references = [aMsgHdr.getStringReference(i) for each
--- a/mailnews/db/gloda/test/resources/glodaTestHelper.js
+++ b/mailnews/db/gloda/test/resources/glodaTestHelper.js
@@ -215,16 +215,18 @@ function imsInit() {
     prefSvc.setBoolPref("mail.biff.show_alert", false);
     prefSvc.setBoolPref("mail.biff.show_tray_icon", false);
     prefSvc.setBoolPref("mail.biff.animate_dock_icon", false);
   
     Gloda.addIndexerListener(messageIndexerListener.onIndexNotification);
     ims.catchAllCollection = Gloda._wildcardCollection(Gloda.NOUN_MESSAGE);
     ims.catchAllCollection.listener = messageCollectionListener;
     
+    // Make the indexer be more verbose about indexing for us...
+    GlodaIndexer._unitTestSuperVerbose = true;
     // The indexer doesn't need to worry about load; zero his rescheduling time. 
     GlodaIndexer._indexInterval = 0;
     // And it doesn't need to adjust its performance, either.
     GlodaIndexer._PERF_SAMPLE_RATE_MS = 24 * 60 * 60 * 1000;
     
     if (ims.injectMechanism == INJECT_FAKE_SERVER) {
       // set up POP3 fakeserver to feed things in...
       [ims.daemon, ims.server] = setupServerDaemon();
@@ -808,16 +810,17 @@ QueryExpectationListener.prototype = {
     //  is, so let's become explicit to avoid related troubles.
     aCollection.becomeExplicit();
     
     // expectedSet should now be empty
     for each (let [key, value] in this.expectedSet) {
       do_throw("Query should have returned " + key + "(" + value + ")");
     }
     
+    dump(">>> queryCompleted, advancing to next test\n");
     next_test();
   },
 }
 
 /**
  * Execute the given query, verifying that the result set contains exactly the
  *  contents of the expected set; no more, no less.  Since we expect that the
  *  query will result in gloda objects, but your expectations will not be posed
@@ -910,18 +913,30 @@ function notifyWhenDatastoreDone(aCallba
 
 var glodaHelperTests = [];
 var glodaHelperIterator = null;
 
 function _gh_test_iterator() {
   do_test_pending();
 
   for (let iTest=0; iTest < glodaHelperTests.length; iTest++) {
-    dump("====== Test function: " + glodaHelperTests[iTest].name + "\n");
-    yield glodaHelperTests[iTest]();
+    let test = glodaHelperTests[iTest];
+    // deal with parameterized tests (via parameterizeTest)
+    if (test.length) {
+      let [testFunc, parameters] = test;
+      for each (let [, parameter] in Iterator(parameters)) {
+        dump("====== Test function: " + testFunc.name + " Parameter: " +
+             parameter.name + "\n");
+        yield testFunc(parameter);
+      }
+    }
+    else {
+      dump("====== Test function: " + test.name + "\n");
+      yield test();
+    }
   }
 
   if (indexMessageState.injectMechanism == INJECT_FAKE_SERVER) {
     killFakeServer();
   }
 
   do_test_finished();
   
@@ -948,16 +963,28 @@ function next_test() {
     do_throw("Caught an exception during execution of next_test: " + ex);
   }
   _next_test_currently_in_test = false;
 }
 
 DEFAULT_LONGEST_TEST_RUN_CONCEIVABLE_SECS = 180;
 
 /**
+ * Purely decorative function to help explain to people reading lists of tests
+ *  using glodaHelperRunTests what is going on.  We just return a tuple of our
+ *  arguments and _gh_test_iterator understands what to do with this, namely
+ *  to run the test once for each element in the aParameters list.  If the
+ *  elements in the aParameters list have a 'name' attribute, it will get
+ *  printed out to help figure out what is actually happening.
+ */
+function parameterizeTest(aTestFunc, aParameters) {
+  return [aTestFunc, aParameters];
+}
+
+/**
  * Test driving logic that takes a list of tests to run.  Every completed test
  *  needs to call (or cause to be called) next_test.
  * 
  * @param aTests A list of test functions to call.
  * @param aLongestTestRunTimeConceivableInSecs Optional parameter 
  */
 function glodaHelperRunTests(aTests, aLongestTestRunTimeConceivableInSecs) {
   if (aLongestTestRunTimeConceivableInSecs == null)
--- a/mailnews/db/gloda/test/resources/messageGenerator.js
+++ b/mailnews/db/gloda/test/resources/messageGenerator.js
@@ -1,16 +1,16 @@
 /* ***** BEGIN LICENSE BLOCK *****
  *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (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.mozilla.org/MPL/
- * 
+ *
  * Software distributed under the License is distributed on an "AS IS" basis,
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  * for the specific language governing rights and limitations under the
  * License.
  *
  * The Original Code is Thunderbird Global Database.
  *
  * The Initial Developer of the Original Code is
@@ -93,25 +93,130 @@ const SUBJECT_NOUNS = [
  *  your additions don't break the secret Monty Python reference!
  */
 const SUBJECT_SUFFIXES = [
   "Today", "Tomorrow", "Yesterday", "In a Fortnight",
   "Needs Attention", "Very Important", "Highest Priority", "Full Of Eels",
   "In The Lobby", "On Your Desk", "In Your Car", "Hiding Behind The Door",
   ];
 
+/**
+ * Base class for MIME Part representation.
+ */
+function SyntheticPart(aProperties) {
+  if (aProperties) {
+    if (aProperties.charset)
+      this._charset = aProperties.charset;
+    if (aProperties.format)
+      this._format = aProperties.format;
+    if (aProperties.filename)
+      this._filename = aProperties.filename;
+    if (aProperties.boundary)
+      this._boundary = aProperties.boundary;
+  }
+}
+SyntheticPart.prototype = {
+  get contentTypeHeaderValue() {
+    s = this._contentType;
+    if (this._charset)
+      s += '; charset=' + this._charset;
+    if (this._format)
+      s += '; format=' + this._format;
+    if (this._filename)
+      s += ';\r\n name="' + this._filename +'"'
+    if (this._boundary)
+      s += ';\r\n boundary="' + this._boundary + '"';
+    return s;
+  },
+  get hasTransferEncoding() {
+    return this._encoding;
+  },
+  get contentTransferEncodingHeaderValue() {
+    return this._encoding;
+  },
+  get hasDisposition() {
+    return this._filename;
+  },
+  get contentDispositionHeaderValue() {
+    s = '';
+    if (this._filename)
+      s += 'attachment;\r\n filename="' + this._filename + '"';
+    return s;
+  },
+};
+
+/**
+ * Leaf MIME part, defaulting to text/plain.
+ */
+function SyntheticPartLeaf(aBody, aProperties) {
+  SyntheticPart.call(this, aProperties);
+  this.body = aBody;
+}
+SyntheticPartLeaf.prototype = {
+  __proto__: SyntheticPart.prototype,
+  _contentType: 'text/plain',
+  _charset: 'ISO-8859-1',
+  _format: 'flowed',
+  _encoding: '7bit',
+  toMessageString: function() {
+    return this.body;
+  }
+}
+
+/**
+ * Multipart (multipart/*) MIME part base class.
+ */
+function SyntheticPartMulti(aParts, aProperties) {
+  SyntheticPart.call(this, aProperties);
+
+  this._boundary = '--------------CHOPCHOP' + this.BOUNDARY_COUNTER;
+  this.__proto__.BOUNDARY_COUNTER += 1;
+  this.parts = (aParts != null) ? aParts : [];
+}
+SyntheticPartMulti.prototype = {
+  __proto__: SyntheticPart.prototype,
+  BOUNDARY_COUNTER: 0,
+  toMessageString: function() {
+    s = "This is a multi-part message in MIME format.\r\n";
+    for (let [,part] in Iterator(this.parts)) {
+      s += "--" + this._boundary + "\r\n";
+      s += "Content-Type: " + part.contentTypeHeaderValue + '\r\n';
+      if (part.hasTransferEncoding)
+        s += 'Content-Transfer-Encoding: ' +
+             part.contentTransferEncodingHeaderValue + '\r\n';
+      if (part.hasDisposition)
+        s += 'Content-Disposition: ' + part.contentDispositionHeaderValue +
+             '\r\n';
+      s += '\r\n';
+      s += part.toMessageString() + '\r\n\r\n';
+    }
+    s += "--" + this._boundary + '--';
+    return s;
+  },
+};
+
+/**
+ * Multipart mixed (multipart/mixed) MIME part.
+ */
+function SyntheticPartMultiMixed() {
+  SyntheticPartMulti.apply(this, arguments);
+}
+SyntheticPartMultiMixed.prototype = {
+  __proto__: SyntheticPartMulti.prototype,
+  _contentType: 'multipart/mixed',
+}
 
 /**
  * A synthetic message, created by the MessageGenerator.  Captures both the
  *  ingredients that went into the synthetic message as well as the rfc822 form
  *  of the message.
  */
-function SyntheticMessage(aHeaders, aBody) {
+function SyntheticMessage(aHeaders, aBodyPart) {
   this.headers = aHeaders || {};
-  this.body = aBody || "";
+  this.bodyPart = aBodyPart || new SyntheticPartLeaf("");
 }
 
 SyntheticMessage.prototype = {
   /** @returns the Message-Id header value. */
   get messageId() { return this._messageId; },
   /**
    * Sets the Message-Id header value.
    *
@@ -236,31 +341,39 @@ SyntheticMessage.prototype = {
    */
   set cc(aNameAndAddresses) {
     this._cc = aNameAndAddresses;
     this.headers["Cc"] = this._commaize(
                            [this._formatMailFromNameAndAddress(nameAndAddr)
                             for each (nameAndAddr in aNameAndAddresses)]);
   },
 
+  get bodyPart() {
+    return this._bodyPart;
+  },
+  set bodyPart(aBodyPart) {
+    this._bodyPart = aBodyPart;
+    this.headers["Content-Type"] = this._bodyPart.contentTypeHeaderValue;
+  },
+
   /**
    * Normalizes header values, which may be strings or arrays of strings, into
    *  a suitable string suitable for appending to the header name/key.
    *
    * @returns a normalized string representation of the header value(s), which
    *     may include spanning multiple lines.
    */
   _formatHeaderValues: function(aHeaderValues) {
     // may not be an array
     if (!(aHeaderValues instanceof Array))
       return aHeaderValues;
     // it's an array!
     if (aHeaderValues.length == 1)
       return aHeaderValues[0];
-    return aHeaderValues.join("\n\t");
+    return aHeaderValues.join("\r\n\t");
   },
 
   /**
    * @returns a string uniquely identifying this message, at least as long as
    *     the messageId is set and unique.
    */
   toString: function() {
     return "msg:" + this._messageId;
@@ -268,17 +381,18 @@ SyntheticMessage.prototype = {
 
   /**
    * @returns this messages in rfc822 format, or something close enough.
    */
   toMessageString: function() {
     let lines = [headerKey + ": " + this._formatHeaderValues(headerValues)
                  for each ([headerKey, headerValues] in Iterator(this.headers))];
 
-    return lines.join("\n") + "\n\n" + this.body + "\n";
+    return lines.join("\r\n") + "\r\n\r\n" + this.bodyPart.toMessageString() +
+      "\r\n";
   },
 
   /**
    * @returns this message in rfc822 format in a string stream.
    */
   toStream: function () {
     let stream = Cc["@mozilla.org/io/string-input-stream;1"]
                    .createInstance(Ci.nsIStringInputStream);
@@ -287,17 +401,18 @@ SyntheticMessage.prototype = {
     return stream;
   },
 
   /**
    * Writes this message to an mbox stream.  This means adding a "From " line
    *  and making sure we've got a trailing newline.
    */
   writeToMboxStream: function (aStream) {
-    let str = "From " + this._from[1] + "\n" + this.toMessageString() + "\n";
+    let str = "From " + this._from[1] + "\r\n" + this.toMessageString() +
+      "\r\n";
     aStream.write(str, str.length);
   }
 }
 
 /**
  * Write a list of messages in mbox format to a file
  *
  * @param aMessages The list of SyntheticMessages instances to write.
@@ -361,36 +476,36 @@ MessageGenerator.prototype = {
    *
    * @param aNameNumber The 'number' of the name you want which must be less
    *     than MAX_VALID_NAMES.
    * @returns The unique name corresponding to the name number.
    */
   makeName: function(aNameNumber) {
     let iFirst = aNameNumber % FIRST_NAMES.length;
     let iLast = (iFirst + Math.floor(aNameNumber / FIRST_NAMES.length)) %
-	            LAST_NAMES.length;
-	
+                LAST_NAMES.length;
+
     return FIRST_NAMES[iFirst] + " " + LAST_NAMES[iLast];
   },
 
   /**
    * Generate a consistently determined (and reversible) e-mail address from
    *  a unique value; intended to work in parallel with makeName.  Currently
    *  up to 26*26 unique addresses can be generated, but if your code cares,
    *  check against MAX_VALID_MAIL_ADDRESSES.
    *
    * @param aNameNumber The 'number' of the mail address you want which must be
    *     less than MAX_VALID_MAIL_ADDRESSES.
    * @returns The unique name corresponding to the name mail address.
    */
   makeMailAddress: function(aNameNumber) {
     let iFirst = aNameNumber % FIRST_NAMES.length;
     let iLast = (iFirst + Math.floor(aNameNumber / FIRST_NAMES.length)) %
-	      LAST_NAMES.length;
-		
+                LAST_NAMES.length;
+
     return FIRST_NAMES[iFirst].toLowerCase() + "@" +
            LAST_NAMES[iLast].toLowerCase() + ".nul";
   },
 
   /**
    * Generate a pair of name and e-mail address.
    *
    * @param aNameNumber The optional 'number' of the name and mail address you
@@ -483,34 +598,44 @@ MessageGenerator.prototype = {
   },
 
   /**
    * Create a SyntheticMessage.  All arguments are optional, but allow
    *  additional control.  With no arguments specified, a new name/address will
    *  be generated that has not been used before, and sent to a new name/address
    *  that has not been used before.
    *
-   * @param aInReplyTo the SyntheticMessage this message should be in reply-to.
-   *     If that message was in reply to another message, we will appropriately
-   *     compensate for that.
    * @param aArgs An object with any of the following attributes provided:
+   *     attachments: A list of dictionaries suitable for passing to
+   *         syntheticPartLeaf, plus a 'body' attribute that has already been
+   *         encoded.  Line chopping is on you FOR NOW.
+   *     body: A dictionary suitable for passing to SyntheticPart plus a 'body'
+   *         attribute that has already been encoded (if encoding is required).
+   *         Line chopping is on you FOR NOW.
+   *     callerData: A value to propagate to the callerData attribute on the
+   *         resulting message.
+   *     inReplyTo: the SyntheticMessage this message should be in reply-to.
+   *         If that message was in reply to another message, we will
+   *         appropriately compensate for that.
    *     replyAll: a boolean indicating whether this should be a reply-to-all or
    *         just to the author of the message.  (er, to-only, not cc.)
+   *     subject: The subject to use; you are responsible for doing any encoding
+   *         before passing it in.
    *     toCount: the number of people who the message should be to.
    * @returns a SyntheticMessage fashioned just to your liking.
    */
-  makeMessage: function(aInReplyTo, aArgs) {
+  makeMessage: function(aArgs) {
     aArgs = aArgs || {};
     let msg = new SyntheticMessage();
 
-    if (aInReplyTo) {
-      msg.parent = aInReplyTo;
+    if (aArgs.inReplyTo) {
+      msg.parent = aArgs.inReplyTo;
       msg.parent.children.push(msg);
 
-      let srcMsg = aInReplyTo;
+      let srcMsg = aArgs.inReplyTo;
 
       msg.subject = (srcMsg.subject.substring(0, 4) == "Re: ") ? srcMsg.subject
                     : ("Re: " + srcMsg.subject);
       if (aArgs.replyAll)
         msg.to = [srcMsg.from].concat(srcMsg.to.slice(1));
       else
         msg.to = [srcMsg.from];
       msg.from = srcMsg.to[0];
@@ -518,26 +643,45 @@ MessageGenerator.prototype = {
       // we want the <>'s.
       msg.headers["In-Reply-To"] = srcMsg.headers["Message-Id"];
       msg.headers["References"] = (srcMsg.headers["References"] || []).concat(
                                    [srcMsg.headers["Message-Id"]]);
     }
     else {
       msg.parent = null;
 
-      msg.subject = this.makeSubject();
+      msg.subject = aArgs.subject || this.makeSubject();
       msg.from = this.makeNameAndAddress();
       msg.to = this.makeNamesAndAddresses(aArgs.toCount || 1);
     }
 
     msg.children = [];
     msg.messageId = this.makeMessageId(msg);
     msg.date = this.makeDate();
 
-    msg.body = "I am an e-mail.";
+    let bodyPart;
+    if (aArgs.bodyPart)
+      bodyPart = aArgs.bodyPart;
+    else if (aArgs.body)
+      bodyPart = new SyntheticPartLeaf(aArgs.body.body, aArgs.body);
+    else
+      bodyPart = new SyntheticPartLeaf("I am an e-mail.");
+
+    // if it has any attachments, create a multipart/mixed to be the body and
+    //  have it be the parent of the existing body and all the attachments
+    if (aArgs.attachments) {
+      let parts = [bodyPart];
+      for each (let [,attachDesc] in Iterator(aArgs.attachments))
+        parts.push(new SyntheticPartLeaf(attachDesc.body, attachDesc));
+      bodyPart = new SyntheticPartMultiMixed(parts);
+    }
+
+    msg.bodyPart = bodyPart;
+
+    msg.callerData = aArgs.callerData;
 
     return msg;
   }
 }
 
 /**
  * Repository of generative message scenarios.  Uses the magic bindMethods
  *  function below to allow you to reference methods/attributes without worrying
@@ -560,49 +704,49 @@ function MessageScenarioFactory(aMessage
 }
 
 MessageScenarioFactory.prototype = {
   /** Create a chain of direct-reply messages of the given length. */
   directReply: function(aNumMessages) {
     aNumMessages = aNumMessages || 2;
     let messages = [this._msgGen.makeMessage()];
     for (let i = 1; i < aNumMessages; i++) {
-      messages.push(msgGen.makeMessage(messages[i-1]));
+      messages.push(this._msgGen.makeMessage({inReplyTo: messages[i-1]}));
     }
     return messages;
   },
 
   /** Two siblings (present), one parent (missing). */
   siblingsMissingParent: function() {
     let missingParent = this._msgGen.makeMessage();
-    let msg1 = this._msgGen.makeMessage(missingParent);
-    let msg2 = this._msgGen.makeMessage(missingParent);
+    let msg1 = this._msgGen.makeMessage({inReplyTo: missingParent});
+    let msg2 = this._msgGen.makeMessage({inReplyTo: missingParent});
     return [msg1, msg2];
   },
 
   /** Present parent, missing child, present grand-child. */
   missingIntermediary: function() {
     let msg1 = this._msgGen.makeMessage();
-    let msg2 = this._msgGen.makeMessage(msg1);
-    let msg3 = this._msgGen.makeMessage(msg2);
+    let msg2 = this._msgGen.makeMessage({inReplyTo: msg1});
+    let msg3 = this._msgGen.makeMessage({inReplyTo: msg2});
     return [msg1, msg3];
   },
 
   /**
    * The root message and all non-leaf nodes have aChildrenPerParent children,
    *  for a total of aHeight layers.  (If aHeight is 1, we have just the root;
    *  if aHeight is 2, the root and his aChildrePerParent children.)
    */
   fullPyramid: function(aChildrenPerParent, aHeight) {
     let msgGen = this._msgGen;
     let root = msgGen.makeMessage();
     let messages = [root];
     function helper(aParent, aRemDepth) {
       for (let iChild = 0; iChild < aChildrenPerParent; iChild++) {
-        let child = msgGen.makeMessage(aParent);
+        let child = msgGen.makeMessage({inReplyTo: aParent});
         messages.push(child);
         if (aRemDepth)
           helper(child, aRemDepth - 1);
       }
     }
     if (aHeight > 1)
       helper(root, aHeight - 2);
     return messages;
new file mode 100644
--- /dev/null
+++ b/mailnews/db/gloda/test/unit/test_intl.js
@@ -0,0 +1,93 @@
+/*
+ * Test that i18n goes through das pipes acceptably.  Currently this means:
+ * - Subject, Body, and Attachment names are properly indexed.
+ */
+
+do_import_script("../mailnews/db/gloda/test/resources/messageGenerator.js");
+
+//these are imported by glodaTestHelper's import of head_maillocal
+// do_import_script("../mailnews/test/resources/mailDirService.js");
+// do_import_script("../mailnews/test/resources/mailTestUtils.js");
+do_import_script("../mailnews/db/gloda/test/resources/glodaTestHelper.js");
+
+// Create a message generator
+var msgGen = new MessageGenerator();
+
+/* ===== Tests ===== */
+
+var intlPhrases = [
+  {
+    name: "Vending Machine",
+    actual: '\u81ea\u52d5\u552e\u8ca8\u6a5f',
+    encodings: {
+      'utf-8': ['=?utf-8?b?6Ieq5YuV5ZSu6LKo5qmf?=',
+                '\xe8\x87\xaa\xe5\x8b\x95\xe5\x94\xae\xe8\xb2\xa8\xe6\xa9\x9f'],
+      'euc-jp': ['=?shift-jis?b?jqmTrppTid2LQA==?=',
+                 '\xbc\xab\xc6\xb0\xd3\xb4\xb2\xdf\xb5\xa1'],
+      'shift-jis': ['=?shift-jis?b?jqmTrppTid2LQA==?=',
+                    '\x8e\xa9\x93\xae\x9aS\x89\xdd\x8b@']
+    }
+  }
+];
+
+/**
+ * For each phrase in the intlPhrases array (we are parameterized over it using
+ *  parameterizeTest in the 'tests' declaration), create a message where the
+ *  subject, body, and attachment name are populated using the encodings in
+ *  the phrase's "encodings" attribute, one encoding per message.  Make sure
+ *  that the strings as exposed by the gloda representation are equal to the
+ *  expected/actual value.
+ */
+function test_index(aPhrase) {
+  // create a synthetic message for each of the delightful encoding types
+  let messages = [];
+  for each (let [charset, encodings] in Iterator(aPhrase.encodings)) {
+    let [quoted, bodyEncoded] = encodings;
+
+    let smsg = msgGen.makeMessage({
+      subject: quoted,
+      body: {charset: charset, encoding: "8bit", body: bodyEncoded},
+      attachments: [
+        {filename: quoted, body: "gabba gabba hey"},
+      ],
+      // save off the actual value for checking
+      callerData: [charset, aPhrase.actual]
+    });
+
+    messages.push(smsg);
+  }
+
+  indexMessages(messages, verify_index, next_test);
+}
+
+/**
+ * Does the per-message verification for test_index.  Knows what is right for
+ *  each message because of the callerData attribute on the synthetic message.
+ */
+function verify_index(smsg, gmsg) {
+  let [charset, actual] = smsg.callerData;
+  let subject = gmsg.subject;
+  let indexedBodyText = gmsg.indexedBodyText.trim();
+  let attachmentName = gmsg.attachmentNames[0];
+  LOG.debug("using character set: " + charset + " actual: " + actual);
+  LOG.debug("subject: " + subject + " (len: " + subject.length + ")");
+  do_check_eq(actual, subject);
+  LOG.debug("body: " + indexedBodyText +
+      " (len: " + indexedBodyText.length + ")");
+  do_check_eq(actual, indexedBodyText);
+  LOG.debug("attachment name:" + attachmentName +
+      " (len: " + attachmentName.length + ")");
+  do_check_eq(actual, attachmentName);
+}
+
+/* ===== Driver ===== */
+
+var tests = [
+  parameterizeTest(test_index, intlPhrases),
+];
+
+function run_test() {
+  // use mbox injection because the fake server chokes sometimes right now
+  injectMessagesUsing(INJECT_MBOX);
+  glodaHelperRunTests(tests);
+}
--- a/mailnews/db/gloda/test/unit/test_query_messages.js
+++ b/mailnews/db/gloda/test/unit/test_query_messages.js
@@ -68,21 +68,23 @@ function categorizeMessage(aSynthMessage
  * Generate messages in a single folder, categorizing them as we go.
  */
 function generateFolderMessages() {
   let messages = [];
   
   let iAuthor = 0;
   for (let iMessage = 0; iMessage < world.MESSAGES_PER_FOLDER; iMessage++) {
     let iConvo = iMessage % world.NUM_CONVERSATIONS;
-    let smsg = msgGen.makeMessage(world.lastMessagesInConvos[iConvo]);
+    let smsg = msgGen.makeMessage({
+      inReplyTo: world.lastMessagesInConvos[iConvo]
+    });
     // we need missing messages to create ghosts, so periodically add an extra
     //  unknown into the equation
     if ((iMessage % 3) == 0)
-      smsg = msgGen.makeMessage(smsg);
+      smsg = msgGen.makeMessage({inReplyTo: smsg});
     
     // makeMessage is not exceedingly clever right now, we need to overwrite
     //  From and To...
     smsg.from = world.peoples[iAuthor];
     iAuthor = (iAuthor + iConvo + 1) % world.NUM_AUTHORS;
     // so, everyone is talking to everyone for this stuff
     smsg.to = world.peoples;
     world.lastMessagesInConvos[iConvo] = smsg;
--- a/mailnews/mime/emitters/src/nsMimeBaseEmitter.cpp
+++ b/mailnews/mime/emitters/src/nsMimeBaseEmitter.cpp
@@ -144,17 +144,18 @@ nsMimeBaseEmitter::~nsMimeBaseEmitter(vo
   {
     for (i=0; i<mAttachArray->Count(); i++)
     {
       attachmentInfoType *attachInfo = (attachmentInfoType *)mAttachArray->ElementAt(i);
       if (!attachInfo)
         continue;
 
       PR_FREEIF(attachInfo->contentType);
-      PR_FREEIF(attachInfo->displayName);
+      if (attachInfo->displayName)
+        NS_Free(attachInfo->displayName);
       PR_FREEIF(attachInfo->urlSpec);
       PR_FREEIF(attachInfo);
     }
     delete mAttachArray;
   }
 
   // Cleanup allocated header arrays...
   CleanupHeaderArray(mHeaderArray);
@@ -383,26 +384,28 @@ nsMimeBaseEmitter::GetOutputListener(nsI
     NS_IF_ADDREF(*listener);
   }
   return NS_OK;
 }
 
 
 // Attachment handling routines
 nsresult
-nsMimeBaseEmitter::StartAttachment(const char *name, const char *contentType, const char *url,
+nsMimeBaseEmitter::StartAttachment(const nsACString &name,
+                                   const char *contentType,
+                                   const char *url,
                                    PRBool aIsExternalAttachment)
 {
   // Ok, now we will setup the attachment info
   mCurrentAttachment = (attachmentInfoType *) PR_NEWZAP(attachmentInfoType);
   if ( (mCurrentAttachment) && mAttachArray)
   {
     ++mAttachCount;
 
-    mCurrentAttachment->displayName = strdup(name);
+    mCurrentAttachment->displayName = ToNewCString(name);
     mCurrentAttachment->urlSpec = strdup(url);
     mCurrentAttachment->contentType = strdup(contentType);
     mCurrentAttachment->isExternalAttachment = aIsExternalAttachment;
   }
 
   return NS_OK;
 }
 
@@ -429,49 +432,51 @@ NS_IMETHODIMP
 nsMimeBaseEmitter::AddAttachmentField(const char *field, const char *value)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMimeBaseEmitter::UtilityWrite(const char *buf)
 {
-  PRInt32     tmpLen = strlen(buf);
   PRUint32    written;
+  Write(nsDependentCString(buf), &written);
+  return NS_OK;
+}
 
-  Write(buf, tmpLen, &written);
-
+NS_IMETHODIMP
+nsMimeBaseEmitter::UtilityWrite(const nsACString &buf)
+{
+  PRUint32    written;
+  Write(buf, &written);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMimeBaseEmitter::UtilityWriteCRLF(const char *buf)
 {
-  PRInt32     tmpLen = strlen(buf);
   PRUint32    written;
-
-  Write(buf, tmpLen, &written);
-  Write(CRLF, 2, &written);
-
+  Write(nsDependentCString(buf), &written);
+  Write(NS_LITERAL_CSTRING(CRLF), &written);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsMimeBaseEmitter::Write(const char *buf, PRUint32 size, PRUint32 *amountWritten)
+nsMimeBaseEmitter::Write(const nsACString &buf, PRUint32 *amountWritten)
 {
   unsigned int        written = 0;
   nsresult rv = NS_OK;
   PRUint32            needToWrite;
 
 #ifdef DEBUG_BenB
   // If you want to see libmime output...
   printf("%s", buf);
 #endif
 
-  PR_LOG(gMimeEmitterLogModule, PR_LOG_ALWAYS, (buf));
+  PR_LOG(gMimeEmitterLogModule, PR_LOG_ALWAYS, (PromiseFlatCString(buf).get()));
   //
   // Make sure that the buffer we are "pushing" into has enough room
   // for the write operation. If not, we have to buffer, return, and get
   // it on the next time through
   //
   *amountWritten = 0;
 
   needToWrite = mBufferMgr->GetSize();
@@ -483,30 +488,32 @@ nsMimeBaseEmitter::Write(const char *buf
     mTotalWritten += written;
     mBufferMgr->ReduceBuffer(written);
     *amountWritten = written;
 
     // if we couldn't write all the old data, buffer the new data
     // and return
     if (mBufferMgr->GetSize() > 0)
     {
-      mBufferMgr->IncreaseBuffer(buf, size);
+      mBufferMgr->IncreaseBuffer(buf.BeginReading(), buf.Length());
       return rv;
     }
   }
 
 
   // if we get here, we are dealing with new data...try to write
   // and then do the right thing...
-  rv = WriteHelper(buf, size, &written);
+  rv = WriteHelper(buf.BeginReading(), buf.Length(), &written);
   *amountWritten = written;
   mTotalWritten += written;
 
-  if (written < size)
-    mBufferMgr->IncreaseBuffer(buf+written, (size-written));
+  if (written < buf.Length()) {
+    const nsACString &remainder = Substring(buf, written);
+    mBufferMgr->IncreaseBuffer(remainder.BeginReading(), remainder.Length());
+  }
 
   return rv;
 }
 
 nsresult
 nsMimeBaseEmitter::WriteHelper(const char *buf, PRUint32 count, PRUint32 *countWritten)
 {
   nsresult rv;
@@ -922,17 +929,17 @@ nsMimeBaseEmitter::Complete()
 {
   // If we are here and still have data to write, we should try
   // to flush it...if we try and fail, we should probably return
   // an error!
   PRUint32      written;
 
   nsresult rv = NS_OK;
   while ( NS_SUCCEEDED(rv) && (mBufferMgr) && (mBufferMgr->GetSize() > 0))
-    rv = Write("", 0, &written);
+    rv = Write(EmptyCString(), &written);
 
   if (mOutListener)
   {
     PRUint32 bytesInStream = 0;
     nsresult rv2 = mInputStream->Available(&bytesInStream);
     NS_ASSERTION(NS_SUCCEEDED(rv2), "Available failed");
     if (bytesInStream)
     {
@@ -958,17 +965,17 @@ nsMimeBaseEmitter::EndHeader()
 // body handling routines
 NS_IMETHODIMP
 nsMimeBaseEmitter::StartBody(PRBool bodyOnly, const char *msgID, const char *outCharset)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsMimeBaseEmitter::WriteBody(const char *buf, PRUint32 size, PRUint32 *amountWritten)
+nsMimeBaseEmitter::WriteBody(const nsACString &buf, PRUint32 *amountWritten)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMimeBaseEmitter::EndBody()
 {
   return NS_OK;
--- a/mailnews/mime/emitters/src/nsMimeBaseEmitter.h
+++ b/mailnews/mime/emitters/src/nsMimeBaseEmitter.h
@@ -91,16 +91,17 @@ public:
 
   // nsISupports interface
   NS_DECL_ISUPPORTS
 
   NS_DECL_NSIMIMEEMITTER
   NS_DECL_NSIINTERFACEREQUESTOR
 
   // Utility output functions...
+  NS_IMETHOD          UtilityWrite(const nsACString &buf);
   NS_IMETHOD          UtilityWriteCRLF(const char *buf);
 
   // For string bundle usage...
   char                *MimeGetStringByName(const char *aHeaderName);
   char                *MimeGetStringByID(PRInt32 aID);
   char                *LocalizeHeaderName(const char *aHeaderName, const char *aDefaultName);
 
   // For header processing...
--- a/mailnews/mime/emitters/src/nsMimeHtmlEmitter.cpp
+++ b/mailnews/mime/emitters/src/nsMimeHtmlEmitter.cpp
@@ -409,17 +409,17 @@ nsMimeHtmlDisplayEmitter::EndHeader()
     UtilityWriteCRLF("<body>");
   }
 
   WriteHTMLHeaders();
   return NS_OK;
 }
 
 nsresult
-nsMimeHtmlDisplayEmitter::StartAttachment(const char *name,
+nsMimeHtmlDisplayEmitter::StartAttachment(const nsACString &name,
                                           const char *contentType,
                                           const char *url,
                                           PRBool aIsExternalAttachment)
 {
   nsresult rv = NS_OK;
   nsCOMPtr<nsIMsgHeaderSink> headerSink;
   rv = GetHeaderSink(getter_AddRefs(headerSink));
 
@@ -435,33 +435,21 @@ nsMimeHtmlDisplayEmitter::StartAttachmen
       nsCOMPtr<nsINntpUrl> nntpUrl (do_QueryInterface(mURL, &rv));
       if (NS_SUCCEEDED(rv) && nntpUrl)
         rv = msgurl->GetOriginalSpec(getter_Copies(uriString));
       else
         rv = msgurl->GetUri(getter_Copies(uriString));
     }
 
     // we need to convert the attachment name from UTF-8 to unicode before
-    // we emit it...
+    // we emit it.  The attachment name has already been rfc2047 processed
+    // upstream of us.  (Namely, mime_decode_filename has been called, deferring
+    // to nsIMimeHeaderParam.decodeParameter.)
     nsString unicodeHeaderValue;
-
-    rv = NS_ERROR_FAILURE;  // use failure to mean that we couldn't decode
-    if (mUnicodeConverter)
-      rv = mUnicodeConverter->DecodeMimeHeader(name, nsnull, PR_FALSE, PR_TRUE,
-                                               unicodeHeaderValue);
-
-    if (NS_FAILED(rv))
-    {
-      CopyUTF8toUTF16(nsDependentCString(name), unicodeHeaderValue);
-
-      // but it's not really a failure if we didn't have a converter
-      // in the first place
-      if ( !mUnicodeConverter )
-        rv = NS_OK;
-    }
+    CopyUTF8toUTF16(nsDependentCString(name), unicodeHeaderValue);
 
     headerSink->HandleAttachment(contentType, url /* was escapedUrl */,
                                  unicodeHeaderValue.get(), uriString.get(),
                                  aIsExternalAttachment);
 
     mSkipAttachment = PR_TRUE;
   }
   else if (mFormat == nsMimeOutput::nsMimeMessagePrintOutput)
@@ -481,17 +469,19 @@ nsMimeHtmlDisplayEmitter::StartAttachmen
 }
 
 // Attachment handling routines
 // Ok, we are changing the way we handle these now...It used to be that we output
 // HTML to make a clickable link, etc... but now, this should just be informational
 // and only show up during printing
 // XXX should they also show up during quoting?
 nsresult
-nsMimeHtmlDisplayEmitter::StartAttachmentInBody(const char *name, const char *contentType, const char *url)
+nsMimeHtmlDisplayEmitter::StartAttachmentInBody(const nsACString &name,
+                                                const char *contentType,
+                                                const char *url)
 {
   mSkipAttachment = PR_FALSE;
 
   if ( (contentType) &&
        ((!strcmp(contentType, APPLICATION_XPKCS7_MIME)) ||
         (!strcmp(contentType, APPLICATION_XPKCS7_SIGNATURE)) ||
         (!strcmp(contentType, TEXT_VCARD)))
      ) {
@@ -587,19 +577,20 @@ nsMimeHtmlDisplayEmitter::EndAllAttachme
   nsCOMPtr<nsIMsgHeaderSink> headerSink;
   rv = GetHeaderSink(getter_AddRefs(headerSink));
   if (headerSink)
     headerSink->OnEndAllAttachments();
   return rv;
 }
 
 nsresult
-nsMimeHtmlDisplayEmitter::WriteBody(const char *buf, PRUint32 size, PRUint32 *amountWritten)
+nsMimeHtmlDisplayEmitter::WriteBody(const nsACString &buf,
+                                    PRUint32 *amountWritten)
 {
-  Write(buf, size, amountWritten);
+  Write(buf, amountWritten);
   return NS_OK;
 }
 
 nsresult
 nsMimeHtmlDisplayEmitter::EndBody()
 {
   if (mFormat != nsMimeOutput::nsMimeMessageFilterSniffer)
   {
--- a/mailnews/mime/emitters/src/nsMimeHtmlEmitter.h
+++ b/mailnews/mime/emitters/src/nsMimeHtmlEmitter.h
@@ -56,40 +56,42 @@ public:
     nsresult Init();
 
     virtual       ~nsMimeHtmlDisplayEmitter (void);
 
     // Header handling routines.
     NS_IMETHOD    EndHeader();
 
     // Attachment handling routines
-    NS_IMETHOD    StartAttachment(const char *name, const char *contentType, const char *url,
+    NS_IMETHOD    StartAttachment(const nsACString &name,
+                                  const char *contentType, const char *url,
                                   PRBool aIsExternalAttachment);
     NS_IMETHOD    AddAttachmentField(const char *field, const char *value);
     NS_IMETHOD    EndAttachment();
     NS_IMETHOD    EndAllAttachments();
 
     // Body handling routines
-    NS_IMETHOD    WriteBody(const char *buf, PRUint32 size, PRUint32 *amountWritten);
+    NS_IMETHOD    WriteBody(const nsACString &buf, PRUint32 *amountWritten);
     NS_IMETHOD    EndBody();
     NS_IMETHOD WriteHTMLHeaders();
 
     virtual nsresult            WriteHeaderFieldHTMLPrefix();
     virtual nsresult            WriteHeaderFieldHTML(const char *field, const char *value);
     virtual nsresult            WriteHeaderFieldHTMLPostfix();
 
 protected:
     PRBool        mFirst;  // Attachment flag...
     PRBool        mSkipAttachment;  // attachments we shouldn't show...
 
     nsCOMPtr<nsIMsgHeaderSink> mHeaderSink;
 
     nsresult GetHeaderSink(nsIMsgHeaderSink ** aHeaderSink);
     PRBool BroadCastHeadersAndAttachments();
-    nsresult StartAttachmentInBody(const char *name, const char *contentType, const char *url);
+    nsresult StartAttachmentInBody(const nsACString &name,
+                                   const char *contentType, const char *url);
 
     nsCOMPtr<nsIDateTimeFormat> mDateFormatter;
     nsresult GenerateDateString(const char * dateString, nsACString& formattedDate);
     nsresult BroadcastHeaders(nsIMsgHeaderSink * aHeaderSink, PRInt32 aHeaderMode, PRBool aFromNewsgroup);
 };
 
 
 #endif /* _nsMimeHtmlEmitter_h_ */
--- a/mailnews/mime/emitters/src/nsMimePlainEmitter.cpp
+++ b/mailnews/mime/emitters/src/nsMimePlainEmitter.cpp
@@ -83,14 +83,14 @@ nsMimePlainEmitter::AddHeaderField(const
 nsresult
 nsMimePlainEmitter::EndHeader()
 {
   UtilityWriteCRLF("");
   return NS_OK; 
 }
 
 NS_IMETHODIMP
-nsMimePlainEmitter::WriteBody(const char *buf, PRUint32 size, PRUint32 *amountWritten)
+nsMimePlainEmitter::WriteBody(const nsACString &buf, PRUint32 *amountWritten)
 {
-  Write(buf, size, amountWritten);
+  Write(buf, amountWritten);
   return NS_OK;
 }
 
--- a/mailnews/mime/emitters/src/nsMimePlainEmitter.h
+++ b/mailnews/mime/emitters/src/nsMimePlainEmitter.h
@@ -52,12 +52,12 @@ public:
     virtual       ~nsMimePlainEmitter (void);
 
     // Header handling routines.
     NS_IMETHOD    StartHeader(PRBool rootMailHeader, PRBool headerOnly, const char *msgID,
                               const char *outCharset);
     NS_IMETHOD    AddHeaderField(const char *field, const char *value);
     NS_IMETHOD    EndHeader();
 
-    NS_IMETHOD    WriteBody(const char *buf, PRUint32 size, PRUint32 *amountWritten);
+    NS_IMETHOD    WriteBody(const nsACString &buf, PRUint32 *amountWritten);
 };
 
 #endif /* _nsMimePlainEmitter_h_ */
--- a/mailnews/mime/emitters/src/nsMimeRawEmitter.cpp
+++ b/mailnews/mime/emitters/src/nsMimeRawEmitter.cpp
@@ -53,14 +53,14 @@ nsMimeRawEmitter::nsMimeRawEmitter()
 }
 
 
 nsMimeRawEmitter::~nsMimeRawEmitter(void)
 {
 }
 
 NS_IMETHODIMP
-nsMimeRawEmitter::WriteBody(const char *buf, PRUint32 size, PRUint32 *amountWritten)
+nsMimeRawEmitter::WriteBody(const nsACString &buf, PRUint32 *amountWritten)
 {
-  Write(buf, size, amountWritten);
+  Write(buf, amountWritten);
   return NS_OK;
 }
 
--- a/mailnews/mime/emitters/src/nsMimeRawEmitter.h
+++ b/mailnews/mime/emitters/src/nsMimeRawEmitter.h
@@ -46,15 +46,15 @@
 #include "nsIURI.h"
 #include "nsIChannel.h"
 
 class nsMimeRawEmitter : public nsMimeBaseEmitter {
 public: 
     nsMimeRawEmitter ();
     virtual       ~nsMimeRawEmitter (void);
 
-    NS_IMETHOD    WriteBody(const char *buf, PRUint32 size, PRUint32 *amountWritten);
+    NS_IMETHOD    WriteBody(const nsACString &buf, PRUint32 *amountWritten);
 
 protected:
 };
 
 
 #endif /* _nsMimeRawEmitter_h_ */
--- a/mailnews/mime/emitters/src/nsMimeXmlEmitter.cpp
+++ b/mailnews/mime/emitters/src/nsMimeXmlEmitter.cpp
@@ -178,27 +178,29 @@ nsMimeXmlEmitter::EndHeader()
 {
   UtilityWrite("</mailheader>");
   return NS_OK;
 }
 
 
 // Attachment handling routines
 nsresult
-nsMimeXmlEmitter::StartAttachment(const char *name, const char *contentType, const char *url,
+nsMimeXmlEmitter::StartAttachment(const nsACString &name,
+                                  const char *contentType,
+                                  const char *url,
                                   PRBool aIsExternalAttachment)
 {
   char    buf[128];
 
   ++mAttachCount;
 
   sprintf(buf, "<mailattachment id=\"%d\">", mAttachCount);
   UtilityWrite(buf);
 
-  AddAttachmentField(HEADER_PARM_FILENAME, name);
+  AddAttachmentField(HEADER_PARM_FILENAME, PromiseFlatCString(name).get());
   return NS_OK;
 }
 
 nsresult
 nsMimeXmlEmitter::AddAttachmentField(const char *field, const char *value)
 {
   WriteXMLTag(field, value);
   return NS_OK;
--- a/mailnews/mime/emitters/src/nsMimeXmlEmitter.h
+++ b/mailnews/mime/emitters/src/nsMimeXmlEmitter.h
@@ -55,17 +55,18 @@ public:
 
     // Header handling routines.
     NS_IMETHOD    StartHeader(PRBool rootMailHeader, PRBool headerOnly, const char *msgID,
                               const char *outCharset);
     NS_IMETHOD    AddHeaderField(const char *field, const char *value);
     NS_IMETHOD    EndHeader();
 
     // Attachment handling routines
-    NS_IMETHOD    StartAttachment(const char *name, const char *contentType, const char *url,
+    NS_IMETHOD    StartAttachment(const nsACString &name,
+                                  const char *contentType, const char *url,
                                   PRBool aIsExternalAttachment);
     NS_IMETHOD    AddAttachmentField(const char *field, const char *value);
     NS_IMETHOD    EndAttachment();
 
     NS_IMETHOD    WriteXMLHeader(const char *msgID);
     NS_IMETHOD    WriteXMLTag(const char *tagName, const char *value);
 
 protected:
--- a/mailnews/mime/public/nsIMimeEmitter.idl
+++ b/mailnews/mime/public/nsIMimeEmitter.idl
@@ -50,17 +50,17 @@ interface nsMimeHeaderDisplayTypes
     const long NormalHeaders = 1;
     const long AllHeaders = 2;
 };
 
 %{C++
 #define NS_IMIME_MISC_STATUS_KEY       "@mozilla.org/MimeMiscStatus;1?type="
 %}
 
-[scriptable, uuid(c470b05e-9006-43a4-aa84-0da080360675)]
+[scriptable, uuid(7a57166f-2891-4122-9a74-6c3fab0caac3)]
 interface nsIMimeEmitter : nsISupports {
 
     // Output listener to allow access to it from mime.
     attribute nsIStreamListener outputListener;
 
     // These will be called to start and stop the total operation.
     void initialize(in nsIURI url, in nsIChannel aChannel, in long aFormat);
     void complete();
@@ -73,26 +73,27 @@ interface nsIMimeEmitter : nsISupports {
                      [const] in string msgID, [const] in string outCharset);
     void addHeaderField([const] in string field, [const] in string value);
     void addAllHeaders(in ACString allheaders);
     void writeHTMLHeaders(); // Book case this with an EndHeader call.
     void endHeader();
     void updateCharacterSet([const] in string aCharset);
 
     // Attachment handling routines.
-    void startAttachment([const] in string name, [const] in string contentType,
+    void startAttachment([const] in AUTF8String name,
+                         [const] in string contentType,
                          [const] in string url, in PRBool aNotDownloaded);
     void addAttachmentField([const] in string field, [const] in string value);
     void endAttachment();
 
     void endAllAttachments();
 
     // Body handling routines.
     void startBody(in PRBool bodyOnly, [const] in string msgID, [const] in string outCharset);
-    void writeBody([const] in string buf, in PRUint32 size, out PRUint32 amountWritten);
+    void writeBody([const] in AUTF8String buf, out PRUint32 amountWritten);
     void endBody();
 
     // Generic write routine. This is necessary for output that
     // libmime needs to pass through without any particular parsing
     // involved (i.e. decoded images, HTML Body Text, etc...
-    void write([const] in string buf, in PRUint32 size, out PRUint32 amountWritten);
+    void write([const] in ACString buf, out PRUint32 amountWritten);
     void utilityWrite([const] in string buf);
 };
--- a/mailnews/mime/src/mimemoz2.cpp
+++ b/mailnews/mime/src/mimemoz2.cpp
@@ -888,24 +888,25 @@ mime_output_fn(const char *buf, PRInt32 
 
 
   // Now, write to the WriteBody method if this is a message body and not
   // a part retrevial
   if (!msd->options->part_to_load || msd->options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay)
   {
     if (msd->output_emitter)
     {
-      msd->output_emitter->WriteBody(buf, (PRUint32) size, &written);
+      msd->output_emitter->WriteBody(Substring(buf, buf+size),
+                                     &written);
     }
   }
   else
   {
     if (msd->output_emitter)
     {
-      msd->output_emitter->Write(buf, (PRUint32) size, &written);
+      msd->output_emitter->Write(Substring(buf, buf+size), &written);
     }
   }
   return written;
 }
 
 extern "C" int
 mime_display_stream_write (nsMIMESession *stream,
                            const char* buf,
@@ -1794,17 +1795,18 @@ mimeEmitterStartAttachment(MimeDisplayOp
 
   mime_stream_data  *msd = GetMSD(opt);
   if (!msd)
     return NS_ERROR_FAILURE;
 
   if (msd->output_emitter)
   {
     nsIMimeEmitter *emitter = (nsIMimeEmitter *)msd->output_emitter;
-    return emitter->StartAttachment(name, contentType, url, aIsExternalAttachment);
+    return emitter->StartAttachment(nsDependentCString(name), contentType, url,
+                                    aIsExternalAttachment);
   }
 
   return NS_ERROR_FAILURE;
 }
 
 extern "C" nsresult
 mimeEmitterEndAttachment(MimeDisplayOptions *opt)
 {
--- a/mailnews/mime/src/nsStreamConverter.cpp
+++ b/mailnews/mime/src/nsStreamConverter.cpp
@@ -884,17 +884,17 @@ const char output[] = "\
 
     nsCAutoString url;
     if (NS_FAILED(mURI->GetSpec(url)))
       return NS_ERROR_FAILURE;
 
     PR_snprintf(outBuf, sizeof(outBuf), output, url.get(), url.get());
 
     if (mEmitter)
-      mEmitter->Write(outBuf, strlen(outBuf), &written);
+      mEmitter->Write(nsDependentCString(outBuf), &written);
 
     // rhp: will this stop the stream???? Not sure.
     return NS_ERROR_FAILURE;
   }
 
   char *buf = (char *)PR_Malloc(aLength);
   if (!buf)
     return NS_ERROR_OUT_OF_MEMORY; /* we couldn't allocate the object */
@@ -925,17 +925,19 @@ const char output[] = "\
     }
     readLen = writePtr - buf;
   }
 
   if (mOutputType == nsMimeOutput::nsMimeMessageSource)
   {
     rc = NS_OK;
     if (mEmitter)
-      rc = mEmitter->Write(buf, readLen, &written);
+    {
+      rc = mEmitter->Write(Substring(buf, buf+readLen), &written);
+    }
   }
   else if (mBridgeStream)
   {
     nsMIMESession   *tSession = (nsMIMESession *) mBridgeStream;
     rc = tSession->put_block((nsMIMESession *)mBridgeStream, buf, readLen);
   }
 
   PR_FREEIF(buf);