--- 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);