summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/compose/src/SmtpService.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/compose/src/SmtpService.jsm')
-rw-r--r--comm/mailnews/compose/src/SmtpService.jsm350
1 files changed, 350 insertions, 0 deletions
diff --git a/comm/mailnews/compose/src/SmtpService.jsm b/comm/mailnews/compose/src/SmtpService.jsm
new file mode 100644
index 0000000000..d90983a213
--- /dev/null
+++ b/comm/mailnews/compose/src/SmtpService.jsm
@@ -0,0 +1,350 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const EXPORTED_SYMBOLS = ["SmtpService"];
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const lazy = {};
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ SmtpClient: "resource:///modules/SmtpClient.jsm",
+ MsgUtils: "resource:///modules/MimeMessageUtils.jsm",
+});
+
+/**
+ * The SMTP service.
+ *
+ * @implements {nsISmtpService}
+ */
+class SmtpService {
+ QueryInterface = ChromeUtils.generateQI(["nsISmtpService"]);
+
+ constructor() {
+ this._servers = [];
+ this._logger = lazy.MsgUtils.smtpLogger;
+ }
+
+ /**
+ * @see nsISmtpService
+ */
+ get defaultServer() {
+ let defaultServerKey = Services.prefs.getCharPref(
+ "mail.smtp.defaultserver",
+ ""
+ );
+ if (defaultServerKey) {
+ // Get it from the prefs.
+ return this.getServerByKey(defaultServerKey);
+ }
+
+ // No pref set, so set the first one as default, and return it.
+ if (this.servers.length > 0) {
+ this.defaultServer = this.servers[0];
+ return this.servers[0];
+ }
+ return null;
+ }
+
+ set defaultServer(server) {
+ Services.prefs.setCharPref("mail.smtp.defaultserver", server.key);
+ }
+
+ get servers() {
+ if (!this._servers.length) {
+ // Load SMTP servers from prefs.
+ this._servers = this._getSmtpServerKeys().map(key =>
+ this._keyToServer(key)
+ );
+ }
+ return this._servers;
+ }
+
+ get wrappedJSObject() {
+ return this;
+ }
+
+ /**
+ * @see nsISmtpService
+ */
+ async sendMailMessage(
+ messageFile,
+ recipients,
+ userIdentity,
+ sender,
+ password,
+ deliveryListener,
+ statusListener,
+ notificationCallbacks,
+ requestDSN,
+ messageId,
+ outURI,
+ outRequest
+ ) {
+ this._logger.debug(`Sending message ${messageId}`);
+ let server = this.getServerByIdentity(userIdentity);
+ if (!server) {
+ // Occurs for at least one unit test, but test does not fail if return
+ // here. This check for "server" can be removed if tests are fixed.
+ console.log(
+ `No server found for identity with email ${userIdentity.email} and ` +
+ `smtpServerKey ${userIdentity.smtpServerKey}`
+ );
+ return;
+ }
+ if (password) {
+ server.password = password;
+ }
+ let runningUrl = this._getRunningUri(server);
+ await server.wrappedJSObject.withClient(client => {
+ deliveryListener?.OnStartRunningUrl(runningUrl, 0);
+ let fresh = true;
+ client.onidle = () => {
+ // onidle can occur multiple times, but we should only init sending
+ // when sending a new message(fresh is true) or when a new connection
+ // replaces the original connection due to error 4xx response
+ // (client.isRetry is true).
+ if (!fresh && !client.isRetry) {
+ return;
+ }
+ // Init when fresh==true OR re-init sending when client.isRetry==true.
+ fresh = false;
+ let from = sender;
+ let to = MailServices.headerParser
+ .parseEncodedHeaderW(decodeURIComponent(recipients))
+ .map(rec => rec.email);
+
+ if (
+ !Services.prefs.getBoolPref(
+ "mail.smtp.useSenderForSmtpMailFrom",
+ false
+ )
+ ) {
+ from = userIdentity.email;
+ }
+ if (!messageId) {
+ messageId = Cc["@mozilla.org/messengercompose/computils;1"]
+ .createInstance(Ci.nsIMsgCompUtils)
+ .msgGenerateMessageId(userIdentity, null);
+ }
+ client.useEnvelope({
+ from: MailServices.headerParser.parseEncodedHeaderW(
+ decodeURIComponent(from)
+ )[0].email,
+ to,
+ size: messageFile.fileSize,
+ requestDSN,
+ messageId,
+ });
+ };
+ let socketOnDrain;
+ client.onready = async () => {
+ let fstream = Cc[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Ci.nsIFileInputStream);
+ // PR_RDONLY
+ fstream.init(messageFile, 0x01, 0, 0);
+
+ let sstream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ );
+ sstream.init(fstream);
+
+ let sentSize = 0;
+ let totalSize = messageFile.fileSize;
+ let progressListener = statusListener?.QueryInterface(
+ Ci.nsIWebProgressListener
+ );
+
+ while (sstream.available()) {
+ let chunk = sstream.read(65536);
+ let canSendMore = client.send(chunk);
+ if (!canSendMore) {
+ // Socket buffer is full, wait for the ondrain event.
+ await new Promise(resolve => (socketOnDrain = resolve));
+ }
+ // In practice, chunks are buffered by TCPSocket, progress reaches 100%
+ // almost immediately unless message is larger than chunk size.
+ sentSize += chunk.length;
+ progressListener?.onProgressChange(
+ null,
+ null,
+ sentSize,
+ totalSize,
+ sentSize,
+ totalSize
+ );
+ }
+ sstream.close();
+ fstream.close();
+ client.end();
+
+ // Set progress to indeterminate.
+ progressListener?.onProgressChange(null, null, 0, -1, 0, -1);
+ };
+ client.ondrain = () => {
+ // Socket buffer is empty, safe to continue sending.
+ socketOnDrain();
+ };
+ client.ondone = exitCode => {
+ if (!AppConstants.MOZ_SUITE) {
+ Services.telemetry.scalarAdd("tb.mails.sent", 1);
+ }
+ deliveryListener?.OnStopRunningUrl(runningUrl, exitCode);
+ };
+ client.onerror = (nsError, errorMessage, secInfo) => {
+ runningUrl.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ if (secInfo) {
+ // TODO(emilio): Passing the failed security info as part of the URI is
+ // quite a smell, but monkey see monkey do...
+ runningUrl.failedSecInfo = secInfo;
+ }
+ runningUrl.errorMessage = errorMessage;
+ deliveryListener?.OnStopRunningUrl(runningUrl, nsError);
+ };
+
+ outRequest.value = {
+ cancel() {
+ client.close(true);
+ },
+ };
+ });
+ }
+
+ /**
+ * @see nsISmtpService
+ */
+ verifyLogon(server, urlListener, msgWindow) {
+ let client = new lazy.SmtpClient(server);
+ client.connect();
+ let runningUrl = this._getRunningUri(server);
+ client.onerror = (nsError, errorMessage, secInfo) => {
+ runningUrl.QueryInterface(Ci.nsIMsgMailNewsUrl);
+ if (secInfo) {
+ runningUrl.failedSecInfo = secInfo;
+ }
+ runningUrl.errorMessage = errorMessage;
+ urlListener.OnStopRunningUrl(runningUrl, nsError);
+ };
+ client.onready = () => {
+ urlListener.OnStopRunningUrl(runningUrl, 0);
+ client.close();
+ };
+ return runningUrl;
+ }
+
+ /**
+ * @see nsISmtpService
+ */
+ getServerByIdentity(userIdentity) {
+ return userIdentity.smtpServerKey
+ ? this.getServerByKey(userIdentity.smtpServerKey)
+ : this.defaultServer;
+ }
+
+ /**
+ * @see nsISmtpService
+ */
+ getServerByKey(key) {
+ return this.servers.find(s => s.key == key);
+ }
+
+ /**
+ * @see nsISmtpService
+ */
+ createServer() {
+ let serverKeys = this._getSmtpServerKeys();
+ let i = 1;
+ let key;
+ do {
+ key = `smtp${i++}`;
+ } while (serverKeys.includes(key));
+
+ serverKeys.push(key);
+ this._saveSmtpServerKeys(serverKeys);
+ this._servers = []; // Reset to force repopulation of this.servers.
+ return this.servers.at(-1);
+ }
+
+ /**
+ * @see nsISmtpService
+ */
+ deleteServer(server) {
+ let serverKeys = this._getSmtpServerKeys().filter(k => k != server.key);
+ this._servers = this.servers.filter(s => s.key != server.key);
+ this._saveSmtpServerKeys(serverKeys);
+ }
+
+ /**
+ * @see nsISmtpService
+ */
+ findServer(username, hostname) {
+ username = username?.toLowerCase();
+ hostname = hostname?.toLowerCase();
+ return this.servers.find(server => {
+ if (
+ (username && server.username.toLowerCase() != username) ||
+ (hostname && server.hostname.toLowerCase() != hostname)
+ ) {
+ return false;
+ }
+ return true;
+ });
+ }
+
+ /**
+ * Get all SMTP server keys from prefs.
+ *
+ * @returns {string[]}
+ */
+ _getSmtpServerKeys() {
+ return Services.prefs
+ .getCharPref("mail.smtpservers", "")
+ .split(",")
+ .filter(Boolean);
+ }
+
+ /**
+ * Save SMTP server keys to prefs.
+ *
+ * @param {string[]} keys - The key list to save.
+ */
+ _saveSmtpServerKeys(keys) {
+ return Services.prefs.setCharPref("mail.smtpservers", keys.join(","));
+ }
+
+ /**
+ * Create an nsISmtpServer from a key.
+ *
+ * @param {string} key - The key for the SmtpServer.
+ * @returns {nsISmtpServer}
+ */
+ _keyToServer(key) {
+ let server = Cc["@mozilla.org/messenger/smtp/server;1"].createInstance(
+ Ci.nsISmtpServer
+ );
+ // Setting the server key will set up all of its other properties by
+ // reading them from the prefs.
+ server.key = key;
+ return server;
+ }
+
+ /**
+ * Get the server URI in the form of smtp://user@hostname:port.
+ *
+ * @param {nsISmtpServer} server - The SMTP server.
+ * @returns {nsIURI}
+ */
+ _getRunningUri(server) {
+ let spec = server.serverURI + (server.port ? `:${server.port}` : "");
+ return Services.io.newURI(spec);
+ }
+}