Bug 495057 - Crash [@ push_tag] or [@ real_write] if a message with text/enriched part is viewed. unit test with \r normalization, why not. r=bienvenu
authorAndrew Sutherland <asutherland@asutherland.org>
Fri, 11 Feb 2011 03:08:15 -0800
changeset 7125 be97fd7c7eeef58a6cc5b0a54caf16413e47fddb
parent 7124 c92d62353031e70a2e31e4c543f6df87f3ad54ec
child 7126 625f6f4e11bd2d99bed3c96feca21c2f355f6f88
push idunknown
push userunknown
push dateunknown
reviewersbienvenu
bugs495057
Bug 495057 - Crash [@ push_tag] or [@ real_write] if a message with text/enriched part is viewed. unit test with \r normalization, why not. r=bienvenu
mailnews/db/gloda/components/jsmimeemitter.js
mailnews/db/gloda/modules/mimemsg.js
mailnews/db/gloda/test/unit/test_mime_emitter.js
--- a/mailnews/db/gloda/components/jsmimeemitter.js
+++ b/mailnews/db/gloda/components/jsmimeemitter.js
@@ -150,17 +150,18 @@ MimeMessageEmitter.prototype = {
     if (indexSemi >= 0)
         aValue = aValue.substring(0, indexSemi);
     return aValue;
   },
 
   _beginPayload: function mime_emitter__beginPayload(aContentType) {
     let contentTypeNoParams = this._stripParams(aContentType).toLowerCase();
     if (contentTypeNoParams == "text/plain" ||
-        contentTypeNoParams == "text/html") {
+        contentTypeNoParams == "text/html" ||
+        contentTypeNoParams == "text/enriched") {
       this._curPart = new this._mimeMsg.MimeBody(contentTypeNoParams);
       this._writeBody = true;
     }
     else if (contentTypeNoParams == "message/rfc822") {
       // startHeader will take care of this
       this._curPart = new this._mimeMsg.MimeMessage();
       // do not fall through into the content-type setting case; this
       //  content-type needs to get clobbered by the actual content-type of
--- a/mailnews/db/gloda/modules/mimemsg.js
+++ b/mailnews/db/gloda/modules/mimemsg.js
@@ -421,29 +421,30 @@ MimeMessage.prototype = {
    * Convert the message and its hierarchy into a "pretty string".  The message
    *  and each MIME part get their own line.  The string never ends with a
    *  newline.  For a non-multi-part message, only a single line will be
    *  returned.
    * Messages have their subject displayed, attachments have their filename and
    *  content-type (ex: image/jpeg) displayed.  "Filler" classes simply have
    *  their class displayed.
    */
-  prettyString: function MimeMessage_prettyString(aVerbose, aIndent) {
+  prettyString: function MimeMessage_prettyString(aVerbose, aIndent,
+                                                  aDumpBody) {
     if (aIndent === undefined)
       aIndent = "";
     let nextIndent = aIndent + "  ";
 
     let s = "Message: " + this.headers.subject;
     if (aVerbose)
       s += this._prettyHeaderString(nextIndent);
 
     for (let iPart = 0; iPart < this.parts.length; iPart++) {
       let part = this.parts[iPart];
-      s += "\n" + nextIndent + (iPart+1) + " " + part.prettyString(aVerbose,
-                                                                   nextIndent);
+      s += "\n" + nextIndent + (iPart+1) + " " +
+        part.prettyString(aVerbose, nextIndent, aDumpBody);
     }
 
     return s;
   },
 };
 
 
 /**
@@ -484,35 +485,40 @@ MimeContainer.prototype = {
     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;
         if (part.contentType == "text/html")
           htmlPart = part;
+        // text/enriched gets transformed into HTML, use it if we don't already
+        //  have an HTML part.
+        else if (!htmlPart && part.contentType == "text/enriched")
+	  htmlPart = part;
       }
       // convert the HTML part if we have one
       if (htmlPart)
         return aMsgFolder.convertMsgSnippetToPlainText(htmlPart.body);
     }
     // if it's not alternative, recurse/aggregate using MimeMessage logic
     return MimeMessage.prototype.coerceBodyToPlaintext.call(this, aMsgFolder);
   },
-  prettyString: function MimeContainer_prettyString(aVerbose, aIndent) {
+  prettyString: function MimeContainer_prettyString(aVerbose, aIndent,
+                                                    aDumpBody) {
     let nextIndent = aIndent + "  ";
 
     let s = "Container: " + this.contentType;
     if (aVerbose)
       s += this._prettyHeaderString(nextIndent);
 
     for (let iPart = 0; iPart < this.parts.length; iPart++) {
       let part = this.parts[iPart];
-      s += "\n" + nextIndent + (iPart+1) + " " + part.prettyString(aVerbose,
-                                                                   nextIndent);
+      s += "\n" + nextIndent + (iPart+1) + " " +
+        part.prettyString(aVerbose, nextIndent, aDumpBody);
     }
 
     return s;
   },
   toString: function MimeContainer_toString() {
     return "Container: " + this.contentType;
   }
 };
@@ -546,22 +552,25 @@ MimeBody.prototype = {
   },
   set size (whatever) {
     // nop
   },
   coerceBodyToPlaintext:
       function MimeBody_coerceBodyToPlaintext(aMsgFolder) {
     if (this.contentType == "text/plain")
       return this.body;
-    if (this.contentType == "text/html")
+    // text/enriched gets transformed into HTML by libmime
+    if (this.contentType == "text/html" ||
+        this.contentType == "text/enriched")
       return aMsgFolder.convertMsgSnippetToPlainText(this.body);
     return "";
   },
-  prettyString: function MimeBody_prettyString(aVerbose, aIndent) {
-    let s = "Body: " + this.contentType + " (" + this.body.length + " bytes)";
+  prettyString: function MimeBody_prettyString(aVerbose, aIndent, aDumpBody) {
+    let s = "Body: " + this.contentType + " (" + this.body.length + " bytes" +
+      (aDumpBody ? (": '" + this.body + "'") : "") + ")";
     if (aVerbose)
       s += this._prettyHeaderString(aIndent + "  ");
     return s;
   },
   toString: function MimeBody_toString() {
     return "Body: " + this.contentType + " (" + this.body.length + " bytes)";
   }
 };
@@ -585,17 +594,18 @@ function MimeUnknown(aContentType) {
 MimeUnknown.prototype = {
   __proto__: HeaderHandlerBase,
   get allAttachments() {
     return []; // we are a leaf
   },
   get allUserAttachments() {
     return []; // we are a leaf
   },
-  prettyString: function MimeUnknown_prettyString(aVerbose, aIndent) {
+  prettyString: function MimeUnknown_prettyString(aVerbose, aIndent,
+                                                  aDumpBody) {
     let s = "Unknown: " + this.contentType;
     if (aVerbose)
       s += this._prettyHeaderString(aIndent + "  ");
     return s;
   },
   toString: function MimeUnknown_toString() {
     return "Unknown: " + this.contentType;
   }
@@ -658,15 +668,16 @@ MimeMessageAttachment.prototype = {
     return this.name != partName;
   },
   get allAttachments() {
     return [this]; // we are a leaf, so just us.
   },
   get allUserAttachments() {
     return [this];
   },
-  prettyString: function MimeMessageAttachment_prettyString(aVerbose, aIndent) {
+  prettyString: function MimeMessageAttachment_prettyString(aVerbose, aIndent,
+                                                            aDumpBody) {
     let s = "Attachment: " + this.name + ", " + this.contentType;
     if (aVerbose)
       s += this._prettyHeaderString(aIndent + "  ");
     return s;
   },
 };
--- a/mailnews/db/gloda/test/unit/test_mime_emitter.js
+++ b/mailnews/db/gloda/test/unit/test_mime_emitter.js
@@ -68,16 +68,22 @@ Components.utils.import("resource:///mod
 
 var partText = new SyntheticPartLeaf("I am text! Woo!");
 var partHtml = new SyntheticPartLeaf(
   "<html><head></head><body>I am HTML! Woo! </body></html>",
   {
     contentType: "text/html"
   }
 );
+var partEnriched = new SyntheticPartLeaf(
+  "<bold><italic>I am not a popular format! sad woo :(</italic></bold>",
+  {
+    contentType: "text/enriched"
+  }
+);
 var partAlternative = new SyntheticPartMultiAlternative([partText, partHtml]);
 var partMailingListFooter = new SyntheticPartLeaf("I am an annoying footer!");
 
 var tachText = {filename: 'bob.txt', body: 'I like cheese!'};
 var partTachText = new SyntheticPartLeaf(tachText.body, tachText);
 var tachInlineText = {filename: 'foo.txt', body: 'Rock the mic',
                       format: null, charset: null,
                       disposition: 'inline'};
@@ -113,16 +119,21 @@ var messageInfos = [
   {
     name: 'text/plain',
     bodyPart: partText,
   },
   {
     name: 'text/html',
     bodyPart: partHtml,
   },
+  // -- simply ugly
+  {
+    name: 'text/enriched',
+    bodyPart: partEnriched,
+  },
   // -- simple w/attachment
   {
     name: 'text/plain w/text attachment (=> multipart/mixed)',
     bodyPart: partText,
     attachments: [tachText],
   },
   {
     name: 'text/plain w/image attachment (=> multipart/mixed)',
@@ -247,16 +258,22 @@ var messageInfos = [
     name: 'multipart/digest',
     bodyPart: new SyntheticPartMultiDigest(partTachMessages.concat()),
   },
   // -- multipart/parallel (allegedly the same as mixed)
   {
     name: 'multipart/parallel',
     bodyPart: new SyntheticPartMultiParallel([partText, partTachImage]),
   },
+  // --- previous bugs
+  // -- bug 495057, text/enriched was being dumb
+  {
+    name: 'text/enriched inside related',
+    bodyPart: new SyntheticPartMultiRelated([partEnriched]),
+  },
   // -- empty sections
   // This was a crasher because the empty part made us try and close the
   //  child preceding the empty part a second time.  The nested multipart led
   //  to the crash providing evidence of the double-close bug but there was
   //  nothing inherently nested-multipart-requiring to trigger the double-close
   //  bug.
   {
     name: 'nested multipart with empty multipart section',
@@ -283,33 +300,52 @@ function test_stream_message(info) {
     verify_stream_message(info, synMsg, aMsgHdr, aMimeMsg);
   });
 
   yield false;
 }
 
 var deathToNewlineTypeThings = /[\r\n]+/g;
 
+/**
+ * Applies any transformations to the synthetic body part that we would expect
+ *  to happen to a message during its libmime journey.  It may be better to
+ *  just put the expected translations in the synthetic body part instead of
+ *  trying to make this method do anything complex.
+ */
+function synTransformBody(aSynBodyPart) {
+  let text = aSynBodyPart.body.trim();
+  // this transforms things into HTML apparently...
+  if (aSynBodyPart._contentType == "text/enriched") {
+    // Our job here is just to transform just enough for our example above.
+    // We also could have provided a manual translation on the body part.
+    text = text.replace("bold", "B", "g")
+               .replace("italic", "I", "g") + "\n<BR>";
+  }
+  return text;
+}
+
 function verify_body_part_equivalence(aSynBodyPart, aMimePart) {
   // the content-type devoid of parameters should match
   do_check_eq(aSynBodyPart._contentType, aMimePart.contentType);
 
   // the header representation of the content-type should also match unless
   //  this is an rfc822 part, in which case it should only match for the
   //  actual contents.
   if (aMimePart.contentType != "message/rfc822")
     do_check_eq(aSynBodyPart.contentTypeHeaderValue.replace(
                   deathToNewlineTypeThings, ""),
                 aMimePart.get("content-type").replace(
                   deathToNewlineTypeThings, ""));
 
   // XXX body part checking will get brittle if we ever actually encode things!
   if (aSynBodyPart.body && !aSynBodyPart._filename &&
       aSynBodyPart._contentType.indexOf("text/") == 0)
-    do_check_eq(aSynBodyPart.body.trim(), aMimePart.body.trim());
+    do_check_eq(synTransformBody(aSynBodyPart),
+                aMimePart.body.trim().replace("\r", "", "g"));
   if (aSynBodyPart.parts) {
     let iPart;
     let realPartOffsetCompensator = 0;
     for (iPart = 0; iPart < aSynBodyPart.parts.length; iPart++) {
       let subSyn = aSynBodyPart.parts[iPart];
       // If this is a degenerate empty, it should not produce output, so
       //  compensate for the offset drift and get on with our lives.
       if (subSyn instanceof SyntheticDegeneratePartEmpty) {
@@ -343,24 +379,26 @@ function verify_stream_message(aInfo, aS
   try {
     // aMimeMsg is normalized; it only ever actually gets one child...
     verify_body_part_equivalence(aSynMsg.bodyPart, aMimeMsg.parts[0]);
   }
   catch (ex) {
     dump("Something was wrong with the MIME rep!\n!!!!!!!!\n");
     dump("Synthetic looks like:\n  " + aSynMsg.prettyString() +
          "\n\n");
-    dump("MIME looks like:  \n" + aMimeMsg.prettyString(true, "  ") + "\n\n");
+    dump("MIME looks like:  \n" + aMimeMsg.prettyString(true, "  ", true) +
+         "\n\n");
     do_throw(ex);
   }
 
   dump("Everything is just fine.\n");
   dump("Synthetic looks like:\n  " + aSynMsg.prettyString() +
        "\n\n");
-  dump("MIME looks like:\n  " + aMimeMsg.prettyString(true, "  ") + "\n\n");
+  dump("MIME looks like:\n  " + aMimeMsg.prettyString(true, "  ", false) +
+       "\n\n");
 
   async_driver();
 }
 
 /**
  * Stream
  */
 function test_sane_bodies() {