Bug 1211292 - Support message encryption in MessageSend.jsm. r=mkmelin

This commit is contained in:
Ping Chen 2020-10-13 13:00:31 +03:00
Родитель 3cd1e3c24e
Коммит a84d75c133
5 изменённых файлов: 261 добавлений и 124 удалений

Просмотреть файл

@ -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