Bug 655536: MsgHdrToMimeMessage could understand detached attachments and feed enclosures r=asuth
--- 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 + " ") : "";