Bug 561851 - Hack libmime so that it notifies attachment sizes to GlodaMimeAttachments; r=asuth sr=bienvenu
authorJonathan Protzenko <jonathan.protzenko@gmail.com>
Thu, 19 Aug 2010 09:46:21 +0100
changeset 6218 80a99f0953695399a9aa48f4c5421cef6cb26cb2
parent 6217 29d46477354ae81b2c1c991d2401519e7ad2ca20
child 6219 d6124a907621361f96a540719dae89874cb9ec24
push idunknown
push userunknown
push dateunknown
reviewersasuth, bienvenu
bugs561851
Bug 561851 - Hack libmime so that it notifies attachment sizes to GlodaMimeAttachments; r=asuth sr=bienvenu
mailnews/compose/public/nsIMsgSend.idl
mailnews/db/gloda/components/jsmimeemitter.js
mailnews/db/gloda/modules/mimemsg.js
mailnews/db/gloda/test/unit/test_mime_attachments_size.js
mailnews/mime/public/nsMailHeaders.h
mailnews/mime/src/mimecryp.cpp
mailnews/mime/src/mimedrft.cpp
mailnews/mime/src/mimeenc.cpp
mailnews/mime/src/mimeeobj.cpp
mailnews/mime/src/mimei.cpp
mailnews/mime/src/mimeiimg.cpp
mailnews/mime/src/mimeleaf.cpp
mailnews/mime/src/mimeleaf.h
mailnews/mime/src/mimemoz2.cpp
mailnews/mime/src/mimemsig.cpp
mailnews/mime/src/mimemult.cpp
mailnews/mime/src/modlmime.h
mailnews/mime/src/modmimee.h
--- a/mailnews/compose/public/nsIMsgSend.idl
+++ b/mailnews/compose/public/nsIMsgSend.idl
@@ -110,16 +110,17 @@ struct nsMsgAttachmentData
                           // Content-Disposition header. For example, if you had copied a document to a 
                           // tmp file, this would be the original, human-readable name of the document.
 
   char *description;      // If you put a string here, it will show up as the Content-Description header.  
                           // This can be any explanatory text; it's not a file name.             
 
   char *x_mac_type, *x_mac_creator; // Mac-specific data that should show up as optional parameters
                                     // to the content-type header.
+  PRInt32 size;                  // The size of the attachment. May be 0.
   PRBool  isExternalAttachment;  // Flag for determining if the attachment is external
 };
 
 
 //
 // When we have downloaded a URL to a tmp file for attaching, this
 // represents everything we learned about it (and did to it) in the
 // process. 
@@ -156,17 +157,17 @@ typedef struct nsMsgAttachedFile
 } nsMsgAttachedFile;
 %}
 
 
 [ptr] native nsMsgAttachmentData(nsMsgAttachmentData);
 [ptr] native nsMsgAttachedFile(nsMsgAttachedFile);
 [ptr] native nsMsgAttachmentHandler(nsMsgAttachmentHandler);
 
