Bug 1211292 - Support message encryption in MessageSend.jsm. r=mkmelin
This commit is contained in:
Родитель
3cd1e3c24e
Коммит
a84d75c133
|
@ -102,7 +102,8 @@ MessageSend.prototype = {
|
|||
deliverMode,
|
||||
originalMsgURI,
|
||||
compType,
|
||||
embeddedAttachments
|
||||
embeddedAttachments,
|
||||
this.sendReport
|
||||
);
|
||||
|
||||
this._messageKey = nsMsgKey_None;
|
||||
|
@ -591,9 +592,10 @@ MessageSend.prototype = {
|
|||
this._msgCopy = msgCopy;
|
||||
this._copyFile = this._messageFile;
|
||||
if (this._folderUri.startsWith("mailbox:")) {
|
||||
let { path, file: fileWriter } = await OS.File.openUnique(
|
||||
OS.Path.join(OS.Constants.Path.tmpDir, "nscopy.tmp")
|
||||
);
|
||||
this._copyFile = Services.dirsvc.get("TmpD", Ci.nsIFile);
|
||||
this._copyFile.append("nscopy.tmp");
|
||||
this._copyFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
|
||||
let fileWriter = await OS.File.open(this._copyFile.path, { write: true });
|
||||
// Add a `From - Date` line, so that nsLocalMailFolder.cpp won't add a
|
||||
// dummy envelope. The date string will be parsed by PR_ParseTimeString.
|
||||
await fileWriter.write(
|
||||
|
@ -613,10 +615,6 @@ MessageSend.prototype = {
|
|||
}
|
||||
await fileWriter.write(await OS.File.read(this._messageFile.path));
|
||||
await fileWriter.close();
|
||||
this._copyFile = Cc["@mozilla.org/file/local;1"].createInstance(
|
||||
Ci.nsIFile
|
||||
);
|
||||
this._copyFile.initWithPath(path);
|
||||
}
|
||||
// Notify nsMsgCompose about the saved folder.
|
||||
if (this._sendListener) {
|
||||
|
|
|
@ -69,7 +69,7 @@ class MimeEncoder {
|
|||
|
||||
// Allow users to override our percentage-wise guess on whether
|
||||
// the file is text or binary.
|
||||
let forceB64 = Services.prefs.getBoolPref("mailnews.send_plaintext_flowed");
|
||||
let forceB64 = Services.prefs.getBoolPref("mail.file_attach_binary");
|
||||
|
||||
// If the content-type is "image/" or something else known to be binary or
|
||||
// several flavors of newlines are present, use base64 unless we're attaching
|
||||
|
@ -85,7 +85,6 @@ class MimeEncoder {
|
|||
} else {
|
||||
// Otherwise, we need to pick an encoding based on the contents of the
|
||||
// document.
|
||||
// let encodeP = true;
|
||||
let encodeP = false;
|
||||
|
||||
// Force quoted-printable if the sender does not allow conversion to 7bit.
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
const EXPORTED_SYMBOLS = ["MimeMessage"];
|
||||
|
||||
let { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
||||
let { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
let { MimeMultiPart, MimePart } = ChromeUtils.import(
|
||||
"resource:///modules/MimePart.jsm"
|
||||
|
@ -12,6 +11,7 @@ let { MimeMultiPart, MimePart } = ChromeUtils.import(
|
|||
let { MsgUtils } = ChromeUtils.import(
|
||||
"resource:///modules/MimeMessageUtils.jsm"
|
||||
);
|
||||
let { jsmime } = ChromeUtils.import("resource:///modules/jsmime.jsm");
|
||||
|
||||
/**
|
||||
* A class to create a top MimePart and write to a tmp file. It works like this:
|
||||
|
@ -36,6 +36,7 @@ class MimeMessage {
|
|||
* @param {string} originalMsgURI
|
||||
* @param {MSG_ComposeType} compType
|
||||
* @param {nsIMsgAttachment[]} embeddedAttachments - Usually Embedded images.
|
||||
* @param {nsIMsgSendReport} sendReport - Used by _startCryptoEncapsulation.
|
||||
*/
|
||||
constructor(
|
||||
userIdentity,
|
||||
|
@ -46,7 +47,8 @@ class MimeMessage {
|
|||
deliverMode,
|
||||
originalMsgURI,
|
||||
compType,
|
||||
embeddedAttachments
|
||||
embeddedAttachments,
|
||||
sendReport
|
||||
) {
|
||||
this._userIdentity = userIdentity;
|
||||
this._compFields = compFields;
|
||||
|
@ -56,6 +58,7 @@ class MimeMessage {
|
|||
this._deliverMode = deliverMode;
|
||||
this._compType = compType;
|
||||
this._embeddedAttachments = embeddedAttachments;
|
||||
this._sendReport = sendReport;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -64,14 +67,30 @@ class MimeMessage {
|
|||
*/
|
||||
async createMessageFile() {
|
||||
let topPart = this._initMimePart();
|
||||
let { path, file: fileWriter } = await OS.File.openUnique(
|
||||
OS.Path.join(OS.Constants.Path.tmpDir, "nsemail.eml")
|
||||
);
|
||||
await topPart.write(fileWriter);
|
||||
await fileWriter.close();
|
||||
let file = Services.dirsvc.get("TmpD", Ci.nsIFile);
|
||||
file.append("nsemail.eml");
|
||||
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
|
||||
|
||||
let fstream = Cc[
|
||||
"@mozilla.org/network/file-output-stream;1"
|
||||
].createInstance(Ci.nsIFileOutputStream);
|
||||
this._fstream = Cc[
|
||||
"@mozilla.org/network/buffered-output-stream;1"
|
||||
].createInstance(Ci.nsIBufferedOutputStream);
|
||||
fstream.init(file, -1, -1, 0);
|
||||
this._fstream.init(fstream, 16 * 1024);
|
||||
|
||||
this._composeSecure = this._getComposeSecure();
|
||||
if (this._composeSecure) {
|
||||
await this._writePart(topPart);
|
||||
this._composeSecure.finishCryptoEncapsulation(false, this._sendReport);
|
||||
} else {
|
||||
await this._writePart(topPart);
|
||||
}
|
||||
|
||||
this._fstream.close();
|
||||
fstream.close();
|
||||
|
||||
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
file.initWithPath(path);
|
||||
return file;
|
||||
}
|
||||
|
||||
|
@ -123,11 +142,19 @@ class MimeMessage {
|
|||
* Collect top level headers like From/To/Subject into a Map.
|
||||
*/
|
||||
_gatherMimeHeaders() {
|
||||
let messageId = this._compFields.getHeader("message-id");
|
||||
if (!messageId) {
|
||||
let messageId = this._compFields.messageId;
|
||||
if (
|
||||
!messageId &&
|
||||
(this._compFields.to ||
|
||||
this._compFields.cc ||
|
||||
this._compFields.bcc ||
|
||||
!this._compFields.newsgroups ||
|
||||
this._userIdentity.getBoolAttribute("generate_news_message_id"))
|
||||
) {
|
||||
messageId = Cc["@mozilla.org/messengercompose/computils;1"]
|
||||
.createInstance(Ci.nsIMsgCompUtils)
|
||||
.msgGenerateMessageId(this._userIdentity);
|
||||
this._compFields.messageId = messageId;
|
||||
}
|
||||
let headers = new Map([
|
||||
["message-id", messageId],
|
||||
|
@ -387,4 +414,127 @@ class MimeMessage {
|
|||
return part;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* If crypto encaspsulation is required, returns an nsIMsgComposeSecure instance.
|
||||
* @returns {nsIMsgComposeSecure}
|
||||
*/
|
||||
_getComposeSecure() {
|
||||
let secureCompose = this._compFields.composeSecure;
|
||||
if (!secureCompose) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
!secureCompose.requiresCryptoEncapsulation(
|
||||
this._userIdentity,
|
||||
this._compFields
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return secureCompose;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pass a stream and other params to this._composeSecure to start crypto
|
||||
* encaspsulation.
|
||||
* @param {nsIOutputStream} stream - The stream to write to.
|
||||
*/
|
||||
_startCryptoEncapsulation() {
|
||||
let recipients = [
|
||||
this._compFields.to,
|
||||
this._compFields.cc,
|
||||
this._compFields.bcc,
|
||||
this._compFields.newsgroups,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(",");
|
||||
|
||||
this._composeSecure.beginCryptoEncapsulation(
|
||||
this._fstream,
|
||||
recipients,
|
||||
this._compFields,
|
||||
this._userIdentity,
|
||||
this._sendReport,
|
||||
this._deliverMode == Ci.nsIMsgSend.nsMsgSaveAsDraft
|
||||
);
|
||||
this._cryptoEncapsulationStarted = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively write an MimePart and its parts to a this._fstream.
|
||||
* @param {MimePart} curPart - The MimePart to write out.
|
||||
* @param {number} [depth=0] - Nested level of a part.
|
||||
*/
|
||||
async _writePart(curPart, depth = 0) {
|
||||
let bodyString = await curPart.getEncodedBodyString();
|
||||
|
||||
if (depth == 0 && this._composeSecure) {
|
||||
// Crypto encaspsulation will add a new content-type header.
|
||||
curPart.deleteHeader("content-type");
|
||||
if (curPart.parts.length > 1) {
|
||||
// Move child parts one layer deeper so that the message is still well
|
||||
// formed after crypto encaspsulation.
|
||||
let newChild = new MimeMultiPart("mixed");
|
||||
newChild.parts = curPart._parts;
|
||||
curPart.parts = [newChild];
|
||||
}
|
||||
}
|
||||
|
||||
// Write out headers.
|
||||
this._writeString(curPart.getHeaderString());
|
||||
|
||||
// Start crypto encaspsulation if needed.
|
||||
if (depth == 0 && this._composeSecure) {
|
||||
this._startCryptoEncapsulation();
|
||||
}
|
||||
|
||||
// Recursively write out parts.
|
||||
if (curPart.parts.length) {
|
||||
// single part message
|
||||
if (curPart.parts.length === 1) {
|
||||
await this._writePart(curPart.parts[0], depth + 1);
|
||||
this._writeString(`${bodyString}\r\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
this._writeString("\r\n");
|
||||
if (depth == 0) {
|
||||
// Current part is a top part and multipart container.
|
||||
this._writeString("This is a multi-part message in MIME format.\r\n");
|
||||
}
|
||||
|
||||
// multipart message
|
||||
for (let part of curPart.parts) {
|
||||
this._writeString(`--${curPart.separator}\r\n`);
|
||||
await this._writePart(part, depth + 1);
|
||||
}
|
||||
this._writeString(`--${curPart.separator}--\r\n`);
|
||||
}
|
||||
|
||||
// Write out body.
|
||||
this._writeBinaryString(`\r\n${bodyString}\r\n`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a binary string to this._fstream.
|
||||
*
|
||||
* @param {BinaryString} str - The binary string to write.
|
||||
*/
|
||||
_writeBinaryString(str) {
|
||||
this._cryptoEncapsulationStarted
|
||||
? this._composeSecure.mimeCryptoWriteBlock(str, str.length)
|
||||
: this._fstream.write(str, str.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a string to this._fstream.
|
||||
*
|
||||
* @param {string} str - The string to write.
|
||||
*/
|
||||
_writeString(str) {
|
||||
this._writeBinaryString(
|
||||
jsmime.mimeutils.typedArrayToString(new TextEncoder().encode(str))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
const EXPORTED_SYMBOLS = ["MimePart", "MimeMultiPart"];
|
||||
|
||||
let { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
|
||||
let { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||
let { jsmime } = ChromeUtils.import("resource:///modules/jsmime.jsm");
|
||||
let { MimeEncoder } = ChromeUtils.import("resource:///modules/MimeEncoder.jsm");
|
||||
|
@ -41,9 +42,37 @@ class MimePart {
|
|||
this._parts = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {BinaryString} text - The string to use as body.
|
||||
*/
|
||||
set bodyText(text) {
|
||||
this._bodyText = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {MimePart[]} - The child parts.
|
||||
*/
|
||||
get parts() {
|
||||
return this._parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {MimePart[]} parts - The child parts.
|
||||
*/
|
||||
set parts(parts) {
|
||||
this._parts = parts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {string} - The separator string.
|
||||
*/
|
||||
get separator() {
|
||||
return this._separator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a header.
|
||||
* @param {string} name - The header name, e.g. "Content-Type".
|
||||
* @param {string} name - The header name, e.g. "content-type".
|
||||
* @param {string} content - The header content, e.g. "text/plain".
|
||||
*/
|
||||
setHeader(name, content) {
|
||||
|
@ -67,6 +96,14 @@ class MimePart {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a header.
|
||||
* @param {string} name - The header name to delete, e.g. "content-type".
|
||||
*/
|
||||
deleteHeader(name) {
|
||||
this._headers.delete(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set headers by an iterable.
|
||||
* @param {Iterable.<string, string>} entries - The header entries.
|
||||
|
@ -77,13 +114,6 @@ class MimePart {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {BinaryString} text - The string to use as body.
|
||||
*/
|
||||
set bodyText(text) {
|
||||
this._bodyText = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an attachment as body, with optional contentDisposition and contentId.
|
||||
* @param {nsIMsgAttachment} attachment - The attachment to use as body.
|
||||
|
@ -116,11 +146,53 @@ class MimePart {
|
|||
this._parts.push(...parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pick an encoding according to _bodyText or _bodyAttachment content. Set
|
||||
* content-transfer-encoding header, then return the encoded value.
|
||||
* @returns {BinaryString}
|
||||
*/
|
||||
async getEncodedBodyString() {
|
||||
let bodyString = this._bodyText;
|
||||
// If this is an attachment part, use the attachment content as bodyString.
|
||||
if (this._bodyAttachment) {
|
||||
bodyString = await this._fetchFile();
|
||||
}
|
||||
if (bodyString) {
|
||||
let encoder = new MimeEncoder(
|
||||
this._charset,
|
||||
this._contentType,
|
||||
this._forceMsgEncoding,
|
||||
this._isMainBody,
|
||||
bodyString
|
||||
);
|
||||
encoder.pickEncoding();
|
||||
this.setHeader("content-transfer-encoding", encoder.encoding);
|
||||
bodyString = encoder.encode();
|
||||
} else if (this._isMainBody) {
|
||||
this.setHeader("content-transfer-encoding", "7bit");
|
||||
}
|
||||
return bodyString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use jsmime to convert _headers to string.
|
||||
* @returns {string}
|
||||
*/
|
||||
getHeaderString() {
|
||||
return jsmime.headeremitter.emitStructuredHeaders(this._headers, {
|
||||
useASCII: true,
|
||||
sanitizeDate: Services.prefs.getBoolPref(
|
||||
"mail.sanitize_date_header",
|
||||
false
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the attachment file to get its content type and content.
|
||||
* @returns {string}
|
||||
*/
|
||||
async fetchFile() {
|
||||
async _fetchFile() {
|
||||
let url = this._bodyAttachment.url;
|
||||
let headers = {};
|
||||
|
||||
|
@ -148,7 +220,9 @@ class MimePart {
|
|||
});
|
||||
// Content-Type is sometimes text/plain;charset=US-ASCII, discard the
|
||||
// charset.
|
||||
this._contentType = res.headers.get("content-type").split(";")[0];
|
||||
this._contentType =
|
||||
this._bodyAttachment.contentType ||
|
||||
res.headers.get("content-type").split(";")[0];
|
||||
|
||||
let parmFolding = Services.prefs.getIntPref(
|
||||
"mail.strictly_mime.parm_folding",
|
||||
|
@ -187,95 +261,16 @@ class MimePart {
|
|||
this.setHeader("content-location", contentLocation);
|
||||
}
|
||||
|
||||
if (this._bodyAttachment.temporary) {
|
||||
let handler = Services.io
|
||||
.getProtocolHandler("file")
|
||||
.QueryInterface(Ci.nsIFileProtocolHandler);
|
||||
// Get an nsIFile from file:///tmp/key.asc.
|
||||
let file = handler.getFileFromURLSpec(this._bodyAttachment.url);
|
||||
OS.File.remove(file.path);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively write a MimePart and its parts to a file.
|
||||
* @param {OS.File} file - The output file to contain a RFC2045 message.
|
||||
* @param {number} [depth=0] - Nested level of a part.
|
||||
*/
|
||||
async write(file, depth = 0) {
|
||||
this._outFile = file;
|
||||
let bodyString = this._bodyText;
|
||||
// If this is an attachment part, use the attachment content as bodyString.
|
||||
if (this._bodyAttachment) {
|
||||
bodyString = await this.fetchFile();
|
||||
}
|
||||
if (bodyString) {
|
||||
let encoder = new MimeEncoder(
|
||||
this._charset,
|
||||
this._contentType,
|
||||
this._forceMsgEncoding,
|
||||
this._isMainBody,
|
||||
bodyString
|
||||
);
|
||||
encoder.pickEncoding();
|
||||
this.setHeader("content-transfer-encoding", encoder.encoding);
|
||||
bodyString = encoder.encode();
|
||||
} else if (this._isMainBody) {
|
||||
this.setHeader("content-transfer-encoding", "7bit");
|
||||
}
|
||||
|
||||
// Write out headers.
|
||||
await this._writeString(
|
||||
jsmime.headeremitter.emitStructuredHeaders(this._headers, {
|
||||
useASCII: true,
|
||||
sanitizeDate: Services.prefs.getBoolPref(
|
||||
"mail.sanitize_date_header",
|
||||
false
|
||||
),
|
||||
})
|
||||
);
|
||||
|
||||
// Recursively write out parts.
|
||||
if (this._parts.length) {
|
||||
// single part message
|
||||
if (!this._separator && this._parts.length === 1) {
|
||||
await this._parts[0].write(file, depth + 1);
|
||||
await this._writeString(`${bodyString}\r\n`);
|
||||
return;
|
||||
}
|
||||
|
||||
await this._writeString("\r\n");
|
||||
if (depth == 0) {
|
||||
// Current part is a top part and multipart container.
|
||||
await this._writeString(
|
||||
"This is a multi-part message in MIME format.\r\n"
|
||||
);
|
||||
}
|
||||
|
||||
// multipart message
|
||||
for (let part of this._parts) {
|
||||
await this._writeString(`--${this._separator}\r\n`);
|
||||
await part.write(file, depth + 1);
|
||||
}
|
||||
await this._writeString(`--${this._separator}--\r\n`);
|
||||
}
|
||||
|
||||
// Write out body.
|
||||
await this._writeBinaryString(`\r\n${bodyString}\r\n`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a binary string to this._outFile, used only for part body, which
|
||||
* is either from nsACString or from this.fetchFile.
|
||||
*
|
||||
* @param {string} str - The binary string to write.
|
||||
*/
|
||||
async _writeBinaryString(str) {
|
||||
await this._outFile.write(jsmime.mimeutils.stringToTypedArray(str));
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a string to this._outFile, used for part headers, which are expected
|
||||
* to be UTF-8.
|
||||
*
|
||||
* @param {string} str - The string to write.
|
||||
*/
|
||||
async _writeString(str) {
|
||||
await this._outFile.write(new TextEncoder().encode(str));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -44,12 +44,7 @@ var progressListener = {
|
|||
* patterns.
|
||||
*/
|
||||
async function getTemporaryFilesCount() {
|
||||
let tmpDir;
|
||||
if (Services.prefs.getBoolPref("mailnews.send.jsmodule", false)) {
|
||||
tmpDir = OS.Constants.Path.tmpDir;
|
||||
} else {
|
||||
tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile).path;
|
||||
}
|
||||
let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile).path;
|
||||
let iterator = new OS.File.DirectoryIterator(tmpDir);
|
||||
let tempFiles = {
|
||||
"nsemail.html": 0, // not actually used by MessageSend.jsm
|
||||
|
|
Загрузка…
Ссылка в новой задаче