summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/base/src/MsgIncomingServer.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mailnews/base/src/MsgIncomingServer.jsm')
-rw-r--r--comm/mailnews/base/src/MsgIncomingServer.jsm1268
1 files changed, 1268 insertions, 0 deletions
diff --git a/comm/mailnews/base/src/MsgIncomingServer.jsm b/comm/mailnews/base/src/MsgIncomingServer.jsm
new file mode 100644
index 0000000000..768fe9340e
--- /dev/null
+++ b/comm/mailnews/base/src/MsgIncomingServer.jsm
@@ -0,0 +1,1268 @@
+/* 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 = ["migrateServerUris", "MsgIncomingServer"];
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+/**
+ * When hostname/username changes, update the corresponding entry in
+ * nsILoginManager.
+ *
+ * @param {string} localStoreType - The store type of the current server.
+ * @param {string} oldHostname - The hostname before the change.
+ * @param {string} oldUsername - The username before the change.
+ * @param {string} newHostname - The hostname after the change.
+ * @param {string} newUsername - The username after the change.
+ */
+function migratePassword(
+ localStoreType,
+ oldHostname,
+ oldUsername,
+ newHostname,
+ newUsername
+) {
+ // When constructing nsIURI, need to wrap IPv6 address in [].
+ oldHostname = oldHostname.includes(":") ? `[${oldHostname}]` : oldHostname;
+ let oldServerUri = `${localStoreType}://${encodeURIComponent(oldHostname)}`;
+ newHostname = newHostname.includes(":") ? `[${newHostname}]` : newHostname;
+ let newServerUri = `${localStoreType}://${encodeURIComponent(newHostname)}`;
+
+ let logins = Services.logins.findLogins(oldServerUri, "", oldServerUri);
+ for (let login of logins) {
+ if (login.username == oldUsername) {
+ // If a nsILoginInfo exists for the old hostname/username, update it to
+ // use the new hostname/username.
+ let newLogin = Cc[
+ "@mozilla.org/login-manager/loginInfo;1"
+ ].createInstance(Ci.nsILoginInfo);
+ newLogin.init(
+ newServerUri,
+ null,
+ newServerUri,
+ newUsername,
+ login.password,
+ "",
+ ""
+ );
+ Services.logins.modifyLogin(login, newLogin);
+ }
+ }
+}
+
+/**
+ * When hostname/username changes, update the folder attributes in related
+ * identities.
+ *
+ * @param {string} oldServerUri - The server uri before the change.
+ * @param {string} newServerUri - The server uri after the change.
+ */
+function migrateIdentities(oldServerUri, newServerUri) {
+ for (let identity of MailServices.accounts.allIdentities) {
+ let attributes = [
+ "fcc_folder",
+ "draft_folder",
+ "archive_folder",
+ "stationery_folder",
+ ];
+ for (let attr of attributes) {
+ let folderUri = identity.getUnicharAttribute(attr);
+ if (folderUri.startsWith(oldServerUri)) {
+ identity.setUnicharAttribute(
+ attr,
+ folderUri.replace(oldServerUri, newServerUri)
+ );
+ }
+ }
+ }
+}
+
+/**
+ * When hostname/username changes, update .spamActionTargetAccount and
+ * .spamActionTargetFolder prefs.
+ *
+ * @param {string} oldServerUri - The server uri before the change.
+ * @param {string} newServerUri - The server uri after the change.
+ */
+function migrateSpamActions(oldServerUri, newServerUri) {
+ for (let server of MailServices.accounts.allServers) {
+ let targetAccount = server.getCharValue("spamActionTargetAccount");
+ let targetFolder = server.getUnicharValue("spamActionTargetFolder");
+ if (targetAccount.startsWith(oldServerUri)) {
+ server.setCharValue(
+ "spamActionTargetAccount",
+ targetAccount.replace(oldServerUri, newServerUri)
+ );
+ }
+ if (targetFolder.startsWith(oldServerUri)) {
+ server.setUnicharValue(
+ "spamActionTargetFolder",
+ targetFolder.replace(oldServerUri, newServerUri)
+ );
+ }
+ }
+}
+
+/**
+ * When hostname/username changes, update targetFolderUri in related filters
+ * to the new folder uri.
+ *
+ * @param {string} oldServerUri - The server uri before the change.
+ * @param {string} newServerUri - The server uri after the change.
+ */
+function migrateFilters(oldServerUri, newServerUri) {
+ for (let server of MailServices.accounts.allServers) {
+ let filterList;
+ try {
+ filterList = server.getFilterList(null);
+ if (!server.canHaveFilters || !filterList) {
+ continue;
+ }
+ } catch (e) {
+ continue;
+ }
+ let changed = false;
+ for (let i = 0; i < filterList.filterCount; i++) {
+ let filter = filterList.getFilterAt(i);
+ for (let action of filter.sortedActionList) {
+ let targetFolderUri;
+ try {
+ targetFolderUri = action.targetFolderUri;
+ } catch (e) {
+ continue;
+ }
+ if (targetFolderUri.startsWith(oldServerUri)) {
+ action.targetFolderUri = targetFolderUri.replace(
+ oldServerUri,
+ newServerUri
+ );
+ changed = true;
+ }
+ }
+ }
+ if (changed) {
+ filterList.saveToDefaultFile();
+ }
+ }
+}
+
+/**
+ * Migrate server uris in LoginManager and various account/folder prefs.
+ *
+ * @param {string} localStoreType - The store type of the current server.
+ * @param {string} oldHostname - The hostname before the change.
+ * @param {string} oldUsername - The username before the change.
+ * @param {string} newHostname - The hostname after the change.
+ * @param {string} newUsername - The username after the change.
+ */
+function migrateServerUris(
+ localStoreType,
+ oldHostname,
+ oldUsername,
+ newHostname,
+ newUsername
+) {
+ try {
+ migratePassword(
+ localStoreType,
+ oldHostname,
+ oldUsername,
+ newHostname,
+ newUsername
+ );
+ } catch (e) {
+ console.error(e);
+ }
+
+ let oldAuth = oldUsername ? `${encodeURIComponent(oldUsername)}@` : "";
+ let newAuth = newUsername ? `${encodeURIComponent(newUsername)}@` : "";
+ // When constructing nsIURI, need to wrap IPv6 address in [].
+ oldHostname = oldHostname.includes(":") ? `[${oldHostname}]` : oldHostname;
+ let oldServerUri = `${localStoreType}://${oldAuth}${encodeURIComponent(
+ oldHostname
+ )}`;
+ newHostname = newHostname.includes(":") ? `[${newHostname}]` : newHostname;
+ let newServerUri = `${localStoreType}://${newAuth}${encodeURIComponent(
+ newHostname
+ )}`;
+
+ try {
+ migrateIdentities(oldServerUri, newServerUri);
+ } catch (e) {
+ console.error(e);
+ }
+ try {
+ migrateSpamActions(oldServerUri, newServerUri);
+ } catch (e) {
+ console.error(e);
+ }
+ try {
+ migrateFilters(oldServerUri, newServerUri);
+ } catch (e) {
+ console.error(e);
+ }
+}
+
+/**
+ * A base class for incoming server, should not be used directly.
+ *
+ * @implements {nsIMsgIncomingServer}
+ * @implements {nsISupportsWeakReference}
+ * @implements {nsIObserver}
+ * @abstract
+ */
+class MsgIncomingServer {
+ QueryInterface = ChromeUtils.generateQI([
+ "nsIMsgIncomingServer",
+ "nsISupportsWeakReference",
+ "nsIObserver",
+ ]);
+
+ constructor() {
+ // nsIMsgIncomingServer attributes that map directly to pref values.
+ this._mapAttrsToPrefs([
+ ["Char", "type"],
+ ["Char", "clientid"],
+ ["Int", "authMethod"],
+ ["Int", "biffMinutes", "check_time"],
+ ["Int", "maxMessageSize", "max_size"],
+ ["Int", "incomingDuplicateAction", "dup_action"],
+ ["Bool", "clientidEnabled"],
+ ["Bool", "downloadOnBiff", "download_on_biff"],
+ ["Bool", "valid"],
+ ["Bool", "emptyTrashOnExit", "empty_trash_on_exit"],
+ ["Bool", "canDelete"],
+ ["Bool", "loginAtStartUp", "login_at_startup"],
+ [
+ "Bool",
+ "defaultCopiesAndFoldersPrefsToServer",
+ "allows_specialfolders_usage",
+ ],
+ ["Bool", "canCreateFoldersOnServer", "canCreateFolders"],
+ ["Bool", "canFileMessagesOnServer", "canFileMessages"],
+ ["Bool", "limitOfflineMessageSize", "limit_offline_message_size"],
+ ["Bool", "hidden"],
+ ]);
+
+ // nsIMsgIncomingServer attributes.
+ this.performingBiff = false;
+ this.accountManagerChrome = "am-main.xhtml";
+ this.biffState = Ci.nsIMsgFolder.nsMsgBiffState_Unknown;
+ this.downloadMessagesAtStartup = false;
+ this.canHaveFilters = true;
+ this.canBeDefaultServer = false;
+ this.displayStartupPage = true;
+ this.supportsDiskSpace = true;
+ this.canCompactFoldersOnServer = true;
+ this.canUndoDeleteOnServer = true;
+ this.sortOrder = 100000000;
+
+ // @type {Map<string, number>} - The key is MsgId+Subject, the value is
+ // this._hdrIndex.
+ this._knownHdrMap = new Map();
+ this._hdrIndex = 0;
+
+ Services.obs.addObserver(this, "passwordmgr-storage-changed");
+ }
+
+ /**
+ * Observe() receives notifications for all accounts, not just this server's
+ * account. So we ignore all notifications not intended for this server.
+ * When the state of the password manager changes we need to clear the
+ * this server's password from the cache in case the user just changed or
+ * removed the password or username.
+ * OAuth2 servers often automatically change the password manager's stored
+ * password (the token).
+ */
+ observe(subject, topic, data) {
+ if (topic == "passwordmgr-storage-changed") {
+ // Check that the notification is for this server and user.
+ let otherFullName = "";
+ let otherUsername = "";
+ if (subject instanceof Ci.nsILoginInfo) {
+ // The login info for a server has been removed with data being
+ // "removeLogin" or "removeAllLogins".
+ otherFullName = subject.origin;
+ otherUsername = subject.username;
+ } else if (subject instanceof Ci.nsIArray) {
+ // Probably a 2 element array containing old and new login info due to
+ // data being "modifyLogin". E.g., a user has modified the password or
+ // username in the password manager or an OAuth2 token string has
+ // automatically changed. Only need to look at names in first array
+ // element (login info before any modification) since the user might
+ // have changed the username as found in the 2nd elements. (The
+ // hostname can't be modified in the password manager.
+ otherFullName = subject.queryElementAt(0, Ci.nsISupports).origin;
+ otherUsername = subject.queryElementAt(0, Ci.nsISupports).username;
+ }
+ if (otherFullName) {
+ if (
+ otherFullName != "mailbox://" + this.hostName ||
+ otherUsername != this.username
+ ) {
+ // Not for this server; keep this server's cached password.
+ return;
+ }
+ } else if (data != "hostSavingDisabled") {
+ // "hostSavingDisabled" only occurs during test_smtpServer.js and
+ // expects the password to be removed from memory cache. Otherwise, we
+ // don't have enough information to decide to remove the cached
+ // password, so keep it.
+ return;
+ }
+ // Remove the password for this server cached in memory.
+ this.password = "";
+ }
+ }
+
+ /**
+ * Set up getters/setters for attributes that map directly to pref values.
+ *
+ * @param {string[]} attributes - An array of attributes. Each attribute is
+ * defined by its type, name and corresponding prefName.
+ */
+ _mapAttrsToPrefs(attributes) {
+ for (let [type, attrName, prefName] of attributes) {
+ prefName = prefName || attrName;
+ Object.defineProperty(this, attrName, {
+ configurable: true,
+ get: () => this[`get${type}Value`](prefName),
+ set: value => {
+ this[`set${type}Value`](prefName, value);
+ },
+ });
+ }
+ }
+
+ get key() {
+ return this._key;
+ }
+
+ set key(key) {
+ this._key = key;
+ this._prefs = Services.prefs.getBranch(`mail.server.${key}.`);
+ this._defaultPrefs = Services.prefs.getBranch("mail.server.default.");
+ }
+
+ get UID() {
+ let uid = this._prefs.getStringPref("uid", "");
+ if (uid) {
+ return uid;
+ }
+ return (this.UID = Services.uuid
+ .generateUUID()
+ .toString()
+ .substring(1, 37));
+ }
+
+ set UID(uid) {
+ if (this._prefs.prefHasUserValue("uid")) {
+ throw new Components.Exception("uid is already set", Cr.NS_ERROR_ABORT);
+ }
+ this._prefs.setStringPref("uid", uid);
+ }
+
+ get hostName() {
+ let hostname = this.getUnicharValue("hostname");
+ if (hostname.includes(":")) {
+ // Reformat the hostname if it contains a port number.
+ this.hostName = hostname;
+ return this.hostName;
+ }
+ return hostname;
+ }
+
+ set hostName(value) {
+ let oldName = this.hostName;
+ this._setHostName("hostname", value);
+
+ if (oldName && oldName != value) {
+ this.onUserOrHostNameChanged(oldName, value, true);
+ }
+ }
+
+ _setHostName(prefName, value) {
+ let [host, port] = value.split(":");
+ if (port) {
+ this.port = Number(port);
+ }
+ this.setUnicharValue(prefName, host);
+ }
+
+ get username() {
+ return this.getUnicharValue("userName");
+ }
+
+ set username(value) {
+ let oldName = this.username;
+ if (oldName && oldName != value) {
+ this.setUnicharValue("userName", value);
+ this.onUserOrHostNameChanged(oldName, value, false);
+ } else {
+ this.setUnicharValue("userName", value);
+ }
+ }
+
+ get port() {
+ let port = this.getIntValue("port");
+ if (port > 1) {
+ return port;
+ }
+
+ // If the port isn't set, use the default port based on the protocol.
+ return this.protocolInfo.getDefaultServerPort(
+ this.socketType == Ci.nsMsgSocketType.SSL
+ );
+ }
+
+ set port(value) {
+ this.setIntValue("port", value);
+ }
+
+ get protocolInfo() {
+ return Cc[
+ `@mozilla.org/messenger/protocol/info;1?type=${this.type}`
+ ].getService(Ci.nsIMsgProtocolInfo);
+ }
+
+ get socketType() {
+ try {
+ return this._prefs.getIntPref("socketType");
+ } catch (e) {
+ // socketType is set to default value. Look at isSecure setting.
+ if (this._prefs.getBoolPref("isSecure", false)) {
+ return Ci.nsMsgSocketType.SSL;
+ }
+ return this._defaultPrefs.getIntPref(
+ "socketType",
+ Ci.nsMsgSocketType.plain
+ );
+ }
+ }
+
+ set socketType(value) {
+ let wasSecure = this.isSecure;
+ this._prefs.setIntPref("socketType", value);
+ let isSecure = this.isSecure;
+ if (wasSecure != isSecure) {
+ this.rootFolder.NotifyBoolPropertyChanged(
+ "isSecure",
+ wasSecure,
+ isSecure
+ );
+ }
+ }
+
+ get isSecure() {
+ return [Ci.nsMsgSocketType.alwaysSTARTTLS, Ci.nsMsgSocketType.SSL].includes(
+ this.socketType
+ );
+ }
+
+ get serverURI() {
+ return this._getServerURI(true);
+ }
+
+ /**
+ * Get server URI in the form of localStoreType://[user@]hostname.
+ *
+ * @param {boolean} includeUsername - Whether to include the username.
+ * @returns {string}
+ */
+ _getServerURI(includeUsername) {
+ let auth =
+ includeUsername && this.username
+ ? `${encodeURIComponent(this.username)}@`
+ : "";
+ // When constructing nsIURI, need to wrap IPv6 address in [].
+ let hostname = this.hostName.includes(":")
+ ? `[${this.hostName}]`
+ : this.hostName;
+ return `${this.localStoreType}://${auth}${encodeURIComponent(hostname)}`;
+ }
+
+ get prettyName() {
+ return this.getUnicharValue("name") || this.constructedPrettyName;
+ }
+
+ set prettyName(value) {
+ this.setUnicharValue("name", value);
+ this.rootFolder.prettyName = value;
+ }
+
+ /**
+ * Construct a pretty name from username and hostname.
+ *
+ * @param {string} username - The user name.
+ * @param {string} hostname - The host name.
+ * @returns {string}
+ */
+ _constructPrettyName(username, hostname) {
+ let prefix = username ? `${username} on ` : "";
+ return `${prefix}${hostname}`;
+ }
+
+ get constructedPrettyName() {
+ return this._constructPrettyName(this.username, this.hostName);
+ }
+
+ get localPath() {
+ let localPath = this.getFileValue("directory-rel", "directory");
+ if (localPath) {
+ // If the local path has already been set, use it.
+ return localPath;
+ }
+
+ // Create the path using protocol info and hostname.
+ localPath = this.protocolInfo.defaultLocalPath;
+ if (!localPath.exists()) {
+ localPath.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ }
+
+ localPath.append(this.hostName);
+ localPath.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+
+ this.localPath = localPath;
+ return localPath;
+ }
+
+ set localPath(localPath) {
+ this.setFileValue("directory-rel", "directory", localPath);
+ }
+
+ get rootFolder() {
+ if (!this._rootFolder) {
+ this._rootFolder = MailServices.folderLookup.getOrCreateFolderForURL(
+ this.serverURI
+ );
+ }
+ return this._rootFolder;
+ }
+
+ get rootMsgFolder() {
+ return this.rootFolder;
+ }
+
+ get msgStore() {
+ if (!this._msgStore) {
+ let contractId = this.getCharValue("storeContractID");
+ if (!contractId) {
+ contractId = "@mozilla.org/msgstore/berkeleystore;1";
+ this.setCharValue("storeContractID", contractId);
+ }
+
+ // After someone starts using the pluggable store, we can no longer
+ // change the value.
+ this.setBoolValue("canChangeStoreType", false);
+
+ this._msgStore = Cc[contractId].createInstance(Ci.nsIMsgPluggableStore);
+ }
+ return this._msgStore;
+ }
+
+ get doBiff() {
+ try {
+ return this._prefs.getBoolPref("check_new_mail");
+ } catch (e) {
+ return this.protocolInfo.defaultDoBiff;
+ }
+ }
+
+ set doBiff(value) {
+ let biffManager = Cc["@mozilla.org/messenger/biffManager;1"].getService(
+ Ci.nsIMsgBiffManager
+ );
+ if (value) {
+ biffManager.addServerBiff(this);
+ } else {
+ biffManager.removeServerBiff(this);
+ }
+ this._prefs.setBoolPref("check_new_mail", value);
+ }
+
+ /**
+ * type, attribute name, pref name
+ */
+ _retentionSettingsPrefs = [
+ ["Int", "retainByPreference", "retainBy"],
+ ["Int", "numHeadersToKeep", "numHdrsToKeep"],
+ ["Int", "daysToKeepHdrs"],
+ ["Int", "daysToKeepBodies"],
+ ["Bool", "cleanupBodiesByDays", "cleanupBodies"],
+ ["Bool", "applyToFlaggedMessages"],
+ ];
+
+ get retentionSettings() {
+ let settings = Cc[
+ "@mozilla.org/msgDatabase/retentionSettings;1"
+ ].createInstance(Ci.nsIMsgRetentionSettings);
+ for (let [type, attrName, prefName] of this._retentionSettingsPrefs) {
+ prefName = prefName || attrName;
+ settings[attrName] = this[`get${type}Value`](prefName);
+ }
+ return settings;
+ }
+
+ set retentionSettings(settings) {
+ for (let [type, attrName, prefName] of this._retentionSettingsPrefs) {
+ prefName = prefName || attrName;
+ this[`set${type}Value`](prefName, settings[attrName]);
+ }
+ }
+
+ get spamSettings() {
+ if (!this.getCharValue("spamActionTargetAccount")) {
+ this.setCharValue("spamActionTargetAccount", this.serverURI);
+ }
+ if (!this._spamSettings) {
+ this._spamSettings = Cc[
+ "@mozilla.org/messenger/spamsettings;1"
+ ].createInstance(Ci.nsISpamSettings);
+ try {
+ this._spamSettings.initialize(this);
+ } catch (e) {
+ console.error(e);
+ }
+ }
+ return this._spamSettings;
+ }
+
+ get spamFilterPlugin() {
+ if (!this._spamFilterPlugin) {
+ this._spamFilterPlugin = Cc[
+ "@mozilla.org/messenger/filter-plugin;1?name=bayesianfilter"
+ ].getService(Ci.nsIMsgFilterPlugin);
+ }
+ return this._spamFilterPlugin;
+ }
+
+ get isDeferredTo() {
+ let account = MailServices.accounts.FindAccountForServer(this);
+ if (!account) {
+ return false;
+ }
+ return MailServices.accounts.allServers.some(
+ server => server.getCharValue("deferred_to_account") == account.key
+ );
+ }
+
+ get serverRequiresPasswordForBiff() {
+ return true;
+ }
+
+ /**
+ * type, attribute name, pref name
+ */
+ _downloadSettingsPrefs = [
+ ["Int", "ageLimitOfMsgsToDownload", "ageLimit"],
+ ["Bool", "downloadUnreadOnly"],
+ ["Bool", "downloadByDate"],
+ ];
+
+ get downloadSettings() {
+ if (!this._downloadSettings) {
+ this._downloadSettings = Cc[
+ "@mozilla.org/msgDatabase/downloadSettings;1"
+ ].createInstance(Ci.nsIMsgDownloadSettings);
+ for (let [type, attrName, prefName] of this._downloadSettingsPrefs) {
+ prefName = prefName || attrName;
+ this._downloadSettings[attrName] = this[`get${type}Value`](prefName);
+ }
+ }
+ return this._downloadSettings;
+ }
+
+ set downloadSettings(settings) {
+ this._downloadSettings = settings;
+ for (let [type, attrName, prefName] of this._downloadSettingsPrefs) {
+ prefName = prefName || attrName;
+ this[`set${type}Value`](prefName, settings[attrName]);
+ }
+ }
+
+ get offlineSupportLevel() {
+ const OFFLINE_SUPPORT_LEVEL_NONE = 0;
+ const OFFLINE_SUPPORT_LEVEL_UNDEFINED = -1;
+ let level = this.getIntValue("offline_support_level");
+ return level == OFFLINE_SUPPORT_LEVEL_UNDEFINED
+ ? OFFLINE_SUPPORT_LEVEL_NONE
+ : level;
+ }
+
+ set offlineSupportLevel(value) {
+ this.setIntValue("offline_support_level", value);
+ }
+
+ get filterScope() {
+ return Ci.nsMsgSearchScope.offlineMailFilter;
+ }
+
+ get searchScope() {
+ return Ci.nsMsgSearchScope.offlineMail;
+ }
+
+ get passwordPromptRequired() {
+ if (!this.serverRequiresPasswordForBiff) {
+ // If the password is not even required for biff we don't need to check
+ // any further.
+ return false;
+ }
+ if (!this.password) {
+ // If the password is empty, check to see if it is stored.
+ this.password = this._getPasswordWithoutUI();
+ }
+ if (this.password) {
+ return false;
+ }
+ return this.authMethod != Ci.nsMsgAuthMethod.OAuth2;
+ }
+
+ getCharValue(prefName) {
+ try {
+ return this._prefs.getCharPref(prefName);
+ } catch (e) {
+ return this._defaultPrefs.getCharPref(prefName, "");
+ }
+ }
+
+ setCharValue(prefName, value) {
+ let defaultValue = this._defaultPrefs.getCharPref(prefName, "");
+ if (!value || value == defaultValue) {
+ this._prefs.clearUserPref(prefName);
+ } else {
+ this._prefs.setCharPref(prefName, value);
+ }
+ }
+
+ getUnicharValue(prefName) {
+ try {
+ return this._prefs.getStringPref(prefName);
+ } catch (e) {
+ return this._defaultPrefs.getStringPref(prefName, "");
+ }
+ }
+
+ setUnicharValue(prefName, value) {
+ let defaultValue = this._defaultPrefs.getStringPref(prefName, "");
+ if (!value || value == defaultValue) {
+ this._prefs.clearUserPref(prefName);
+ } else {
+ this._prefs.setStringPref(prefName, value);
+ }
+ }
+
+ getIntValue(prefName) {
+ try {
+ return this._prefs.getIntPref(prefName);
+ } catch (e) {
+ return this._defaultPrefs.getIntPref(prefName, 0);
+ }
+ }
+
+ setIntValue(prefName, value) {
+ let defaultValue = this._defaultPrefs.getIntPref(prefName, value - 1);
+ if (defaultValue == value) {
+ this._prefs.clearUserPref(prefName);
+ } else {
+ this._prefs.setIntPref(prefName, value);
+ }
+ }
+
+ getBoolValue(prefName) {
+ try {
+ return this._prefs.getBoolPref(prefName);
+ } catch (e) {
+ return this._defaultPrefs.getBoolPref(prefName, false);
+ }
+ }
+
+ setBoolValue(prefName, value) {
+ let defaultValue = this._defaultPrefs.getBoolPref(prefName, !value);
+ if (defaultValue == value) {
+ this._prefs.clearUserPref(prefName);
+ } else {
+ this._prefs.setBoolPref(prefName, value);
+ }
+ }
+
+ getFileValue(relPrefName, absPrefName) {
+ try {
+ let file = this._prefs.getComplexValue(
+ relPrefName,
+ Ci.nsIRelativeFilePref
+ ).file;
+ file.normalize();
+ return file;
+ } catch (e) {
+ try {
+ let file = this._prefs.getComplexValue(absPrefName, Ci.nsIFile);
+ this._prefs.setComplexValue(relPrefName, Ci.nsIRelativeFilePref, {
+ QueryInterface: ChromeUtils.generateQI(["nsIRelativeFilePref"]),
+ file,
+ relativeToKey: "ProfD",
+ });
+ return file;
+ } catch (e) {
+ return null;
+ }
+ }
+ }
+
+ setFileValue(relPrefName, absPrefName, file) {
+ this._prefs.setComplexValue(relPrefName, Ci.nsIRelativeFilePref, {
+ QueryInterface: ChromeUtils.generateQI(["nsIRelativeFilePref"]),
+ file,
+ relativeToKey: "ProfD",
+ });
+ this._prefs.setComplexValue(absPrefName, Ci.nsIFile, file);
+ }
+
+ onUserOrHostNameChanged(oldValue, newValue, hostnameChanged) {
+ migrateServerUris(
+ this.localStoreType,
+ hostnameChanged ? oldValue : this.hostName,
+ hostnameChanged ? this.username : oldValue,
+ this.hostName,
+ this.username
+ );
+ this._spamSettings = null;
+
+ // Clear the clientid because the user or host have changed.
+ this.clientid = "";
+
+ let atIndex = newValue.indexOf("@");
+ if (!this.prettyName || (!hostnameChanged && atIndex != -1)) {
+ // If new username contains @ then better not update the pretty name.
+ return;
+ }
+
+ atIndex = this.prettyName.indexOf("@");
+ if (
+ !hostnameChanged &&
+ atIndex != -1 &&
+ oldValue == this.prettyName.slice(0, atIndex)
+ ) {
+ // If username changed and the pretty name has the old username before @,
+ // update to the new username.
+ this.prettyName = newValue + this.prettyName.slice(atIndex);
+ } else if (
+ hostnameChanged &&
+ oldValue == this.prettyName.slice(atIndex + 1)
+ ) {
+ // If hostname changed and the pretty name has the old hostname after @,
+ // update to the new hostname.
+ this.prettyName = this.prettyName.slice(0, atIndex + 1) + newValue;
+ } else {
+ // Set the `name` pref anyway, to make tests happy.
+ // eslint-disable-next-line no-self-assign
+ this.prettyName = this.prettyName;
+ }
+ }
+
+ /**
+ * Try to get the password from nsILoginManager.
+ *
+ * @returns {string}
+ */
+ _getPasswordWithoutUI() {
+ let serverURI = this._getServerURI();
+ let logins = Services.logins.findLogins(serverURI, "", serverURI);
+ for (let login of logins) {
+ if (login.username == this.username) {
+ return login.password;
+ }
+ }
+ return null;
+ }
+
+ getPasswordWithUI(promptMessage, promptTitle) {
+ let password = this._getPasswordWithoutUI();
+ if (password) {
+ this.password = password;
+ return this.password;
+ }
+ let outUsername = {};
+ let outPassword = {};
+ let ok;
+ let authPrompt;
+ try {
+ // This prompt has a checkbox for saving password.
+ authPrompt = Cc["@mozilla.org/messenger/msgAuthPrompt;1"].getService(
+ Ci.nsIAuthPrompt
+ );
+ } catch (e) {
+ // Often happens in tests. This prompt has no checkbox for saving password.
+ authPrompt = Services.ww.getNewAuthPrompter(null);
+ }
+ if (this.username) {
+ ok = authPrompt.promptPassword(
+ promptTitle,
+ promptMessage,
+ this.serverURI,
+ Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY,
+ outPassword
+ );
+ } else {
+ ok = authPrompt.promptUsernameAndPassword(
+ promptTitle,
+ promptMessage,
+ this.serverURI,
+ Ci.nsIAuthPrompt.SAVE_PASSWORD_PERMANENTLY,
+ outUsername,
+ outPassword
+ );
+ }
+ if (ok) {
+ if (outUsername.value) {
+ this.username = outUsername.value;
+ }
+ this.password = outPassword.value;
+ } else {
+ throw Components.Exception("Password dialog canceled", Cr.NS_ERROR_ABORT);
+ }
+ return this.password;
+ }
+
+ forgetPassword() {
+ let serverURI = this._getServerURI();
+ let logins = Services.logins.findLogins(serverURI, "", serverURI);
+ for (let login of logins) {
+ if (login.username == this.username) {
+ Services.logins.removeLogin(login);
+ }
+ }
+ this.password = "";
+ }
+
+ forgetSessionPassword() {
+ this.password = "";
+ }
+
+ closeCachedConnections() {}
+
+ shutdown() {
+ this.closeCachedConnections();
+
+ if (this._filterList) {
+ this._filterList.logStream = null;
+ this._filterList = null;
+ }
+ if (this._spamSettings) {
+ this._spamSettings.logStream = null;
+ this._spamSettings = null;
+ }
+ }
+
+ getFilterList(msgWindow) {
+ if (!this._filterList) {
+ if (!this.rootFolder.filePath.path) {
+ // Happens in tests.
+ return null;
+ }
+ let filterFile = this.rootFolder.filePath.clone();
+ filterFile.append("msgFilterRules.dat");
+ try {
+ this._filterList = MailServices.filters.OpenFilterList(
+ filterFile,
+ this.rootFolder,
+ msgWindow
+ );
+ } catch (e) {
+ console.error(e);
+ const NS_ERROR_FILE_FS_CORRUPTED = 0x80520016;
+ if (e.result == NS_ERROR_FILE_FS_CORRUPTED && filterFile.exists()) {
+ // OpenFilterList will create a new one next time.
+ filterFile.renameTo(filterFile.parent, "msgFilterRules.dat.orig");
+ }
+ }
+ }
+ return this._filterList;
+ }
+
+ setFilterList(value) {
+ this._filterList = value;
+ }
+
+ getEditableFilterList(msgWindow) {
+ if (!this._editableFilterList) {
+ return this.getFilterList(msgWindow);
+ }
+ return this._editableFilterList;
+ }
+
+ setEditableFilterList(value) {
+ this._editableFilterList = value;
+ }
+
+ setDefaultLocalPath(value) {
+ this.protocolInfo.setDefaultLocalPath(value);
+ }
+
+ getNewMessages(folder, msgWindow, urlListener) {
+ folder.getNewMessages(msgWindow, urlListener);
+ }
+
+ writeToFolderCache(folderCache) {
+ this.rootFolder.writeToFolderCache(folderCache, true);
+ }
+
+ clearAllValues() {
+ for (let prefName of this._prefs.getChildList("")) {
+ this._prefs.clearUserPref(prefName);
+ }
+ }
+
+ removeFiles() {
+ if (this.getCharValue("deferred_to_account") || this.isDeferredTo) {
+ throw Components.Exception(
+ "Should not remove files for a deferred account",
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+ this.localPath.remove(true);
+ }
+
+ getMsgFolderFromURI(folder, uri) {
+ try {
+ return this.rootMsgFolder.getChildWithURI(uri, true, true) || folder;
+ } catch (e) {
+ return folder;
+ }
+ }
+
+ isNewHdrDuplicate(newHdr) {
+ // If the message has been partially downloaded, the message should not
+ // be considered a duplicated message. See bug 714090.
+ if (newHdr.flags & Ci.nsMsgMessageFlags.Partial) {
+ return false;
+ }
+
+ if (!newHdr.subject || !newHdr.messageId) {
+ return false;
+ }
+
+ let key = `${newHdr.messageId}${newHdr.subject}`;
+ if (this._knownHdrMap.get(key)) {
+ return true;
+ }
+
+ this._knownHdrMap.set(key, ++this._hdrIndex);
+
+ const MAX_SIZE = 500;
+ if (this._knownHdrMap.size > MAX_SIZE) {
+ // Release the oldest half of downloaded hdrs.
+ for (let [k, v] of this._knownHdrMap) {
+ if (v < this._hdrIndex - MAX_SIZE / 2) {
+ this._knownHdrMap.delete(k);
+ } else if (this._knownHdrMap.size <= MAX_SIZE / 2) {
+ break;
+ }
+ }
+ }
+ return false;
+ }
+
+ equals(server) {
+ return this.key == server.key;
+ }
+
+ _configureTemporaryReturnReceiptsFilter(filterList) {
+ let identity = MailServices.accounts.getFirstIdentityForServer(this);
+ if (!identity) {
+ return;
+ }
+ let incorp = Ci.nsIMsgMdnGenerator.eIncorporateInbox;
+ if (identity.getBoolAttribute("use_custom_prefs")) {
+ incorp = this.getIntValue("incorporate_return_receipt");
+ } else {
+ incorp = Services.prefs.getIntPref("mail.incorporate.return_receipt");
+ }
+
+ let enable = incorp == Ci.nsIMsgMdnGenerator.eIncorporateSent;
+
+ const FILTER_NAME = "mozilla-temporary-internal-MDN-receipt-filter";
+ let filter = filterList.getFilterNamed(FILTER_NAME);
+
+ if (filter) {
+ filter.enabled = enable;
+ return;
+ } else if (!enable || !identity.fccFolder) {
+ return;
+ }
+
+ filter = filterList.createFilter(FILTER_NAME);
+ if (!filter) {
+ return;
+ }
+
+ filter.enabled = true;
+ filter.temporary = true;
+
+ let term = filter.createTerm();
+ let value = term.value;
+ value.attrib = Ci.nsMsgSearchAttrib.OtherHeader + 1;
+ value.str = "multipart/report";
+ term.attrib = Ci.nsMsgSearchAttrib.OtherHeader + 1;
+ term.op = Ci.nsMsgSearchOp.Contains;
+ term.booleanAnd = true;
+ term.arbitraryHeader = "Content-Type";
+ term.value = value;
+ filter.appendTerm(term);
+
+ term = filter.createTerm();
+ value = term.value;
+ value.attrib = Ci.nsMsgSearchAttrib.OtherHeader + 1;
+ value.str = "disposition-notification";
+ term.attrib = Ci.nsMsgSearchAttrib.OtherHeader + 1;
+ term.op = Ci.nsMsgSearchOp.Contains;
+ term.booleanAnd = true;
+ term.arbitraryHeader = "Content-Type";
+ term.value = value;
+ filter.appendTerm(term);
+
+ let action = filter.createAction();
+ action.type = Ci.nsMsgFilterAction.MoveToFolder;
+ action.targetFolderUri = identity.fccFolder;
+ filter.appendAction(action);
+ filterList.insertFilterAt(0, filter);
+ }
+
+ _configureTemporaryServerSpamFilters(filterList) {
+ let spamSettings = this.spamSettings;
+ if (!spamSettings.useServerFilter) {
+ return;
+ }
+ let serverFilterName = spamSettings.serverFilterName;
+ let serverFilterTrustFlags = spamSettings.serverFilterTrustFlags;
+ if (!serverFilterName || !serverFilterName) {
+ return;
+ }
+
+ // Check if filters have been setup already.
+ let yesFilterName = `${serverFilterName}Yes`;
+ let noFilterName = `${serverFilterName}No`;
+ let filter = filterList.getFilterNamed(yesFilterName);
+ if (!filter) {
+ filter = filterList.getFilterNamed(noFilterName);
+ }
+ if (filter) {
+ return;
+ }
+
+ let serverFilterList = MailServices.filters.OpenFilterList(
+ spamSettings.serverFilterFile,
+ null,
+ null
+ );
+ filter = serverFilterList.getFilterNamed(yesFilterName);
+ if (filter && serverFilterTrustFlags & Ci.nsISpamSettings.TRUST_POSITIVES) {
+ filter.temporary = true;
+ // Check if we're supposed to move junk mail to junk folder; if so, add
+ // filter action to do so.
+ let searchTerms = filter.searchTerms;
+ if (searchTerms.length) {
+ searchTerms[0].beginsGrouping = true;
+ searchTerms.at(-1).endsGrouping = true;
+ }
+
+ // Create a new term, checking if the user set junk status. The term will
+ // search for junkscoreorigin != "user".
+ let term = filter.createTerm();
+ term.attrib = Ci.nsMsgSearchAttrib.JunkScoreOrigin;
+ term.op = Ci.nsMsgSearchOp.Isnt;
+ term.booleanAnd = true;
+ let value = term.value;
+ value.attrib = Ci.nsMsgSearchAttrib.JunkScoreOrigin;
+ value.str = "user";
+ term.value = value;
+ filter.appendTerm(term);
+
+ if (spamSettings.moveOnSpam) {
+ let spamFolderURI = spamSettings.spamFolderURI;
+ if (spamFolderURI) {
+ let action = filter.createAction();
+ action.type = Ci.nsMsgFilterAction.MoveToFolder;
+ action.targetFolderUri = spamFolderURI;
+ filter.appendAction(action);
+ }
+ }
+
+ if (spamSettings.markAsReadOnSpam) {
+ let action = filter.createAction();
+ action.type = Ci.nsMsgFilterAction.MarkRead;
+ filter.appendAction(action);
+ }
+ filterList.insertFilterAt(0, filter);
+ }
+
+ filter = serverFilterList.getFilterNamed(noFilterName);
+ if (filter && serverFilterTrustFlags & Ci.nsISpamSettings.TRUST_NEGATIVES) {
+ filter.temporary = true;
+ filterList.insertFilterAt(0, filter);
+ }
+ }
+
+ configureTemporaryFilters(filterList) {
+ this._configureTemporaryReturnReceiptsFilter(filterList);
+ this._configureTemporaryServerSpamFilters(filterList);
+ }
+
+ clearTemporaryReturnReceiptsFilter() {
+ if (!this._filterList) {
+ return;
+ }
+ let filter = this._filterList.getFilterNamed(
+ "mozilla-temporary-internal-MDN-receipt-filter"
+ );
+ if (filter) {
+ this._filterList.removeFilter(filter);
+ }
+ }
+
+ getForcePropertyEmpty(name) {
+ return this.getCharValue(`${name}.empty`) == "true";
+ }
+
+ setForcePropertyEmpty(name, value) {
+ return this.setCharValue(`${name}.empty`, value ? "true" : "");
+ }
+
+ performExpand(msgWindow) {}
+
+ get wrappedJSObject() {
+ return this;
+ }
+
+ _passwordPromise = null;
+
+ /**
+ * Show a password prompt. If a prompt is currently shown, just wait for it.
+ *
+ * @param {string} message - The text inside the prompt.
+ * @param {string} title - The title of the prompt.
+ */
+ async getPasswordWithUIAsync(message, title) {
+ if (this._passwordPromise) {
+ await this._passwordPromise;
+ return this.password;
+ }
+ let deferred = {};
+ this._passwordPromise = new Promise((resolve, reject) => {
+ deferred.resolve = resolve;
+ deferred.reject = reject;
+ });
+ try {
+ this.getPasswordWithUI(message, title);
+ } catch (e) {
+ deferred.reject(e);
+ throw e;
+ } finally {
+ this._passwordPromise = null;
+ }
+ deferred.resolve();
+ return this.password;
+ }
+}