Bug 655536: MsgHdrToMimeMessage could understand detached attachments and feed enclosures r=asuth
authorJonathan Protzenko <jonathan.protzenko@gmail.com>
Tue, 09 Aug 2011 09:50:05 -0700
changeset 8276 b1d3493ea46637b8bbb18c7a1f003580f74a1ffd
parent 8275 447605dd23677f139d315bde7caeac06ff1507de
child 8277 71b93e1bf96c235a83de95f9df0479aac4974233
push id6362
push userjonathan.protzenko@gmail.com
push dateTue, 09 Aug 2011 16:51:12 +0000
treeherdercomm-central@71b93e1bf96c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersasuth
bugs655536
Bug 655536: MsgHdrToMimeMessage could understand detached attachments and feed enclosures r=asuth
mailnews/db/gloda/components/jsmimeemitter.js
mailnews/db/gloda/modules/datamodel.js
mailnews/db/gloda/modules/fundattr.js
mailnews/db/gloda/modules/gloda.js
mailnews/db/gloda/test/unit/test_mime_emitter.js
mailnews/mime/src/mimemoz2.cpp
mailnews/test/resources/messageGenerator.js
--- a/mailnews/db/gloda/components/jsmimeemitter.js
+++ b/mailnews/db/gloda/components/jsmimeemitter.js
@@ -429,26 +429,21 @@ MimeMessageEmitter.prototype = {
     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
       if (partName) {
         // we disguise this MimeMessage into something that can be used as a
         // MimeAttachment so that it is transparent for the user code
         this._partMap[partName].url = aUrl;
-        this._partMap[partName].isExternalAttachment = aIsExternalAttachment;
+        this._partMap[partName].isExternal = 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 if (partName) {
       let part = new this._mimeMsg.MimeMessageAttachment(partName,
           aName, aContentType, aUrl, aIsExternalAttachment);
       // replace the existing part with the attachment...
       this._replacePart(part);
     }
   },
   addAttachmentField: function mime_emitter_addAttachmentField(aField, aValue) {
--- a/mailnews/db/gloda/modules/datamodel.js
+++ b/mailnews/db/gloda/modules/datamodel.js
@@ -840,29 +840,31 @@ GlodaIdentity.prototype = {
     return "";
   }
 };
 
 
 /**
  * An attachment, with as much information as we can gather on it
  */
-function GlodaAttachment(aName, aContentType, aSize, aURL) {
+function GlodaAttachment(aName, aContentType, aSize, aURL, aIsExternal) {
   // _datastore set on the prototype by GlodaDatastore
   this._name = aName;
   this._contentType = aContentType;
   this._size = aSize;
   this._url = aURL;
+  this._isExternal = aIsExternal;
 }
 
 GlodaAttachment.prototype = {
   NOUN_ID: 105,
   // set by GlodaDatastore
   get name() { return this._name; },
   get contentType() { return this._contentType; },
   get size() { return this._size; },
   get url() { return this._url; },
+  get isExternal() { return this._isExternal; },
 
   toString: function gloda_attachment_toString() {
     return "attachment: " + this._name + ":" + this._contentType;
   },
 
 };
--- a/mailnews/db/gloda/modules/fundattr.js
+++ b/mailnews/db/gloda/modules/fundattr.js
@@ -607,17 +607,22 @@ var GlodaFundAttr = {
       // provide convenience attributes to Gloda consumers, so that they can run
       // through the list of attachments of a given message, to possibly build a
       // visualization on top of it. We still reject bogus mime types, which
       // means yencode won't be supported. Oh, I feel really bad.
       let attachmentInfos = [];
       for each (let [, att] in Iterator(aMimeMsg.allUserAttachments)) {
         if (att.isRealAttachment) {
           attachmentInfos.push(
-            new GlodaAttachment(att.name, att.contentType, att.size, att.url));
+            new GlodaAttachment(att.name,
+                                att.contentType,
+                                att.size,
+                                att.url,
+                                att.isExternal)
+          );
         }
       }
       aGlodaMessage.attachmentInfos = attachmentInfos;
     }
 
     // TODO: deal with mailing lists, including implicit-to.  this will require
     //  convincing the indexer to pass us in the previous message if it is
     //  available.  (which we'll simply pass to everyone... it can help body
--- a/mailnews/db/gloda/modules/gloda.js
+++ b/mailnews/db/gloda/modules/gloda.js
@@ -1353,21 +1353,22 @@ var Gloda = {
       clazz: GlodaAttachment,
       allowsArbitraryAttrs: false,
       isPrimitive: false,
       toJSON: function (x) {
           return {
             name: x.name,
             contentType: x.contentType,
             size: x.size,
-            url: x.url
+            url: x.url,
+            isExternal: x.isExternal,
           }
         },
       fromJSON: function (x) {
-          return new GlodaAttachment(x.name, x.contentType, x.size, x.url);
+          return new GlodaAttachment(x.name, x.contentType, x.size, x.url, x.isExternal);
         },
       }, this.NOUN_ATTACHMENT);
 
     // parameterized identity is just two identities; we store the first one
     //  (whose value set must be very constrainted, like the 'me' identities)
     //  as the parameter, the second (which does not need to be constrained)
     //  as the value.
     this.defineNoun({
--- a/mailnews/db/gloda/test/unit/test_mime_emitter.js
+++ b/mailnews/db/gloda/test/unit/test_mime_emitter.js
@@ -77,16 +77,38 @@ 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!");
 
+// This is an external attachment, i.e. a mime part that basically says "go find
+// the attachment on disk, assuming it still exists, here's the path to the file
+// on disk". It turns out feed enclosures are presented in the exact same way,
+// so this covers this case as well.
+var tachExternal = {
+  body:
+    'You deleted an attachment from this message. The original MIME headers for the attachment were:\n'+
+    'Content-Type: image/png;\n'+
+    ' name="conversations-bug1.png"\n'+
+    'Content-Transfer-Encoding: base64\n'+
+    'Content-Disposition: attachment;\n'+
+    ' filename="conversations-bug1.png"',
+  contentType: 'image/png',
+  filename: "conversations-bug1.png",
+  charset: null,
+  format: null,
+  encoding: 'base64',
+  extraHeaders: {
+    'X-Mozilla-External-Attachment-URL': 'file:///tmp/conversations-bug1.png',
+    'X-Mozilla-Altered': 'AttachmentDetached; date="Wed Aug 03 11:11:33 2011"',
+  },
+};
 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'};
 var partTachInlineText = new SyntheticPartLeaf(tachInlineText.body,
                                                tachInlineText);
 
@@ -446,40 +468,47 @@ var partTachNestedMessages = [
       }
     )
   }),
   msgGen.makeMessage({
       attachments: [tachImage]
     }),
   msgGen.makeMessage({
       attachments: [tachImage, tachApplication]
-    })
+    }),
 ];
 
 var attMessagesParams = [
   {
+    attachments: [tachExternal],
+  },
+  {
     name: 'attached rfc822',
     bodyPart: new SyntheticPartMultiMixed([partAlternative,
                                            partTachNestedMessages[0]]),
   },
   {
     name: 'attached rfc822 w. image inside',
     bodyPart: new SyntheticPartMultiMixed([partAlternative,
                                            partTachNestedMessages[1]]),
   },
   {
     name: 'attached x/funky + attached rfc822 w. (image + x/funky) inside',
     bodyPart: new SyntheticPartMultiMixed([partAlternative,
                                            partTachApplication,
                                            partTachNestedMessages[2]]),
-  }
+  },
 ];
 
 var expectedAttachmentsInfo = [
   {
+    allAttachmentsContentTypes: ["image/png"],
+    allUserAttachmentsContentTypes: ["image/png"],
+  },
+  {
     allAttachmentsContentTypes: [],
     allUserAttachmentsContentTypes: ["message/rfc822"],
     firstAttachmentName: "S\u00e3o Paulo.eml",
   },
   {
     allAttachmentsContentTypes: ["image/png"],
     allUserAttachmentsContentTypes: ["message/rfc822"]
   },
@@ -491,16 +520,17 @@ var expectedAttachmentsInfo = [
 
 function test_attachments_correctness () {
   for each (let [i, params] in Iterator(attMessagesParams)) {
     let synMsg = gMessageGenerator.makeMessage(params);
     let synSet = new SyntheticMessageSet([synMsg]);
     yield add_sets_to_folder(gInbox, [synSet]);
 
     let msgHdr = synSet.getMsgHdr(0);
+    // dump(synMsg.toMboxString()+"\n\n");
 
     MsgHdrToMimeMessage(msgHdr, null, function(aMsgHdr, aMimeMsg) {
       try {
         let expected = expectedAttachmentsInfo[i];
         if ("firstAttachmentName" in expected) {
           let att = aMimeMsg.allUserAttachments[0];
           do_check_eq(att.name.length, expected.firstAttachmentName.length);
           for (let i = 0; i < att.name.length; ++i)
--- a/mailnews/mime/src/mimemoz2.cpp
+++ b/mailnews/mime/src/mimemoz2.cpp
@@ -466,16 +466,20 @@ GenerateAttachmentData(MimeObject *objec
     if (NS_SUCCEEDED(MsgEscapeString(nsDependentCString(tmp->real_name),
                                      nsINetUtil::ESCAPE_XALPHAS, aResult)))
       urlString.Append(aResult);
     else
       urlString.Append(tmp->real_name);
     if (tmp->real_type && !strcmp(tmp->real_type, "message/rfc822") &&
            !StringEndsWith(urlString, NS_LITERAL_CSTRING(".eml"), nsCaseInsensitiveCStringComparator()))
       urlString.Append(".eml");
+  } else if (tmp->isExternalAttachment) {
+    // Allows the JS mime emitter to figure out the part information.
+    urlString.Append("?part=");
+    urlString.Append(part);
   }
   nsresult rv = nsMimeNewURI(&(tmp->url), urlString.get(), nsnull);
 
   PR_FREEIF(urlSpec);
 
   if ( (NS_FAILED(rv)) || (!tmp->url) )
     return NS_ERROR_OUT_OF_MEMORY;
 
--- a/mailnews/test/resources/messageGenerator.js
+++ b/mailnews/test/resources/messageGenerator.js
@@ -114,16 +114,18 @@ function SyntheticPart(aProperties) {
     if ("boundary" in aProperties)
       this._boundary = aProperties.boundary;
     if ("encoding" in aProperties)
       this._encoding = aProperties.encoding;
     if ("contentId" in aProperties)
       this._contentId = aProperties.contentId;
     if ("disposition" in aProperties)
       this._forceDisposition = aProperties.disposition;
+    if ("extraHeaders" in aProperties)
+      this._extraHeaders = aProperties.extraHeaders;
   }
 }
 SyntheticPart.prototype = {
   _forceDisposition: null,
   get contentTypeHeaderValue() {
     let s = this._contentType;
     if (this._charset)
       s += '; charset=' + this._charset;
@@ -157,16 +159,22 @@ SyntheticPart.prototype = {
     return s;
   },
   get hasContentId() {
     return this._contentId;
   },
   get contentIdHeaderValue() {
     return '<' + this._contentId + '>';
   },
+  get hasExtraHeaders() {
+    return this._extraHeaders;
+  },
+  get extraHeaders() {
+    return this._extraHeaders;
+  },
 };
 
 /**
  * Leaf MIME part, defaulting to text/plain.
  */
 function SyntheticPartLeaf(aBody, aProperties) {
   SyntheticPart.call(this, aProperties);
   this.body = aBody;
@@ -223,16 +231,19 @@ SyntheticPartMulti.prototype = {
       if (part.hasTransferEncoding)
         s += 'Content-Transfer-Encoding: ' +
              part.contentTransferEncodingHeaderValue + '\r\n';
       if (part.hasDisposition)
         s += 'Content-Disposition: ' + part.contentDispositionHeaderValue +
              '\r\n';
       if (part.hasContentId)
         s += 'Content-ID: ' + part.contentIdHeaderValue + '\r\n';
+      if (part.hasExtraHeaders)
+        for each (let [k, v] in Iterator(part.extraHeaders))
+          s += k + ': ' + v + '\r\n';
       s += '\r\n';
       s += part.toMessageString() + '\r\n\r\n';
     }
     s += "--" + this._boundary + '--';
     return s;
   },
   prettyString: function(aIndent) {
     let nextIndent = (aIndent != null) ? (aIndent + "  ") : "";