releases-comm-central/mail/services/sync/modules/engines/identities.sys.mjs

398 строки
11 KiB
JavaScript

/* 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";
import { SCORE_INCREMENT_XLARGE } from "resource://services-sync/constants.sys.mjs";
const { MailServices } = ChromeUtils.import(
"resource:///modules/MailServices.jsm"
);
const SYNCED_IDENTITY_PROPERTIES = {
attachSignature: "attach_signature",
attachVCard: "attach_vcard",
autoQuote: "auto_quote",
catchAll: "catchAll",
catchAllHint: "catchAllHint",
composeHtml: "compose_html",
email: "useremail",
escapedVCard: "escapedVCard",
fullName: "fullName",
htmlSigFormat: "htmlSigFormat",
htmlSigText: "htmlSigText",
label: "label",
organization: "organization",
replyOnTop: "reply_on_top",
replyTo: "reply_to",
sigBottom: "sig_bottom",
sigOnForward: "sig_on_fwd",
sigOnReply: "sig_on_reply",
};
/**
* IdentityRecord 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 IdentityRecord(collection, id) {
CryptoWrapper.call(this, collection, id);
}
IdentityRecord.prototype = {
__proto__: CryptoWrapper.prototype,
_logName: "Record.Identity",
};
Utils.deferGetSet(IdentityRecord, "cleartext", ["accounts", "prefs", "smtpID"]);
export function IdentitiesEngine(service) {
SyncEngine.call(this, "Identities", service);
}
IdentitiesEngine.prototype = {
__proto__: SyncEngine.prototype,
_storeObj: IdentityStore,
_trackerObj: IdentityTracker,
_recordObj: IdentityRecord,
version: 1,
syncPriority: 4,
/*
* 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 IdentityStore(name, engine) {
Store.call(this, name, engine);
}
IdentityStore.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) {
const identity = MailServices.accounts.createIdentity();
identity.UID = record.id;
for (const key of Object.keys(SYNCED_IDENTITY_PROPERTIES)) {
if (key in record.prefs) {
identity[key] = record.prefs[key];
}
}
if (record.smtpID) {
const smtpServer = MailServices.smtp.servers.find(
s => s.UID == record.smtpID
);
if (smtpServer) {
identity.smtpServerKey = smtpServer.key;
} else {
this._log.warn(
`Identity uses SMTP server ${record.smtpID}, but it doesn't exist.`
);
}
}
for (const { id, isDefault } of record.accounts) {
const account = MailServices.accounts.accounts.find(
a => a.incomingServer?.UID == id
);
if (account) {
account.addIdentity(identity);
if (isDefault) {
account.defaultIdentity = identity;
}
} else {
this._log.warn(`Identity is for account ${id}, but it doesn't exist.`);
}
}
},
/**
* 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) {
const identity = MailServices.accounts.allIdentities.find(
i => i.UID == record.id
);
if (!identity) {
this._log.trace("Asked to remove record that doesn't exist, ignoring");
return;
}
for (const server of MailServices.accounts.getServersForIdentity(
identity
)) {
const account = MailServices.accounts.findAccountForServer(server);
account.removeIdentity(identity);
// Removing the identity from one account should destroy it.
// No need to continue.
return;
}
},
/**
* 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) {
const identity = MailServices.accounts.allIdentities.find(
i => i.UID == record.id
);
if (!identity) {
this._log.trace("Skipping update for unknown item: " + record.id);
return;
}
for (const key of Object.keys(SYNCED_IDENTITY_PROPERTIES)) {
if (key in record.prefs) {
identity[key] = record.prefs[key];
}
}
if (record.smtpID) {
const smtpServer = MailServices.smtp.servers.find(
s => s.UID == record.smtpID
);
if (smtpServer) {
identity.smtpServerKey = smtpServer.key;
} else {
this._log.warn(
`Identity uses SMTP server ${record.smtpID}, but it doesn't exist.`
);
}
} else {
identity.smtpServerKey = null;
}
for (const { id, isDefault } of record.accounts) {
const account = MailServices.accounts.accounts.find(
a => a.incomingServer?.UID == id
);
if (account) {
if (!account.identities.includes(identity)) {
account.addIdentity(identity);
}
if (isDefault && account.defaultIdentity != identity) {
account.defaultIdentity = identity;
}
} else {
this._log.warn(`Identity is for account ${id}, but it doesn't exist.`);
}
}
},
/**
* 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() {
const ids = {};
for (const i of MailServices.accounts.allIdentities) {
const servers = MailServices.accounts.getServersForIdentity(i);
if (servers.find(s => ["imap", "pop3"].includes(s.type))) {
ids[i.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) {
const record = new IdentityRecord(collection, id);
const identity = MailServices.accounts.allIdentities.find(i => i.UID == id);
// If we don't know about this ID, mark the record as deleted.
if (!identity) {
record.deleted = true;
return record;
}
record.accounts = [];
for (const server of MailServices.accounts.getServersForIdentity(
identity
)) {
const account = MailServices.accounts.findAccountForServer(server);
if (account) {
record.accounts.push({
id: server.UID,
isDefault: account.defaultIdentity == identity,
});
}
}
record.prefs = {};
for (const key of Object.keys(SYNCED_IDENTITY_PROPERTIES)) {
record.prefs[key] = identity[key];
}
if (identity.smtpServerKey) {
const smtpServer = MailServices.smtp.getServerByIdentity(identity);
record.smtpID = smtpServer.UID;
}
return record;
},
};
function IdentityTracker(name, engine) {
Tracker.call(this, name, engine);
}
IdentityTracker.prototype = {
__proto__: Tracker.prototype,
_changedIDs: new Set(),
_ignoreAll: false,
async getChangedIDs() {
const changes = {};
for (const 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.identity.", this);
Services.obs.addObserver(this, "account-identity-added");
Services.obs.addObserver(this, "account-identity-removed");
Services.obs.addObserver(this, "account-default-identity-changed");
},
onStop() {
Services.prefs.removeObserver("mail.account.", this);
Services.obs.removeObserver(this, "account-identity-added");
Services.obs.removeObserver(this, "account-identity-removed");
Services.obs.removeObserver(this, "account-default-identity-changed");
},
observe(subject, topic, data) {
if (this._ignoreAll) {
return;
}
const markAsChanged = identity => {
if (identity && !this._changedIDs.has(identity.UID)) {
this._changedIDs.add(identity.UID);
this.score = SCORE_INCREMENT_XLARGE;
}
};
if (
["account-identity-added", "account-identity-removed"].includes(topic)
) {
markAsChanged(subject.QueryInterface(Ci.nsIMsgIdentity));
return;
}
if (topic == "account-default-identity-changed") {
// The default identity has changed, update the default identity and
// the previous one, which will now be second on the list.
const [newDefault, oldDefault] = Services.prefs
.getStringPref(`mail.account.${data}.identities`)
.split(",");
if (newDefault) {
markAsChanged(MailServices.accounts.getIdentity(newDefault));
}
if (oldDefault) {
markAsChanged(MailServices.accounts.getIdentity(oldDefault));
}
return;
}
const idKey = data.split(".")[2];
const prefName = data.substring(idKey.length + 15);
if (
prefName != "smtpServer" &&
!Object.values(SYNCED_IDENTITY_PROPERTIES).includes(prefName)
) {
return;
}
// Don't use .getIdentity because it will create one if it doesn't exist.
markAsChanged(
MailServices.accounts.allIdentities.find(i => i.key == idKey)
);
},
};