diff --git a/mail/components/extensions/parent/ext-messages.js b/mail/components/extensions/parent/ext-messages.js
index 3657549dd0..ca6d3a84da 100644
--- a/mail/components/extensions/parent/ext-messages.js
+++ b/mail/components/extensions/parent/ext-messages.js
@@ -754,15 +754,34 @@ this.messages = class extends ExtensionAPIPersistent {
}
return convertMessagePart(mimeMsg);
},
- async getRaw(messageId) {
+ async getRaw(messageId, options) {
+ let data_format = options?.data_format;
+ if (!["File", "BinaryString"].includes(data_format)) {
+ data_format =
+ extension.manifestVersion < 3 ? "BinaryString" : "File";
+ }
+
let msgHdr = messageTracker.getMessage(messageId);
if (!msgHdr) {
throw new ExtensionError(`Message not found: ${messageId}.`);
}
- return getRawMessage(msgHdr).catch(ex => {
+ try {
+ let raw = await getRawMessage(msgHdr);
+ if (data_format == "File") {
+ // Convert binary string to Uint8Array and return a File.
+ let bytes = new Uint8Array(raw.length);
+ for (let i = 0; i < raw.length; i++) {
+ bytes[i] = raw.charCodeAt(i) & 0xff;
+ }
+ return new File([bytes], `message-${messageId}.eml`, {
+ type: "message/rfc822",
+ });
+ }
+ return raw;
+ } catch (ex) {
console.error(ex);
throw new ExtensionError(`Error reading message ${messageId}`);
- });
+ }
},
async listAttachments(messageId) {
let msgHdr = messageTracker.getMessage(messageId);
diff --git a/mail/components/extensions/schemas/messages.json b/mail/components/extensions/schemas/messages.json
index 02519f0213..1ab7d35b49 100644
--- a/mail/components/extensions/schemas/messages.json
+++ b/mail/components/extensions/schemas/messages.json
@@ -461,20 +461,52 @@
{
"name": "getRaw",
"type": "function",
- "description": "Returns the unmodified source of a message as a `binary string <|link-binary-string|>`__, which is a simple series of 8-bit values. Throws if the message could not be read, for example due to network issues. If the message contains non-ASCII characters, the body parts in the binary string cannot be read directly and must be decoded according to their character sets. Use :ref:`messages.getFull` to get the correctly decoded parts. Manually decoding the raw message is probably too error-prone, especially if the message contains MIME parts with different character set encodings or attachments.\n\nTo get a readable version of the raw message as it appears in Thunderbird's message source view, it may be sufficient to decode the message according to the character set specified in its main `content-type <|link-content-type|>`__ header (example: text/html; charset=UTF-8) using the following function (see MDN for `supported input encodings <|link-input-encoding|>`__): includes/messages/decodeBinaryString.jsJavaScript",
+ "description": "Returns the unmodified source of a message. Throws if the message could not be read, for example due to network issues.",
"async": "callback",
"parameters": [
{
"name": "messageId",
"type": "integer"
},
+ {
+ "name": "options",
+ "type": "object",
+ "properties": {
+ "data_format": {
+ "choices": [
+ {
+ "max_manifest_version": 2,
+ "description": "The message can either be returned as a DOM File or as a `binary string <|link-binary-string|>`__. The historic default is to return a binary string (kept for backward compatibility). However, it is now recommended to use the ``File`` format, because the DOM File object can be used as-is with the downloads API and has useful methods to access the content, like `File.text() <|link-DOMFile-text|>`__ and `File.arrayBuffer() <|link-DOMFile-arrayBuffer|>`__. Working with binary strings is error prone and needs special handling: includes/messages/decodeBinaryString.jsJavaScript (see MDN for `supported input encodings <|link-input-encoding|>`__).",
+ "type": "string",
+ "enum": ["File", "BinaryString"]
+ },
+ {
+ "min_manifest_version": 3,
+ "description": "The message can either be returned as a DOM File (default) or as a `binary string <|link-binary-string|>`__. It is recommended to use the ``File`` format, because the DOM File object can be used as-is with the downloads API and has useful methods to access the content, like `File.text() <|link-DOMFile-text|>`__ and `File.arrayBuffer() <|link-DOMFile-arrayBuffer|>`__. Working with binary strings is error prone and needs special handling: includes/messages/decodeBinaryString.jsJavaScript (see MDN for `supported input encodings <|link-input-encoding|>`__).",
+ "type": "string",
+ "enum": ["File", "BinaryString"]
+ }
+ ]
+ }
+ },
+ "optional": true
+ },
{
"type": "function",
"name": "callback",
"optional": true,
"parameters": [
{
- "type": "string"
+ "choices": [
+ {
+ "type": "string"
+ },
+ {
+ "type": "object",
+ "isInstanceOf": "File",
+ "additionalProperties": true
+ }
+ ]
}
]
}
diff --git a/mail/components/extensions/test/xpcshell/test_ext_messages_get.js b/mail/components/extensions/test/xpcshell/test_ext_messages_get.js
index 94c1b8d34d..d0da1a3459 100644
--- a/mail/components/extensions/test/xpcshell/test_ext_messages_get.js
+++ b/mail/components/extensions/test/xpcshell/test_ext_messages_get.js
@@ -27,7 +27,7 @@ const OPENPGP_KEY_PATH = PathUtils.join(
* is unique and there are minor differences between the account
* implementations, we don't compare exactly with a reference message.
*/
-add_task(async function test_plain() {
+add_task(async function test_plain_mv2() {
let _account = createAccount();
let _folder = await createSubfolder(
_account.incomingServer.rootFolder,
@@ -46,6 +46,20 @@ add_task(async function test_plain() {
browser.test.assertEq(1, messages.length);
let [message] = messages;
+
+ // Expected message content:
+ // -------------------------
+ // From andy@anway.invalid
+ // Content-Type: text/plain; charset=ISO-8859-1; format=flowed
+ // Subject: Big Meeting Today
+ // From: "Andy Anway"
+ // To: "Bob Bell"
+ // Message-Id: <0@made.up.invalid>
+ // Date: Wed, 06 Nov 2019 22:37:40 +1300
+ //
+ // Hello Bob Bell!
+ //
+
browser.test.assertEq("Big Meeting Today", message.subject);
browser.test.assertEq(
'"Andy Anway" ',
@@ -60,31 +74,35 @@ add_task(async function test_plain() {
);
}
- // From andy@anway.invalid
- // Content-Type: text/plain; charset=ISO-8859-1; format=flowed
- // Subject: Big Meeting Today
- // From: "Andy Anway"
- // To: "Bob Bell"
- // Message-Id: <0@made.up.invalid>
- // Date: Wed, 06 Nov 2019 22:37:40 +1300
- //
- // Hello Bob Bell!
- //
+ let strMessage_1 = await browser.messages.getRaw(message.id);
+ browser.test.assertEq("string", typeof strMessage_1);
+ let strMessage_2 = await browser.messages.getRaw(message.id, {
+ data_format: "BinaryString",
+ });
+ browser.test.assertEq("string", typeof strMessage_2);
+ let fileMessage_3 = await browser.messages.getRaw(message.id, {
+ data_format: "File",
+ });
+ // eslint-disable-next-line mozilla/use-isInstance
+ browser.test.assertTrue(fileMessage_3 instanceof File);
+ // Since we do not have utf-8 chars in the test message, the returned BinaryString is
+ // identical to the return value of File.text().
+ let strMessage_3 = await fileMessage_3.text();
- let rawMessage = await browser.messages.getRaw(message.id);
- // Fold Windows line-endings \r\n to \n.
- rawMessage = rawMessage.replace(/\r/g, "");
- browser.test.assertEq("string", typeof rawMessage);
- browser.test.assertTrue(
- rawMessage.includes("Subject: Big Meeting Today\n")
- );
- browser.test.assertTrue(
- rawMessage.includes('From: "Andy Anway" \n')
- );
- browser.test.assertTrue(
- rawMessage.includes('To: "Bob Bell" \n')
- );
- browser.test.assertTrue(rawMessage.includes("Hello Bob Bell!"));
+ for (let strMessage of [strMessage_1, strMessage_2, strMessage_3]) {
+ // Fold Windows line-endings \r\n to \n.
+ strMessage = strMessage.replace(/\r/g, "");
+ browser.test.assertTrue(
+ strMessage.includes("Subject: Big Meeting Today\n")
+ );
+ browser.test.assertTrue(
+ strMessage.includes('From: "Andy Anway" \n')
+ );
+ browser.test.assertTrue(
+ strMessage.includes('To: "Bob Bell" \n')
+ );
+ browser.test.assertTrue(strMessage.includes("Hello Bob Bell!"));
+ }
// {
// "contentType": "message/rfc822",
@@ -162,6 +180,166 @@ add_task(async function test_plain() {
cleanUpAccount(_account);
});
+add_task(async function test_plain_mv3() {
+ let _account = createAccount();
+ let _folder = await createSubfolder(
+ _account.incomingServer.rootFolder,
+ "test1"
+ );
+ await createMessages(_folder, 1);
+
+ let extension = ExtensionTestUtils.loadExtension({
+ background: async () => {
+ let accounts = await browser.accounts.list();
+ browser.test.assertEq(1, accounts.length);
+
+ for (let account of accounts) {
+ let folder = account.folders.find(f => f.name == "test1");
+ let { messages } = await browser.messages.list(folder);
+ browser.test.assertEq(1, messages.length);
+
+ let [message] = messages;
+
+ // Expected message content:
+ // -------------------------
+ // From chris@clarke.invalid
+ // Content-Type: text/plain; charset=ISO-8859-1; format=flowed
+ // Subject: Small Party Tomorrow
+ // From: "Chris Clarke"
+ // To: "David Davol"
+ // Message-Id: <1@made.up.invalid>
+ // Date: Tue, 01 Feb 2000 01:00:00 +0100
+ //
+ // Hello David Davol!
+ //
+
+ browser.test.assertEq("Small Party Tomorrow", message.subject);
+ browser.test.assertEq(
+ '"Chris Clarke" ',
+ message.author
+ );
+
+ // The msgHdr of NNTP messages have no recipients.
+ if (account.type != "nntp") {
+ browser.test.assertEq(
+ "David Davol ",
+ message.recipients[0]
+ );
+ }
+
+ let fileMessage_1 = await browser.messages.getRaw(message.id);
+ // eslint-disable-next-line mozilla/use-isInstance
+ browser.test.assertTrue(fileMessage_1 instanceof File);
+ // Since we do not have utf-8 chars in the test message, the returned
+ // BinaryString is identical to the return value of File.text().
+ let strMessage_1 = await fileMessage_1.text();
+
+ let strMessage_2 = await browser.messages.getRaw(message.id, {
+ data_format: "BinaryString",
+ });
+ browser.test.assertEq("string", typeof strMessage_2);
+
+ let fileMessage_3 = await browser.messages.getRaw(message.id, {
+ data_format: "File",
+ });
+ // eslint-disable-next-line mozilla/use-isInstance
+ browser.test.assertTrue(fileMessage_3 instanceof File);
+ let strMessage_3 = await fileMessage_3.text();
+
+ for (let strMessage of [strMessage_1, strMessage_2, strMessage_3]) {
+ // Fold Windows line-endings \r\n to \n.
+ strMessage = strMessage.replace(/\r/g, "");
+ browser.test.assertTrue(
+ strMessage.includes("Subject: Small Party Tomorrow\n")
+ );
+ browser.test.assertTrue(
+ strMessage.includes('From: "Chris Clarke" \n')
+ );
+ browser.test.assertTrue(
+ strMessage.includes('To: "David Davol" \n')
+ );
+ browser.test.assertTrue(strMessage.includes("Hello David Davol!"));
+ }
+
+ // {
+ // "contentType": "message/rfc822",
+ // "headers": {
+ // "content-type": ["text/plain; charset=ISO-8859-1; format=flowed"],
+ // "subject": ["Small Party Tomorrow"],
+ // "from": ["\"Chris Clarke\" "],
+ // "to": ["\"David Davol\" "],
+ // "message-id": ["<1@made.up.invalid>"],
+ // "date": ["Tue, 01 Feb 2000 01:00:00 +0100"]
+ // },
+ // "partName": "",
+ // "size": 20,
+ // "parts": [
+ // {
+ // "body": "David Davol!\n\n",
+ // "contentType": "text/plain",
+ // "headers": {
+ // "content-type": ["text/plain; charset=ISO-8859-1; format=flowed"]
+ // },
+ // "partName": "1",
+ // "size": 20
+ // }
+ // ]
+ // }
+
+ let fullMessage = await browser.messages.getFull(message.id);
+ browser.test.log(JSON.stringify(fullMessage));
+ browser.test.assertEq("object", typeof fullMessage);
+ browser.test.assertEq("message/rfc822", fullMessage.contentType);
+
+ browser.test.assertEq("object", typeof fullMessage.headers);
+ for (let header of [
+ "content-type",
+ "date",
+ "from",
+ "message-id",
+ "subject",
+ "to",
+ ]) {
+ browser.test.assertTrue(Array.isArray(fullMessage.headers[header]));
+ browser.test.assertEq(1, fullMessage.headers[header].length);
+ }
+ browser.test.assertEq(
+ "Small Party Tomorrow",
+ fullMessage.headers.subject[0]
+ );
+ browser.test.assertEq(
+ '"Chris Clarke" ',
+ fullMessage.headers.from[0]
+ );
+ browser.test.assertEq(
+ '"David Davol" ',
+ fullMessage.headers.to[0]
+ );
+
+ browser.test.assertTrue(Array.isArray(fullMessage.parts));
+ browser.test.assertEq(1, fullMessage.parts.length);
+ browser.test.assertEq("object", typeof fullMessage.parts[0]);
+ browser.test.assertEq(
+ "Hello David Davol!",
+ fullMessage.parts[0].body.trimRight()
+ );
+ }
+
+ browser.test.notifyPass("finished");
+ },
+ manifest: {
+ manifest_version: 3,
+ permissions: ["accountsRead", "messagesRead"],
+ },
+ });
+
+ await extension.startup();
+ await extension.awaitFinish("finished");
+ await extension.unload();
+
+ cleanUpAccount(_account);
+});
+
/**
* Test that mime parsers for all message types retrieve the correctly decoded
* headers and bodies. Bodies should no not be returned, if it is an attachment.