-[scriptable, uuid(1313d4e4-3ac4-4fe7-bae5-e7049018c331)]
+[scriptable, uuid(8a3eb87e-6643-439b-bf0c-3aa2ede692ef)]
 interface nsIMsgSend : nsISupports 
 {
   //
   // This is the primary interface for creating and sending RFC822 messages
   // in the new architecture. Currently, this method supports many arguments
   // that change the behavior of the operation. This will change in time to 
   // be separate calls that will be more singluar in nature.
   //
--- a/mailnews/db/gloda/components/jsmimeemitter.js
+++ b/mailnews/db/gloda/components/jsmimeemitter.js
@@ -86,16 +86,17 @@ function MimeMessageEmitter() {
   this._mimeMsg = {};
   Cu.import("resource:///modules/gloda/mimemsg.js", this._mimeMsg);
 
   this._url = null;
 
   this._outputListener = null;
 
   this._curPart = null;
+  this._curAttachment = null;
   this._partMap = {};
 
   this._state = kStateUnknown;
 
   this._writeBody = false;
 }
 
 const deathToNewlines = /\n/g;
@@ -107,16 +108,17 @@ MimeMessageEmitter.prototype = {
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIMimeEmitter]),
 
   initialize: function mime_emitter_initialize(aUrl, aChannel, aFormat) {
     this._url = aUrl;
     this._curPart = new this._mimeMsg.MimeMessage();
     // the partName is intentionally ""!  not a place-holder!
     this._curPart.partName = "";
+    this._curAttachment = "";
     this._partMap[""] = this._curPart;
 
     // pull options across...
     let options = this._mimeMsg.MsgHdrToMimeMessage.OPTION_TUNNEL;
     this._saneBodySize = (options && ("saneBodySize" in options)) ?
                            options.saneBodySize : false;
 
     this._mimeMsg.MsgHdrToMimeMessage.RESULT_RENDEVOUZ[aUrl.spec] =
@@ -124,16 +126,17 @@ MimeMessageEmitter.prototype = {
   },
 
   complete: function mime_emitter_complete() {
     this._url = null;
 
     this._outputListener = null;
 
     this._curPart = null;
+    this._curAttachment = null;
     this._partMap = null;
   },
 
   setPipe: function mime_emitter_setPipe(aInputStream, aOutputStream) {
     // we do not care about these
   },
   set outputListener(aListener) {
     this._outputListener = aListener;
@@ -335,58 +338,64 @@ MimeMessageEmitter.prototype = {
   // The attachment processing happens after the initial streaming phase (during
   //  which time we receive the messages, both bodies and headers).  Our caller
   //  traverses the libmime child object hierarchy, emitting an attachment for
   //  each leaf object or sub-message.
   startAttachment: function mime_emitter_startAttachment(aName, aContentType,
       aUrl, aIsExternalAttachment) {
     this._state = kStateInAttachment;
 
+    // we need to strip our magic flags from the URL
+    aUrl = aUrl.replace("header=filter&emitter=js&", "");
+    // the url should contain a part= piece that tells us the part name, which
+    // we then use to figure out where to place that part if it's a real
+    // attachment.
+    let partMatch = this._partRE.exec(aUrl);
+    let partName = partMatch && partMatch[1];
+    this._curAttachment = partName;
+
     if (aContentType == "message/rfc822") {
       // we want to offer extension authors a way to see attachments as the
       // message readers sees them, which means attaching an extra url property
       // to the part that was already created before
-      aUrl = aUrl.replace("header=filter&emitter=js&", "");
-      let partMatch = this._partRE.exec(aUrl);
-      if (partMatch) {
+      if (partName) {
         // we disguise this MimeMessage into something that can be used as a
         // MimeAttachment so that it is transparent for the user code
-        let partName = partMatch[1];
         this._partMap[partName].url = aUrl;
         this._partMap[partName].isExternalAttachment = aIsExternalAttachment;
         this._partMap[partName].name = aName;
         this._partMap[partName].isRealAttachment = true;
       }
     }
     else if (aIsExternalAttachment) {
       // external attachments do not pass their part path information.
       // both aUrl in this call and the call to addAttachmentField (with
       // X-Mozilla-PartURL) receive the same thing; the URL to the file on disk.
     }
     else {
-      // we need to strip our magic flags from the URL
-      aUrl = aUrl.replace("header=filter&emitter=js&", "");
-      // the url should contain a part= piece that tells us the part name, which
-      //  we then use to figure out where.
-      let partMatch = this._partRE.exec(aUrl);
-      if (partMatch) {
-        let part = new this._mimeMsg.MimeMessageAttachment(partMatch[1],
+      if (partName) {
+        let part = new this._mimeMsg.MimeMessageAttachment(partName,
             aName, aContentType, aUrl, aIsExternalAttachment);
         if (part.isRealAttachment) {
           // replace the existing part with the attachment...
           this._replacePart(part);
         }
       }
     }
   },
   addAttachmentField: function mime_emitter_addAttachmentField(aField, aValue) {
-    // all that gets passed in here is X-Mozilla-PartURL with a value that
+    // what gets passed in here is X-Mozilla-PartURL with a value that
     //  is completely identical to aUrl from the call to startAttachment.
     //  (it's the same variable they use in each case).  As such, there is
-    //  no reason to handle anything here.
+    //  no reason to handle that here.
+    //
+    //  However, we also pass information about the size of the attachment, and
+    //  that we want to handle
+    if (aField == "X-Mozilla-PartSize" && this._curPart)
+        this._partMap[this._curAttachment].size = parseInt(aValue);
   },
   endAttachment: function mime_emitter_endAttachment() {
     // don't need to do anything here, since we don't care about the headers.
   },
   endAllAttachments: function mime_emitter_endAllAttachments() {
     // nop
   },
 
--- a/mailnews/db/gloda/modules/mimemsg.js
+++ b/mailnews/db/gloda/modules/mimemsg.js
@@ -367,16 +367,24 @@ MimeMessage.prototype = {
       return [this];
     else
       // Why is there no flatten method for arrays?
       return [child.allUserAttachments for each ([, child] in Iterator(this.parts))]
         .reduce(function (a, b) a.concat(b), []);
   },
 
   /**
+   * @return the total size of this message, that is, the size of all subparts
+   */
+  get size () {
+    return [child.size for each ([, child] in Iterator(this.parts))]
+      .reduce(function (a, b) a + Math.max(b, 0), 0);
+  },
+
+  /**
    * @param aMsgFolder A message folder, any message folder.  Because this is
    *    a hack.
    * @return The concatenation of all of the body parts where parts
    *    available as text/plain are pulled as-is, and parts only available
    *    as text/html are converted to plaintext form first.  In other words,
    *    if we see a multipart/alternative with a text/plain, we take the
    *    text/plain.  If we see a text/html without an alternative, we convert
    *    that to text.
@@ -447,16 +455,20 @@ MimeContainer.prototype = {
       results = results.concat(child.allAttachments);
     }
     return results;
   },
   get allUserAttachments () {
     return [child.allUserAttachments for each ([, child] in Iterator(this.parts))]
       .reduce(function (a, b) a.concat(b), []);
   },
+  get size () {
+    return [child.size for each ([, child] in Iterator(this.parts))]
+      .reduce(function (a, b) a + Math.max(b, 0), 0);
+  },
   coerceBodyToPlaintext:
       function MimeContainer_coerceBodyToPlaintext(aMsgFolder) {
     if (this.contentType == "multipart/alternative") {
       let htmlPart;
       // pick the text/plain if we can find one, otherwise remember the HTML one
       for each (let [, part] in Iterator(this.parts)) {
         if (part.contentType == "text/plain")
           return part.body;
@@ -509,16 +521,19 @@ function MimeBody(aContentType) {
 MimeBody.prototype = {
   __proto__: HeaderHandlerBase,
   get allAttachments() {
     return []; // we are a leaf
   },
   get allUserAttachments() {
     return []; // we are a leaf
   },
+  get size() {
+    return this.body.length;
+  },
   coerceBodyToPlaintext:
       function MimeBody_coerceBodyToPlaintext(aMsgFolder) {
     if (this.contentType == "text/plain")
       return this.body;
     if (this.contentType == "text/html")
       return aMsgFolder.convertMsgSnippetToPlainText(this.body);
     return "";
   },
@@ -589,16 +604,18 @@ function getLocalizedPartStr() {
  * @class An attachment proper.  We think it's an attachment because it has a
  *  filename that libmime was able to figure out.
  *
  * @ivar partName @see{MimeMessage.partName}
  * @ivar name The filename of this attachment.
  * @ivar contentType The MIME content type of this part.
  * @ivar url The URL to stream if you want the contents of this part.
  * @ivar isExternal Is the attachment stored someplace else than in the message?
+ * @ivar size The size of the attachment if available, -1 otherwise (size is set
+ *  after initialization by jsmimeemitter.js)
  */
 function MimeMessageAttachment(aPartName, aName, aContentType, aUrl,
                                aIsExternal) {
   this.partName = aPartName;
   this.name = aName;
   this.contentType = aContentType;
   this.url = aUrl;
   this.isExternal = aIsExternal;
new file mode 100644
--- /dev/null
+++ b/mailnews/db/gloda/test/unit/test_mime_attachments_size.js
@@ -0,0 +1,239 @@
+/* ***** 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
+ * the Mozilla Foundation
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Jonathan Protzenko <jonathan.protzenko@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * General testing of the byte-counting libmime facility, to make sure that what
+ * is streamed to us is actually labeled with the right size.
+ */
+
+/*
+ * Do not include glodaTestHelper because we do not want gloda loaded and it
+ *  adds a lot of runtime overhead which makes certain debugging strategies like
+ *  using chronicle-recorder impractical.
+ */
+load("../../mailnews/resources/mailDirService.js");
+load("../../mailnews/resources/mailTestUtils.js");
+load("../../mailnews/resources/logHelper.js");
+load("../../mailnews/resources/asyncTestUtils.js");
+
+load("../../mailnews/resources/messageGenerator.js");
+load("../../mailnews/resources/messageModifier.js");
+load("../../mailnews/resources/messageInjection.js");
+
+// Create a message generator
+const msgGen = gMessageGenerator = new MessageGenerator();
+// Create a message scenario generator using that message generator
+const scenarios = gMessageScenarioFactory = new MessageScenarioFactory(msgGen);
+
+Components.utils.import("resource:///modules/gloda/mimemsg.js");
+
+var htmlText = "<html><head></head><body>I am HTML! Woo! </body></html>";
+
+var partHtml = new SyntheticPartLeaf(
+  htmlText,
+  {
+    contentType: "text/html"
+  }
+);
+
+var originalText =
+  "Longtemps, je me suis couché de bonne heure. Parfois, à "+
+  "peine ma bougie éteinte, mes yeux se fermaient si vite que je n'avais pas le "+
+  "temps de me dire : « Je m'endors. »";
+
+var b64Text =
+  "TG9uZ3RlbXBzLCBqZSBtZSBzdWlzIGNvdWNow6kgZGUgYm9ubmUgaGV1cmUuIFBhcmZvaXMs\n"+
+  "IMOgIHBlaW5lIG1hIGJvdWdpZSDDqXRlaW50ZSwgbWVzIHlldXggc2UgZmVybWFpZW50IHNp\n"+
+  "IHZpdGUgcXVlIGplIG4nYXZhaXMgcGFzIGxlIHRlbXBzIGRlIG1lIGRpcmUgOiDCqyBKZSBt\n"+
+  "J2VuZG9ycy4gwrsK";
+
+var qpText =
+  "Longtemps,=20je=20me=20suis=20couch=C3=A9=20de=20bonne=20heure.=20Parfois,=\n"+
+  "=20=C3=A0=20peine=20ma=20bougie=20=C3=A9teinte,=20mes=20yeux=20se=20fermaie=\n"+
+  "nt=20si=20vite=20que=20je=20n'avais=20pas=20le=20temps=20de=20me=20dire=20:=\n"+
+  "=20=C2=AB=20Je=20m'endors.=20=C2=BB";
+
+var uuText =
+  "begin 666 -\n"+
+  "M3&]N9W1E;7!S+\"!J92!M92!S=6ES(&-O=6-HPZD@9&4@8F]N;F4@:&5U<F4N\n"+
+  "M(%!A<F9O:7,L(,.@('!E:6YE(&UA(&)O=6=I92##J71E:6YT92P@;65S('EE\n"+
+  "M=7@@<V4@9F5R;6%I96YT('-I('9I=&4@<75E(&IE(&XG879A:7,@<&%S(&QE\n"+
+  "G('1E;7!S(&1E(&UE(&1I<F4@.B#\"JR!*92!M)V5N9&]R<RX@PKL*\n"+
+  "\n"+
+  "end";
+
+var yencText =
+  "Hello there --\n"+
+  "=ybegin line=128 size=174 name=jane.doe\n"+
+  "\x76\x99\x98\x91\x9e\x8f\x97\x9a\x9d\x56\x4a\x94\x8f\x4a\x97\x8f"+
+  "\x4a\x9d\x9f\x93\x9d\x4a\x8d\x99\x9f\x8d\x92\xed\xd3\x4a\x8e\x8f"+
+  "\x4a\x8c\x99\x98\x98\x8f\x4a\x92\x8f\x9f\x9c\x8f\x58\x4a\x7a\x8b"+
+  "\x9c\x90\x99\x93\x9d\x56\x4a\xed\xca\x4a\x9a\x8f\x93\x98\x8f\x4a"+
+  "\x97\x8b\x4a\x8c\x99\x9f\x91\x93\x8f\x4a\xed\xd3\x9e\x8f\x93\x98"+
+  "\x9e\x8f\x56\x4a\x97\x8f\x9d\x4a\xa3\x8f\x9f\xa2\x4a\x9d\x8f\x4a"+
+  "\x90\x8f\x9c\x97\x8b\x93\x8f\x98\x9e\x4a\x9d\x93\x4a\xa0\x93\x9e"+
+  "\x8f\x4a\x9b\x9f\x8f\x4a\x94\x8f\x4a\x98\x51\x8b\xa0\x8b\x93\x9d"+
+  "\x0d\x0a\x4a\x9a\x8b\x9d\x4a\x96\x8f\x4a\x9e\x8f\x97\x9a\x9d\x4a"+
+  "\x8e\x8f\x4a\x97\x8f\x4a\x8e\x93\x9c\x8f\x4a\x64\x4a\xec\xd5\x4a"+
+  "\x74\x8f\x4a\x97\x51\x8f\x98\x8e\x99\x9c\x9d\x58\x4a\xec\xe5\x34"+
+  "\x0d\x0a"+
+  "=yend size=174 crc32=7efccd8e\n";
+
+// that completely exotic encoding is only detected if there is no content type
+// on the message, which is usually the case in newsgroups. I hate you yencode!
+var partYencText = new SyntheticPartLeaf("I am text! Woo!\n\n"+yencText,
+  { contentType: '' } );
+
+var tachText = {filename: 'bob.txt', body: originalText};
+
+var tachInlineText = {filename: 'foo.txt', body: originalText,
+                      format: null, charset: null,
+                      disposition: 'inline'};
+
+// images have a different behavior than other attachments: they are displayed
+// inline most of the time, so there are two different code paths that need to
+// enable streaming and byte counting to the JS mime emitter
+
+var tachImage = {filename: 'bob.png', contentType: 'image/png',
+                 encoding: 'base64', charset: null, format: null,
+                 body: b64Text};
+
+var tachPdf = {filename: 'bob.pdf', contentType: 'application/pdf',
+                 encoding: 'base64', charset: null, format: null,
+                 body: b64Text};
+
+var tachUU = {filename: 'john.doe', contentType: 'application/x-uuencode',
+                 encoding: 'uuencode', charset: null, format: null,
+                 body: uuText};
+
+var tachApplication = {filename: 'funky.funk',
+                       contentType: 'application/x-funky', body: originalText};
+
+var relImage = {contentType: 'image/png',
+                encoding: 'base64', charset: null, format: null,
+                contentId: 'part1.foo@bar.com',
+                body: b64Text};
+var partRelImage = new SyntheticPartLeaf(relImage.body, relImage);
+
+var messageInfos = [
+  // encoding type specific to newsgroups, not interested, gloda doesn't even
+  // treat this as an attachment (probably because gloda requires an attachment
+  // to have a content-type, which these yencoded parts don't have), but size IS
+  // counted properly nonetheless
+  /*{
+    name: 'text/plain with yenc inline',
+    bodyPart: partYencText,
+    subject: "yEnc-Prefix: \"jane.doe\" 174 yEnc bytes - yEnc test (1)",
+  },*/
+  // inline image, not interested either, gloda doesn't keep that as an
+  // attachment (probably a deliberate choice), size is NOT counted properly
+  // (don't want to investigate, I doubt it's a useful information anyway)
+  /*{
+    name: 'multipart/related',
+    bodyPart: new SyntheticPartMultiRelated([partHtml, partRelImage]),
+  },*/
+  // all of the other common cases work fine
+  {
+    name: 'text/plain w/app attachment (=> multipart/mixed)',
+    bodyPart: partHtml,
+    attachments: [tachImage, tachPdf, tachUU,
+      tachApplication, tachText, tachInlineText,],
+  },
+];
+
+function check_attachments(aMimeMsg) {
+  if (aMimeMsg == null)
+    do_throw("We really should have gotten a result!");
+
+  /* Today's gory details: libmime somehow counts the trailing newline for an
+   * attachment MIME part. Most of the time, assuming attachment has N bytes (no matter
+   * what's inside, newlines or not), libmime will return N + 1 bytes. On Linux
+   * and Mac, this always holds. However, on Windows, if the attachment is not
+   * encoded (that is, is inline text), libmime will return N + 2 bytes.
+   *
+   * This means on Windows, att.size = 175 for application/x-funky, 174 for
+   * other test attachments (and the real size is 173 bytes).
+   */
+  let epsilon = ("@mozilla.org/windows-registry-key;1" in Components.classes) ? 2 : 1;
+
+  do_check_true(aMimeMsg.allAttachments.length > 0);
+
+  let totalSize = htmlText.length;
+
+  for each (let [i, att] in Iterator(aMimeMsg.allAttachments)) {
+    dump("*** Attachment now is "+att.name+"\n");
+    do_check_true((att.size - originalText.length) <= epsilon);
+    totalSize += att.size;
+  }
+
+  do_check_true((aMimeMsg.size - totalSize) <= epsilon);
+
+  async_driver();
+}
+
+function test_message_attachments(info) {
+  let synMsg = gMessageGenerator.makeMessage(info);
+  let synSet = new SyntheticMessageSet([synMsg]);
+  yield add_sets_to_folder(gInbox, [synSet]);
+
+  let msgHdr = synSet.getMsgHdr(0);
+  dump(synMsg.toMboxString()+"\n");
+
+  MsgHdrToMimeMessage(msgHdr, null, function(aMsgHdr, aMimeMsg) {
+    try {
+      check_attachments(aMimeMsg);
+    } catch (e) {
+      do_throw(e);
+    }
+  });
+
+  yield false;
+}
+
+/* ===== Driver ===== */
+
+var tests = [
+  parameterizeTest(test_message_attachments, messageInfos),
+];
+
+var gInbox;
+
+function run_test() {
+  // use mbox injection because the fake server chokes sometimes right now
+  gInbox = configure_message_injection({mode: "local"});
+  async_run_tests(tests);
+}
--- a/mailnews/mime/public/nsMailHeaders.h
+++ b/mailnews/mime/public/nsMailHeaders.h
@@ -107,12 +107,13 @@
 #define HEADER_PARM_CHARSET                 "charset"
 #define HEADER_PARM_START                   "start"
 #define HEADER_PARM_BOUNDARY                "BOUNDARY"
 #define HEADER_PARM_FILENAME                "FILENAME"
 #define HEADER_PARM_NAME                    "NAME"
 #define HEADER_PARM_TYPE                    "TYPE"
 
 #define HEADER_X_MOZILLA_PART_URL           "X-Mozilla-PartURL"
+#define HEADER_X_MOZILLA_PART_SIZE          "X-Mozilla-PartSize"
 #define HEADER_X_MOZILLA_IDENTITY_KEY       "X-Identity-Key"
 #define HEADER_X_MOZILLA_ACCOUNT_KEY       "X-Account-Key"
 #define HEADER_X_MOZILLA_KEYWORDS          "X-Mozilla-Keys"
 #endif /* nsMailHeaders_h_ */
--- a/mailnews/mime/src/mimecryp.cpp
+++ b/mailnews/mime/src/mimecryp.cpp
@@ -162,17 +162,17 @@ MimeEncrypted_parse_buffer (const char *
 
   if (obj->closed_p) return -1;
 
   /* Don't consult output_p here, since at this point we're behaving as a
    simple container object -- the output_p decision should be made by
    the child of this object. */
 
   if (enc->decoder_data)
-  return MimeDecoderWrite (enc->decoder_data, buffer, size);
+  return MimeDecoderWrite (enc->decoder_data, buffer, size, nsnull);
   else
   return ((MimeEncryptedClass *)obj->clazz)->parse_decoded_buffer (buffer,
                                    size,
                                    obj);
 }
 
 
 static int
--- a/mailnews/mime/src/mimedrft.cpp
+++ b/mailnews/mime/src/mimedrft.cpp
@@ -2006,17 +2006,17 @@ mime_decompose_file_output_fn (const cha
   NS_ASSERTION (mdd && buf, "missing mime draft data and/or buf");
   if (!mdd || !buf) return -1;
   if (!size) return NS_OK;
 
   if ( !mdd->tmpFileStream )
     return NS_OK;
 
   if (mdd->decoder_data) {
-    ret = MimeDecoderWrite(mdd->decoder_data, buf, size);
+    ret = MimeDecoderWrite(mdd->decoder_data, buf, size, nsnull);
     if (ret == -1) return -1;
   }
   else
   {
     PRUint32 bytesWritten;
     mdd->tmpFileStream->Write(buf, size, &bytesWritten);
     if (bytesWritten < size)
       return MIME_ERROR_WRITING_FILE;
--- a/mailnews/mime/src/mimeenc.cpp
+++ b/mailnews/mime/src/mimeenc.cpp
@@ -66,17 +66,18 @@ struct MimeDecoderData {
   MimeObject *objectToDecode; // might be null, only used for QP currently
   /* Where to write the decoded data */
   nsresult (*write_buffer) (const char *buf, PRInt32 size, void *closure);
   void *closure;
 };
 
 
 static int
-mime_decode_qp_buffer (MimeDecoderData *data, const char *buffer, PRInt32 length)
+mime_decode_qp_buffer (MimeDecoderData *data, const char *buffer,
+          PRInt32 length, PRInt32 *outSize)
 {
   /* Warning, we are overwriting the buffer which was passed in.
    This is ok, because decoding these formats will never result
    in larger data than the input, only smaller. */
   const char *in  = buffer;
   char *out = (char *) buffer;
   char token [3];
   int i;
@@ -189,16 +190,20 @@ mime_decode_qp_buffer (MimeDecoderData *
       *out++ = token[0];
 
       token[0] = token[1];
       token[1] = token[2];
       i = 2;
     }
   }
 
+  // Fill the size
+  if (outSize)
+    *outSize = out - buffer;
+
   /* Now that we've altered the data in place, write it. */
   if (out > buffer)
   return data->write_buffer (buffer, (out - buffer), data->closure);
   else
   return 1;
 }
 
 
@@ -242,17 +247,17 @@ mime_decode_base64_token (const char *in
     NS_ERROR("Count is 6 bits, should be at least 8");
     return 1;
   }
 }
 
 
 static int
 mime_decode_base64_buffer (MimeDecoderData *data,
-               const char *buffer, PRInt32 length)
+               const char *buffer, PRInt32 length, PRInt32 *outSize)
 {
   /* Warning, we are overwriting the buffer which was passed in.
    This is ok, because decoding these formats will never result
    in larger data than the input, only smaller. */
   const char *in  = buffer;
   char *out = (char *) buffer;
   char token [4];
   int i;
@@ -316,27 +321,29 @@ mime_decode_base64_buffer (MimeDecoderDa
     else
     {
       int n = mime_decode_base64_token (token, out);
       /* Advance "out" by the number of bytes just written to it. */
       out += n;
     }
   }
 
+  if (outSize)
+    *outSize = out - buffer;
   /* Now that we've altered the data in place, write it. */
   if (out > buffer)
   return data->write_buffer (buffer, (out - buffer), data->closure);
   else
   return 1;
 }
 
 
 static int
 mime_decode_uue_buffer (MimeDecoderData *data,
-            const char *input_buffer, PRInt32 input_length)
+            const char *input_buffer, PRInt32 input_length, PRInt32 *outSize)
 {
   /* First, copy input_buffer into state->line_buffer until we have
    a complete line.
 
    Then decode that line in place (in the line_buffer) and write
    it out.
 
    Then pull the next line into line_buffer and continue.
@@ -542,16 +549,20 @@ mime_decode_uue_buffer (MimeDecoderData 
 # undef DEC
 
       /* Now write out what we decoded for this line.
        */
       NS_ASSERTION(out >= line && out < in, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
       if (out > line)
       status = data->write_buffer (line, (out - line), data->closure);
 
+      // The assertion above tells us this is >= 0
+      if (outSize)
+        *outSize = out - line;
+
       /* Reset the line so that we don't think it's partial next time. */
       *line = 0;
 
       if (status < 0) /* abort */
       goto DONE;
     }
   }
 
@@ -559,17 +570,17 @@ mime_decode_uue_buffer (MimeDecoderData 
 
  DONE:
 
   return status;
 }
 
 static int
 mime_decode_yenc_buffer (MimeDecoderData *data,
-            const char *input_buffer, PRInt32 input_length)
+            const char *input_buffer, PRInt32 input_length, PRInt32 *outSize)
 {
   /* First, copy input_buffer into state->line_buffer until we have
    a complete line.
 
    Then decode that line in place (in the line_buffer) and write
    it out.
 
    Then pull the next line into line_buffer and continue.
@@ -733,16 +744,20 @@ mime_decode_yenc_buffer (MimeDecoderData
             return -1;  /* last character cannot be escape char */
           c -= 64;
         }
         c -= 42;
         *dest = c;
         dest ++;
       }
 
+      // The assertion below is helpful, too
+      if (outSize)
+        *outSize = dest - line;
+
       /* Now write out what we decoded for this line. */
       NS_ASSERTION(dest >= line && dest < src, "nothing to write!");
       if (dest > line)
       {
         status = data->write_buffer (line, dest - line, data->closure);
         if (status < 0) /* abort */
           return status;
       }
@@ -823,30 +838,31 @@ MimeUUDecoderInit (nsresult (*output_fn)
 MimeDecoderData *
 MimeYDecoderInit (nsresult (*output_fn) (const char *, PRInt32, void *),
            void *closure)
 {
   return mime_decoder_init (mime_yencode, output_fn, closure);
 }
 
 int
-MimeDecoderWrite (MimeDecoderData *data, const char *buffer, PRInt32 size)
+MimeDecoderWrite (MimeDecoderData *data, const char *buffer, PRInt32 size,
+          PRInt32 *outSize)
 {
   NS_ASSERTION(data, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
   if (!data) return -1;
   switch(data->encoding)
   {
   case mime_Base64:
-    return mime_decode_base64_buffer (data, buffer, size);
+    return mime_decode_base64_buffer (data, buffer, size, outSize);
   case mime_QuotedPrintable:
-    return mime_decode_qp_buffer (data, buffer, size);
+    return mime_decode_qp_buffer (data, buffer, size, outSize);
   case mime_uuencode:
-    return mime_decode_uue_buffer (data, buffer, size);
+    return mime_decode_uue_buffer (data, buffer, size, outSize);
   case mime_yencode:
-    return mime_decode_yenc_buffer (data, buffer, size);
+    return mime_decode_yenc_buffer (data, buffer, size, outSize);
   default:
     NS_ERROR("Invalid decoding");
     return -1;
   }
 }
 
 
 /* ================== Encoders.
--- a/mailnews/mime/src/mimeeobj.cpp
+++ b/mailnews/mime/src/mimeeobj.cpp
@@ -210,19 +210,19 @@ GOTTA STILL DO THIS FOR QUOTING!
 }
 
 static int
 MimeExternalObject_parse_buffer (const char *buffer, PRInt32 size, MimeObject *obj)
 {
   NS_ASSERTION(!obj->closed_p, "1.1 <rhp@netscape.com> 19 Mar 1999 12:00");
   if (obj->closed_p) return -1;
 
-  if (obj->output_p &&
-    obj->options &&
-    !obj->options->write_html_p)
+  // If we want to stream anyway, or we have a good reason to stream
+  if ((obj->options && obj->options->stream_all_attachments) ||
+      obj->output_p && obj->options && !obj->options->write_html_p)
   {
     /* The data will be base64-decoded and passed to
      MimeExternalObject_parse_decoded_buffer. */
     return ((MimeObjectClass*)&MIME_SUPERCLASS)->parse_buffer(buffer, size,
                                 obj);
   }
   else
   {
@@ -241,25 +241,32 @@ MimeExternalObject_parse_decoded_buffer 
    the case where we're not emitting HTML, and want access to the raw
    data itself.
 
    We override the `parse_decoded_buffer' method provided by MimeLeaf
    because, unlike most children of MimeLeaf, we do not want to line-
    buffer the decoded data -- we want to simply pass it along to the
    backend, without going through our `parse_line' method.
    */
-  if (!obj->output_p ||
-    !obj->options ||
-    obj->options->write_html_p)
+  // So unless it's intentional and we do want to actually stream it...
+  if (!(obj->options && obj->options->stream_all_attachments) &&
+      (!obj->output_p || !obj->options || obj->options->write_html_p))
   {
     NS_ERROR("MimeObject is missing some data");
     return -1;
   }
 
-  return MimeObject_write(obj, buf, size, PR_TRUE);
+  /* Don't do a roundtrip through XPConnect when we're only interested in
+   * metadata and size. 0 means ok, the caller just checks for negative return
+   * value
+   */
+  if (obj->options && obj->options->stream_all_attachments)
+    return 0;
+  else
+    return MimeObject_write(obj, buf, size, PR_TRUE);
 }
 
 
 static int
 MimeExternalObject_parse_line (const char *line, PRInt32 length, MimeObject *obj)
 {
   NS_ERROR("This method should never be called (externals do no line buffering).");
   return -1;
--- a/mailnews/mime/src/mimei.cpp
+++ b/mailnews/mime/src/mimei.cpp
@@ -1555,16 +1555,20 @@ mime_parse_url_options(const char *url, 
         //  really appreciates.
         options->show_attachment_inline_p = PR_TRUE;
         // however, show_attachment_inline_p also results in a few
         //  subclasses writing junk into the body for display purposes.
         // put a stop to these shenanigans by enabling write_pure_bodies.
         //  current offenders are:
         //  - MimeInlineImage
         options->write_pure_bodies = PR_TRUE;
+        // we also want (in this case) libmime to decode all attachments, not
+        // just the ones it displays inline, so that we can count the bytes and
+        // notify the js mime emitter about the total size of attachments
+        options->stream_all_attachments = PR_TRUE;
       }
     }
 
     q = end;
     if (*q)
       q++;
   }
 
--- a/mailnews/mime/src/mimeiimg.cpp
+++ b/mailnews/mime/src/mimeiimg.cpp
@@ -216,16 +216,23 @@ MimeInlineImage_parse_decoded_buffer (co
 {
   /* This is called (by MimeLeafClass->parse_buffer) with blocks of data
    that have already been base64-decoded.  Pass this raw image data
    along to the backend-specific image display code.
    */
   MimeInlineImage *img  = (MimeInlineImage *) obj;
   int status;
 
+  /* Don't do a roundtrip through XPConnect when we're only interested in
+   * metadata and size. 0 means ok, the caller just checks for negative return
+   * value
+   */
+  if (obj->options && obj->options->stream_all_attachments)
+    return 0;
+
   if (obj->output_p &&
     obj->options &&
     !obj->options->write_html_p)
   {
     /* in this case, we just want the raw data...
      Make the stream, if it's not made, and dump the data out.
      */
 
--- a/mailnews/mime/src/mimeleaf.cpp
+++ b/mailnews/mime/src/mimeleaf.cpp
@@ -111,16 +111,19 @@ MimeLeaf_finalize (MimeObject *object)
 
 
 static int
 MimeLeaf_parse_begin (MimeObject *obj)
 {
   MimeLeaf *leaf = (MimeLeaf *) obj;
   MimeDecoderData *(*fn) (nsresult (*) (const char*, PRInt32, void*), void*) = 0;
 
+  // Initial size is zero
+  leaf->sizeSoFar = 0;
+
   /* Initialize a decoder if necessary.
    */
   if (!obj->encoding)
   ;
   else if (!PL_strcasecmp(obj->encoding, ENCODING_BASE64))
   fn = &MimeB64DecoderInit;
   else if (!PL_strcasecmp(obj->encoding, ENCODING_QUOTED_PRINTABLE))
   leaf->decoder_data = 
@@ -162,24 +165,31 @@ MimeLeaf_parse_buffer (const char *buffe
 
   /* If we're not supposed to write this object, bug out now.
    */
   if (!obj->output_p ||
     !obj->options ||
     !obj->options->output_fn)
   return 0;
 
+  int rv;
   if (leaf->decoder_data &&
       obj->options && 
       obj->options->format_out != nsMimeOutput::nsMimeMessageDecrypt
-      && obj->options->format_out != nsMimeOutput::nsMimeMessageAttach)
-  return MimeDecoderWrite (leaf->decoder_data, buffer, size);
-  else
-  return ((MimeLeafClass *)obj->clazz)->parse_decoded_buffer (buffer, size,
+      && obj->options->format_out != nsMimeOutput::nsMimeMessageAttach) {
+    int outSize = 0;
+    rv = MimeDecoderWrite (leaf->decoder_data, buffer, size, &outSize);
+    leaf->sizeSoFar += outSize;
+  }
+  else {
+    rv = ((MimeLeafClass *)obj->clazz)->parse_decoded_buffer (buffer, size,
                                 obj);
+    leaf->sizeSoFar += size;
+  }
+  return rv;
 }
 
 static int
 MimeLeaf_parse_line (const char *line, PRInt32 length, MimeObject *obj)
 {
   NS_ERROR("MimeLeaf_parse_line shouldn't ever be called.");
   return -1;
 }
--- a/mailnews/mime/src/mimeleaf.h
+++ b/mailnews/mime/src/mimeleaf.h
@@ -73,11 +73,16 @@ struct MimeLeafClass {
 extern MimeLeafClass mimeLeafClass;
 
 struct MimeLeaf {
   MimeObject object;    /* superclass variables */
 
   /* If we're doing Base64, Quoted-Printable, or UU decoding, this is the
    state object for the decoder. */
   MimeDecoderData *decoder_data;
+
+  /* We want to count the size of the MimeObject to offer consumers the
+   * opportunity to display the sizes of attachments.
+   */
+  int sizeSoFar;
 };
 
 #endif /* _MIMELEAF_H_ */
--- a/mailnews/mime/src/mimemoz2.cpp
+++ b/mailnews/mime/src/mimemoz2.cpp
@@ -281,17 +281,17 @@ ValidateRealName(nsMsgAttachmentData *aA
     aAttach->real_name = ToNewCString(newAttachName);
   }
 }
 
 static  PRInt32     attIndex = 0;
 
 nsresult
 GenerateAttachmentData(MimeObject *object, const char *aMessageURL, MimeDisplayOptions *options,
-                       PRBool isAnAppleDoublePart, nsMsgAttachmentData *aAttachData)
+                       PRBool isAnAppleDoublePart, PRInt32 attSize, nsMsgAttachmentData *aAttachData)
 {
   nsCString imappart;
   nsCString part;
   PRBool isIMAPPart;
   PRBool isExternalAttachment = PR_FALSE;
 
   /* be sure the object has not be marked as Not to be an attachment */
   if (object->dontShowAsAttachment)
@@ -338,16 +338,17 @@ GenerateAttachmentData(MimeObject *objec
   if ((options->format_out == nsMimeOutput::nsMimeMessageBodyDisplay) && (PL_strncasecmp(aMessageURL, urlSpec, strlen(urlSpec)) == 0))
     return NS_OK;
 
   nsMsgAttachmentData *tmp = &(aAttachData[attIndex++]);
 
   tmp->real_type = object->content_type ? strdup(object->content_type) : nsnull;
   tmp->real_encoding = object->encoding ? strdup(object->encoding) : nsnull;
   tmp->isExternalAttachment = isExternalAttachment;
+  tmp->size = attSize;
 
   PRInt32 i;
   char *charset = nsnull;
   char *disp = MimeHeaders_get(object->headers, HEADER_CONTENT_DISPOSITION, PR_FALSE, PR_FALSE);
   if (disp)
   {
     tmp->real_name = MimeHeaders_get_parameter(disp, "filename", &charset, nsnull);
     if (isAnAppleDoublePart)
@@ -512,19 +513,25 @@ BuildAttachmentList(MimeObject *anObject
     PRBool isAnInlineMessage = mime_typep(child, (MimeObjectClass *) &mimeMessageClass);
 
     // AppleDouble part need special care: we need to fetch the part as well its two
     // children for the needed info as they could be anywhere, eventually, they won't contain
     // a name or file name. In any case we need to build only one attachment data
     PRBool isAnAppleDoublePart = mime_typep(child, (MimeObjectClass *) &mimeMultipartAppleDoubleClass) &&
                                  ((MimeContainer *)child)->nchildren == 2;
 
+    // -1 means we don't know the size. So far, we only give the size of leaf
+    // objects
+    PRInt32 attSize = -1;
+    if (isALeafObject)
+      attSize = ((MimeLeaf *)child)->sizeSoFar;
+
     if (isALeafObject || isAnInlineMessage || isAnAppleDoublePart)
     {
-      rv = GenerateAttachmentData(child, aMessageURL, anObject->options, isAnAppleDoublePart, aAttachData);
+      rv = GenerateAttachmentData(child, aMessageURL, anObject->options, isAnAppleDoublePart, attSize, aAttachData);
       NS_ENSURE_SUCCESS(rv, rv);
     }
 
     // Now build the attachment list for the children of our object...
     if (!isALeafObject && !isAnAppleDoublePart)
     {
       rv = BuildAttachmentList((MimeObject *)child, aAttachData, aMessageURL);
       NS_ENSURE_SUCCESS(rv, rv);
@@ -579,17 +586,17 @@ MimeGetAttachmentList(MimeObject *tobj, 
   memset(*data, 0, (n + 1) * sizeof(nsMsgAttachmentData));
 
   // Now, build the list!
 
   nsresult rv;
 
   if (isAnInlineMessage)
   {
-    rv = GenerateAttachmentData(obj, aMessageURL, obj->options, PR_FALSE, *data);
+    rv = GenerateAttachmentData(obj, aMessageURL, obj->options, PR_FALSE, -1, *data);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   return BuildAttachmentList((MimeObject *) cobj, *data, aMessageURL);
 }
 
 extern "C" void
 MimeFreeAttachmentList(nsMsgAttachmentData *data)
 {
@@ -633,38 +640,42 @@ NotifyEmittersOfAttachmentList(MimeDispl
       ++tmp;
       continue;
     }
 
     nsCAutoString spec;
     if ( tmp->url )
       tmp->url->GetSpec(spec);
 
+    nsCAutoString sizeStr;
+    sizeStr.AppendInt(tmp->size);
     mimeEmitterStartAttachment(opt, tmp->real_name, tmp->real_type, spec.get(), tmp->isExternalAttachment);
     mimeEmitterAddAttachmentField(opt, HEADER_X_MOZILLA_PART_URL, spec.get());
+    mimeEmitterAddAttachmentField(opt, HEADER_X_MOZILLA_PART_SIZE, sizeStr.get());
 
     if ( (opt->format_out == nsMimeOutput::nsMimeMessageQuoting) ||
          (opt->format_out == nsMimeOutput::nsMimeMessageBodyQuoting) ||
          (opt->format_out == nsMimeOutput::nsMimeMessageSaveAs) ||
          (opt->format_out == nsMimeOutput::nsMimeMessagePrintOutput))
     {
       mimeEmitterAddAttachmentField(opt, HEADER_CONTENT_DESCRIPTION, tmp->description);
       mimeEmitterAddAttachmentField(opt, HEADER_CONTENT_TYPE, tmp->real_type);
       mimeEmitterAddAttachmentField(opt, HEADER_CONTENT_ENCODING,    tmp->real_encoding);
 
       /* rhp - for now, just leave these here, but they are really
                not necessary
-      printf("URL for Part      : %s\n", spec);
+      printf("URL for Part      : %s\n", spec.get());
       printf("Real Name         : %s\n", tmp->real_name);
       printf("Desired Type      : %s\n", tmp->desired_type);
       printf("Real Type         : %s\n", tmp->real_type);
       printf("Real Encoding     : %s\n", tmp->real_encoding);
       printf("Description       : %s\n", tmp->description);
       printf("Mac Type          : %s\n", tmp->x_mac_type);
       printf("Mac Creator       : %s\n", tmp->x_mac_creator);
+      printf("Size              : %d\n", tmp->size);
       */
     }
 
     mimeEmitterEndAttachment(opt);
     ++i;
     ++tmp;
   }
   mimeEmitterEndAllAttachments(opt);
@@ -1445,16 +1456,17 @@ MimeDisplayOptions::MimeDisplayOptions()
 
   attachment_icon_layer_id = 0;
 
   missing_parts = PR_FALSE;
   show_attachment_inline_p = PR_FALSE;
   quote_attachment_inline_p = PR_FALSE;
   notify_nested_bodies = PR_FALSE;
   write_pure_bodies = PR_FALSE;
+  stream_all_attachments = PR_FALSE;
 }
 
 MimeDisplayOptions::~MimeDisplayOptions()
 {
   PR_FREEIF(part_to_load);
   PR_FREEIF(default_charset);
 }
 ////////////////////////////////////////////////////////////////
--- a/mailnews/mime/src/mimemsig.cpp
+++ b/mailnews/mime/src/mimemsig.cpp
@@ -436,17 +436,17 @@ MimeMultipartSigned_parse_line (const ch
     /* fall through. */
 
   case MimeMultipartSignedSignatureLine:
     if (hash_line_p)
     {
       /* Feed this line into the signature verification routines. */
 
       if (sig->sig_decoder_data)
-      status = MimeDecoderWrite (sig->sig_decoder_data, line, length);
+      status = MimeDecoderWrite (sig->sig_decoder_data, line, length, nsnull);
       else
       status = (((MimeMultipartSignedClass *) obj->clazz)
             ->crypto_signature_hash (line, length,
                          sig->crypto_closure));
       if (status < 0) return status;
     }
     break;
 
--- a/mailnews/mime/src/mimemult.cpp
+++ b/mailnews/mime/src/mimemult.cpp
@@ -162,17 +162,16 @@ MimeMultipart_parse_line (const char *li
      it to HTML, simply pass it through unaltered. */
   if (obj->output_p &&
     obj->options &&
     !obj->options->write_html_p &&
     obj->options->output_fn
           && obj->options->format_out != nsMimeOutput::nsMimeMessageAttach)
   return MimeObject_write(obj, line, length, PR_TRUE);
 
-
   if (mult->state == MimeMultipartEpilogue)  /* already done */
     boundary = MimeMultipartBoundaryTypeNone;
   else
     boundary = ((MimeMultipartClass *)obj->clazz)->check_boundary(obj, line,
                                                                   length);
 
   if (boundary == MimeMultipartBoundaryTypeTerminator ||
     boundary == MimeMultipartBoundaryTypeSeparator)
--- a/mailnews/mime/src/modlmime.h
+++ b/mailnews/mime/src/modlmime.h
@@ -401,11 +401,18 @@ public:
   PRBool notify_nested_bodies;
 
   /**
    * When true, compels mime parts to only write the actual body
    *  payload and not display-gunk like links to attachments. This was
    *  primarily introduced for the benefit of the javascript emitter.
    */
   PRBool write_pure_bodies;
+
+  /**
+   * When true, forces all attachments to be decoded and streamed to the mime
+   *  emitter. At the moment, only the JS mime emitter uses this. Other mime
+   *  emitters would probably choke.
+   */
+  PRBool stream_all_attachments;
 };
 
 #endif /* _MODLMIME_H_ */
--- a/mailnews/mime/src/modmimee.h
+++ b/mailnews/mime/src/modmimee.h
@@ -87,17 +87,18 @@ MimeEncoderData *MimeQPEncoderInit (nsre
 MimeEncoderData *MimeUUEncoderInit (const char *filename,
                   nsresult (*output_fn) (const char *buf,
                             PRInt32 size,
                             void *closure),
                   void *closure);
 
 /* Push data through the encoder/decoder, causing the above-provided write_fn
    to be called with encoded/decoded data. */
-int MimeDecoderWrite (MimeDecoderData *data, const char *buffer, PRInt32 size);
+int MimeDecoderWrite (MimeDecoderData *data, const char *buffer, PRInt32 size,
+                  PRInt32 *outSize);
 int MimeEncoderWrite (MimeEncoderData *data, const char *buffer, PRInt32 size);
 
 /* When you're done encoding/decoding, call this to free the data.  If
    abort_p is PR_FALSE, then calling this may cause the write_fn to be called
    one last time (as the last buffered data is flushed out.)
  */
 int MimeDecoderDestroy(MimeDecoderData *data, PRBool abort_p);
 int MimeEncoderDestroy(MimeEncoderData *data, PRBool abort_p);