summaryrefslogtreecommitdiffstats
path: root/comm/mail/services/sync/modules/engines/accounts.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/services/sync/modules/engines/accounts.sys.mjs')
-rw-r--r--comm/mail/services/sync/modules/engines/accounts.sys.mjs392
1 files changed, 392 insertions, 0 deletions
diff --git a/comm/mail/services/sync/modules/engines/accounts.sys.mjs b/comm/mail/services/sync/modules/engines/accounts.sys.mjs
new file mode 100644
index 0000000000..d504da0fee
--- /dev/null
+++ b/comm/mail/services/sync/modules/engines/accounts.sys.mjs
@@ -0,0 +1,392 @@
+/* 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/. */
+
+import { CryptoWrapper } from "resource://services-sync/record.sys.mjs";
+import {
+ Store,
+ SyncEngine,
+ Tracker,
+} from "resource://services-sync/engines.sys.mjs";
+import { Utils } from "resource://services-sync/util.sys.mjs";
+
+const { SCORE_INCREMENT_XLARGE } = ChromeUtils.import(
+ "resource://services-sync/constants.js"
+);
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+const SYNCED_SMTP_PROPERTIES = {
+ authMethod: "authMethod",
+ port: "port",
+ description: "name",
+ socketType: "socketType",
+};
+
+const SYNCED_SERVER_PROPERTIES = {
+ authMethod: "authMethod",
+ biffMinutes: "check_time",
+ doBiff: "check_new_mail",
+ downloadOnBiff: "download_on_biff",
+ emptyTrashOnExit: "empty_trash_on_exit",
+ incomingDuplicateAction: "dup_action",
+ limitOfflineMessageSize: "limit_offline_message_size",
+ loginAtStartUp: "login_at_startup",
+ maxMessageSize: "max_size",
+ port: "port",
+ prettyName: "name",
+ socketType: "socketType",
+};
+
+/**
+ * AccountRecord represents the state of an add-on in an application.
+ *
+ * Each add-on has its own record for each application ID it is installed
+ * on.
+ *
+ * The ID of add-on records is a randomly-generated GUID. It is random instead
+ * of deterministic so the URIs of the records cannot be guessed and so
+ * compromised server credentials won't result in disclosure of the specific
+ * add-ons present in a Sync account.
+ *
+ * The record contains the following fields:
+ *
+ */
+export function AccountRecord(collection, id) {
+ CryptoWrapper.call(this, collection, id);
+}
+
+AccountRecord.prototype = {
+ __proto__: CryptoWrapper.prototype,
+ _logName: "Record.Account",
+};
+Utils.deferGetSet(AccountRecord, "cleartext", [
+ "username",
+ "hostname",
+ "type",
+ "prefs",
+ "isDefault",
+]);
+
+export function AccountsEngine(service) {
+ SyncEngine.call(this, "Accounts", service);
+}
+
+AccountsEngine.prototype = {
+ __proto__: SyncEngine.prototype,
+ _storeObj: AccountStore,
+ _trackerObj: AccountTracker,
+ _recordObj: AccountRecord,
+ version: 1,
+ syncPriority: 3,
+
+ /*
+ * Returns a changeset for this sync. Engine implementations can override this
+ * method to bypass the tracker for certain or all changed items.
+ */
+ async getChangedIDs() {
+ return this._tracker.getChangedIDs();
+ },
+};
+
+function AccountStore(name, engine) {
+ Store.call(this, name, engine);
+}
+AccountStore.prototype = {
+ __proto__: Store.prototype,
+
+ /**
+ * Create an item in the store from a record.
+ *
+ * This is called by the default implementation of applyIncoming(). If using
+ * applyIncomingBatch(), this won't be called unless your store calls it.
+ *
+ * @param record
+ * The store record to create an item from
+ */
+ async create(record) {
+ if (record.type == "smtp") {
+ let smtpServer = MailServices.smtp.createServer();
+ smtpServer.UID = record.id;
+ smtpServer.username = record.username;
+ smtpServer.hostname = record.hostname;
+ for (let key of Object.keys(SYNCED_SMTP_PROPERTIES)) {
+ if (key in record.prefs) {
+ smtpServer[key] = record.prefs[key];
+ }
+ }
+ if (record.isDefault) {
+ MailServices.smtp.defaultServer = smtpServer;
+ }
+ return;
+ }
+
+ try {
+ // Ensure there is a local mail account...
+ MailServices.accounts.localFoldersServer;
+ } catch {
+ // ... if not, make one.
+ MailServices.accounts.createLocalMailAccount();
+ }
+
+ let server = MailServices.accounts.createIncomingServer(
+ record.username,
+ record.hostname,
+ record.type
+ );
+ server.UID = record.id;
+
+ for (let key of Object.keys(SYNCED_SERVER_PROPERTIES)) {
+ if (key in record.prefs) {
+ server[key] = record.prefs[key];
+ }
+ }
+
+ let account = MailServices.accounts.createAccount();
+ account.incomingServer = server;
+
+ if (server.loginAtStartUp) {
+ Services.wm
+ .getMostRecentWindow("mail:3pane")
+ ?.GetNewMsgs(server, server.rootFolder);
+ }
+ },
+
+ /**
+ * Remove an item in the store from a record.
+ *
+ * This is called by the default implementation of applyIncoming(). If using
+ * applyIncomingBatch(), this won't be called unless your store calls it.
+ *
+ * @param record
+ * The store record to delete an item from
+ */
+ async remove(record) {
+ let smtpServer = MailServices.smtp.servers.find(s => s.UID == record.id);
+ if (smtpServer) {
+ MailServices.smtp.deleteServer(smtpServer);
+ return;
+ }
+
+ let server = MailServices.accounts.allServers.find(s => s.UID == record.id);
+ if (!server) {
+ this._log.trace("Asked to remove record that doesn't exist, ignoring");
+ return;
+ }
+
+ let account = MailServices.accounts.FindAccountForServer(server);
+ if (account) {
+ MailServices.accounts.removeAccount(account, true);
+ } else {
+ // Is this even possible?
+ MailServices.accounts.removeIncomingServer(account, true);
+ }
+ },
+
+ /**
+ * Update an item from a record.
+ *
+ * This is called by the default implementation of applyIncoming(). If using
+ * applyIncomingBatch(), this won't be called unless your store calls it.
+ *
+ * @param record
+ * The record to use to update an item from
+ */
+ async update(record) {
+ if (record.type == "smtp") {
+ await this._updateSMTP(record);
+ return;
+ }
+
+ await this._updateIncoming(record);
+ },
+
+ async _updateSMTP(record) {
+ let smtpServer = MailServices.smtp.servers.find(s => s.UID == record.id);
+ if (!smtpServer) {
+ this._log.trace("Skipping update for unknown item: " + record.id);
+ return;
+ }
+ smtpServer.username = record.username;
+ smtpServer.hostname = record.hostname;
+ for (let key of Object.keys(SYNCED_SMTP_PROPERTIES)) {
+ if (key in record.prefs) {
+ smtpServer[key] = record.prefs[key];
+ }
+ }
+ if (record.isDefault) {
+ MailServices.smtp.defaultServer = smtpServer;
+ }
+ },
+
+ async _updateIncoming(record) {
+ let server = MailServices.accounts.allServers.find(s => s.UID == record.id);
+ if (!server) {
+ this._log.trace("Skipping update for unknown item: " + record.id);
+ return;
+ }
+ if (server.type != record.type) {
+ throw new Components.Exception(
+ `Refusing to change server type from ${server.type} to ${record.type}`,
+ Cr.NS_ERROR_FAILURE
+ );
+ }
+
+ for (let key of Object.keys(SYNCED_SERVER_PROPERTIES)) {
+ if (key in record.prefs) {
+ server[key] = record.prefs[key];
+ }
+ }
+ },
+
+ /**
+ * Determine whether a record with the specified ID exists.
+ *
+ * Takes a string record ID and returns a booleans saying whether the record
+ * exists.
+ *
+ * @param id
+ * string record ID
+ * @return boolean indicating whether record exists locally
+ */
+ async itemExists(id) {
+ return id in (await this.getAllIDs());
+ },
+
+ /**
+ * Obtain the set of all known record IDs.
+ *
+ * @return Object with ID strings as keys and values of true. The values
+ * are ignored.
+ */
+ async getAllIDs() {
+ let ids = {};
+ for (let s of MailServices.smtp.servers) {
+ ids[s.UID] = true;
+ }
+ for (let s of MailServices.accounts.allServers) {
+ if (["imap", "pop3"].includes(s.type)) {
+ ids[s.UID] = true;
+ }
+ }
+ return ids;
+ },
+
+ /**
+ * Create a record from the specified ID.
+ *
+ * If the ID is known, the record should be populated with metadata from
+ * the store. If the ID is not known, the record should be created with the
+ * delete field set to true.
+ *
+ * @param id
+ * string record ID
+ * @param collection
+ * Collection to add record to. This is typically passed into the
+ * constructor for the newly-created record.
+ * @return record type for this engine
+ */
+ async createRecord(id, collection) {
+ let record = new AccountRecord(collection, id);
+
+ let server = MailServices.smtp.servers.find(s => s.UID == id);
+ if (server) {
+ record.type = "smtp";
+ record.username = server.username;
+ record.hostname = server.hostname;
+ record.prefs = {};
+ for (let key of Object.keys(SYNCED_SMTP_PROPERTIES)) {
+ record.prefs[key] = server[key];
+ }
+ record.isDefault = MailServices.smtp.defaultServer == server;
+ return record;
+ }
+
+ server = MailServices.accounts.allServers.find(s => s.UID == id);
+ // If we don't know about this ID, mark the record as deleted.
+ if (!server) {
+ record.deleted = true;
+ return record;
+ }
+
+ record.type = server.type;
+ record.username = server.username;
+ record.hostname = server.hostName;
+ record.prefs = {};
+ for (let key of Object.keys(SYNCED_SERVER_PROPERTIES)) {
+ record.prefs[key] = server[key];
+ }
+
+ return record;
+ },
+};
+
+function AccountTracker(name, engine) {
+ Tracker.call(this, name, engine);
+}
+AccountTracker.prototype = {
+ __proto__: Tracker.prototype,
+
+ _changedIDs: new Set(),
+ _ignoreAll: false,
+
+ async getChangedIDs() {
+ let changes = {};
+ for (let id of this._changedIDs) {
+ changes[id] = 0;
+ }
+ return changes;
+ },
+
+ clearChangedIDs() {
+ this._changedIDs.clear();
+ },
+
+ get ignoreAll() {
+ return this._ignoreAll;
+ },
+
+ set ignoreAll(value) {
+ this._ignoreAll = value;
+ },
+
+ onStart() {
+ Services.prefs.addObserver("mail.server.", this);
+ Services.obs.addObserver(this, "message-server-removed");
+ },
+
+ onStop() {
+ Services.prefs.removeObserver("mail.server.", this);
+ Services.obs.removeObserver(this, "message-server-removed");
+ },
+
+ observe(subject, topic, data) {
+ if (this._ignoreAll) {
+ return;
+ }
+
+ let server;
+ if (topic == "message-server-removed") {
+ server = subject.QueryInterface(Ci.nsIMsgIncomingServer);
+ } else {
+ let serverKey = data.split(".")[2];
+ let prefName = data.substring(serverKey.length + 13);
+ if (!Object.values(SYNCED_SERVER_PROPERTIES).includes(prefName)) {
+ return;
+ }
+
+ // Don't use getIncomingServer or it'll throw if the server doesn't exist.
+ server = MailServices.accounts.allServers.find(s => s.key == serverKey);
+ }
+
+ if (
+ server &&
+ ["imap", "pop3"].includes(server.type) &&
+ !this._changedIDs.has(server.UID)
+ ) {
+ this._changedIDs.add(server.UID);
+ this.score += SCORE_INCREMENT_XLARGE;
+ }
+ },
+};