diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/addrbook/modules/AddrBookManager.jsm | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mailnews/addrbook/modules/AddrBookManager.jsm')
-rw-r--r-- | comm/mailnews/addrbook/modules/AddrBookManager.jsm | 608 |
1 files changed, 608 insertions, 0 deletions
diff --git a/comm/mailnews/addrbook/modules/AddrBookManager.jsm b/comm/mailnews/addrbook/modules/AddrBookManager.jsm new file mode 100644 index 0000000000..6e15a4c971 --- /dev/null +++ b/comm/mailnews/addrbook/modules/AddrBookManager.jsm @@ -0,0 +1,608 @@ +/* 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 = ["AddrBookManager"]; + +const { AppConstants } = ChromeUtils.importESModule( + "resource://gre/modules/AppConstants.sys.mjs" +); +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + clearTimeout: "resource://gre/modules/Timer.sys.mjs", + setTimeout: "resource://gre/modules/Timer.sys.mjs", +}); + +XPCOMUtils.defineLazyModuleGetters(lazy, { + compareAddressBooks: "resource:///modules/AddrBookUtils.jsm", + MailGlue: "resource:///modules/MailGlue.jsm", +}); + +/** Test for valid directory URIs. */ +const URI_REGEXP = /^([\w-]+):\/\/([\w\.-]*)([/:].*|$)/; + +/** + * When initialized, a map of nsIAbDirectory objects. Keys to this map are + * the directories' URIs. + */ +let store = null; + +/** Valid address book types. This differs by operating system. */ +let types = ["jsaddrbook", "jscarddav", "moz-abldapdirectory"]; +if (AppConstants.platform == "macosx") { + types.push("moz-abosxdirectory"); +} else if (AppConstants.platform == "win") { + types.push("moz-aboutlookdirectory"); +} + +/** + * A pre-sorted list of directories in the right order, to be returned by + * AddrBookManager.directories. That function is called a lot, and there's + * no need to sort the list every time. + * + * Call updateSortedDirectoryList after `store` changes and before any + * notifications happen. + */ +let sortedDirectoryList = []; +function updateSortedDirectoryList() { + sortedDirectoryList = [...store.values()]; + sortedDirectoryList.sort(lazy.compareAddressBooks); +} + +/** + * Initialise an address book directory by URI. + * + * @param {string} uri - URI for the directory. + * @param {boolean} shouldStore - Whether to keep a reference to this address + * book in the store. + * @returns {nsIAbDirectory} + */ +function createDirectoryObject(uri, shouldStore = false) { + let uriParts = URI_REGEXP.exec(uri); + if (!uriParts) { + throw Components.Exception( + `Unexpected uri: ${uri}`, + Cr.NS_ERROR_MALFORMED_URI + ); + } + + let [, scheme] = uriParts; + let dir = Cc[ + `@mozilla.org/addressbook/directory;1?type=${scheme}` + ].createInstance(Ci.nsIAbDirectory); + + try { + if (shouldStore) { + // This must happen before .init is called, or the OS X provider breaks + // in some circumstances. If .init fails, we'll remove it again. + // The Outlook provider also needs this since during the initialisation + // of the top-most directory, contained mailing lists already need + // to loop that directory. + store.set(uri, dir); + } + dir.init(uri); + } catch (ex) { + if (shouldStore) { + store.delete(uri); + } + throw ex; + } + + return dir; +} + +/** + * Read the preferences and create any address books defined there. + */ +function ensureInitialized() { + if (store !== null) { + return; + } + if (lazy.MailGlue.isToolboxProcess) { + throw new Components.Exception( + "AddrBookManager tried to start in the Developer Tools process!", + Cr.NS_ERROR_UNEXPECTED + ); + } + + store = new Map(); + + for (let pref of Services.prefs.getChildList("ldap_2.servers.")) { + try { + if (pref.endsWith(".uri")) { + let uri = Services.prefs.getStringPref(pref); + if (uri.startsWith("ldap://") || uri.startsWith("ldaps://")) { + let prefName = pref.substring(0, pref.length - 4); + + uri = `moz-abldapdirectory://${prefName}`; + createDirectoryObject(uri, true); + } + } else if (pref.endsWith(".dirType")) { + let prefName = pref.substring(0, pref.length - 8); + let dirType = Services.prefs.getIntPref(pref); + let fileName = Services.prefs.getStringPref(`${prefName}.filename`, ""); + let uri = Services.prefs.getStringPref(`${prefName}.uri`, ""); + + switch (dirType) { + case Ci.nsIAbManager.MAPI_DIRECTORY_TYPE: + if ( + Cu.isInAutomation || + Services.env.exists("XPCSHELL_TEST_PROFILE_DIR") + ) { + // Don't load the OS Address Book in tests. + break; + } + if (Services.prefs.getIntPref(`${prefName}.position`, 1) < 1) { + // Migration: the previous address book manager set the position + // value to 0 to indicate the removal of an address book. + Services.prefs.clearUserPref(`${prefName}.position`); + Services.prefs.setIntPref(pref, -1); + break; + } + if (AppConstants.platform == "macosx") { + createDirectoryObject(uri, true); + } else if (AppConstants.platform == "win") { + let outlookInterface = Cc[ + "@mozilla.org/addressbook/outlookinterface;1" + ].getService(Ci.nsIAbOutlookInterface); + for (let folderURI of outlookInterface.getFolderURIs(uri)) { + createDirectoryObject(folderURI, true); + } + } + break; + case Ci.nsIAbManager.JS_DIRECTORY_TYPE: + if (fileName) { + let uri = `jsaddrbook://${fileName}`; + createDirectoryObject(uri, true); + } + break; + case Ci.nsIAbManager.CARDDAV_DIRECTORY_TYPE: + if (fileName) { + let uri = `jscarddav://${fileName}`; + createDirectoryObject(uri, true); + } + break; + } + } + } catch (ex) { + console.error(ex); + } + } + + updateSortedDirectoryList(); +} + +// Force the manager to shut down. For tests only. +Services.obs.addObserver(async () => { + // Allow directories to tidy up. + for (let directory of store.values()) { + await directory.cleanUp(); + } + // Clear the store. The next call to ensureInitialized will recreate it. + store = null; + Services.obs.notifyObservers(null, "addrbook-reloaded"); +}, "addrbook-reload"); + +/** Cache for the cardForEmailAddress function, and timer to clear it. */ +let addressCache = new Map(); +let addressCacheTimer = null; + +// Throw away cached cards if the display name properties change, so we can +// get the updated version of the card that changed. +Services.prefs.addObserver("mail.displayname.version", () => { + addressCache.clear(); + Services.obs.notifyObservers(null, "addrbook-displayname-changed"); +}); + +// When this prefence has been updated, we need to update the +// mail.displayname.version, which notifies it's preference observer (above). +// This will then notify the addrbook-displayname-changed observer, and change +// the displayname in the thread tree and message header. +Services.prefs.addObserver("mail.showCondensedAddresses", () => { + Services.prefs.setIntPref( + "mail.displayname.version", + Services.prefs.getIntPref("mail.displayname.version") + 1 + ); +}); + +/** + * @implements {nsIAbManager} + * @implements {nsICommandLineHandler} + */ +function AddrBookManager() {} +AddrBookManager.prototype = { + QueryInterface: ChromeUtils.generateQI([ + "nsIAbManager", + "nsICommandLineHandler", + ]), + classID: Components.ID("{224d3ef9-d81c-4d94-8826-a79a5835af93}"), + + /* nsIAbManager */ + + get directories() { + ensureInitialized(); + return sortedDirectoryList.slice(); + }, + getDirectory(uri) { + if (uri.startsWith("moz-abdirectory://")) { + throw new Components.Exception( + "The root address book no longer exists", + Cr.NS_ERROR_FAILURE + ); + } + + ensureInitialized(); + if (store.has(uri)) { + return store.get(uri); + } + + let uriParts = URI_REGEXP.exec(uri); + if (!uriParts) { + throw Components.Exception( + `Unexpected uri: ${uri}`, + Cr.NS_ERROR_MALFORMED_URI + ); + } + let [, scheme, fileName, tail] = uriParts; + if (tail && types.includes(scheme)) { + if ( + (scheme == "jsaddrbook" && tail.startsWith("/")) || + scheme == "moz-aboutlookdirectory" + ) { + let parent; + if (scheme == "jsaddrbook") { + parent = this.getDirectory(`${scheme}://${fileName}`); + } else { + parent = this.getDirectory(`${scheme}:///${tail.split("/")[1]}`); + } + for (let list of parent.childNodes) { + list.QueryInterface(Ci.nsIAbDirectory); + if (list.URI == uri) { + return list; + } + } + throw Components.Exception( + `No ${scheme} directory for uri=${uri}`, + Cr.NS_ERROR_UNEXPECTED + ); + } else if (scheme == "jscarddav") { + throw Components.Exception( + `No ${scheme} directory for uri=${uri}`, + Cr.NS_ERROR_UNEXPECTED + ); + } + // `tail` could point to a mailing list. + return createDirectoryObject(uri); + } + throw Components.Exception( + `No directory for uri=${uri}`, + Cr.NS_ERROR_FAILURE + ); + }, + getDirectoryFromId(dirPrefId) { + ensureInitialized(); + for (let dir of store.values()) { + if (dir.dirPrefId == dirPrefId) { + return dir; + } + } + return null; + }, + getDirectoryFromUID(uid) { + ensureInitialized(); + for (let dir of store.values()) { + if (dir.UID == uid) { + return dir; + } + } + return null; + }, + getMailListFromName(name) { + ensureInitialized(); + for (let dir of store.values()) { + let hit = dir.getMailListFromName(name); + if (hit) { + return hit; + } + } + return null; + }, + newAddressBook(dirName, uri, type, uid) { + function ensureUniquePrefName() { + let leafName = dirName.replace(/\W/g, ""); + if (!leafName) { + leafName = "_nonascii"; + } + + let existingNames = Array.from(store.values(), dir => dir.dirPrefId); + let uniqueCount = 0; + prefName = `ldap_2.servers.${leafName}`; + while (existingNames.includes(prefName)) { + prefName = `ldap_2.servers.${leafName}_${++uniqueCount}`; + } + } + + if (!dirName) { + throw new Components.Exception( + "dirName must be specified", + Cr.NS_ERROR_INVALID_ARG + ); + } + if (uid && this.getDirectoryFromUID(uid)) { + throw new Components.Exception( + `An address book with the UID ${uid} already exists`, + Cr.NS_ERROR_ABORT + ); + } + + let prefName; + ensureInitialized(); + + switch (type) { + case Ci.nsIAbManager.LDAP_DIRECTORY_TYPE: { + let file = Services.dirsvc.get("ProfD", Ci.nsIFile); + file.append("ldap.sqlite"); + file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + + ensureUniquePrefName(); + Services.prefs.setStringPref(`${prefName}.description`, dirName); + Services.prefs.setStringPref(`${prefName}.filename`, file.leafName); + Services.prefs.setStringPref(`${prefName}.uri`, uri); + if (uid) { + Services.prefs.setStringPref(`${prefName}.uid`, uid); + } + + uri = `moz-abldapdirectory://${prefName}`; + let dir = createDirectoryObject(uri, true); + updateSortedDirectoryList(); + Services.obs.notifyObservers(dir, "addrbook-directory-created"); + break; + } + case Ci.nsIAbManager.MAPI_DIRECTORY_TYPE: { + if (AppConstants.platform == "macosx") { + uri = "moz-abosxdirectory:///"; + if (store.has(uri)) { + throw Components.Exception( + `Can't create new ab of type=${type} - already exists`, + Cr.NS_ERROR_UNEXPECTED + ); + } + prefName = "ldap_2.servers.osx"; + } else if (AppConstants.platform == "win") { + uri = "moz-aboutlookdirectory:///"; + if (store.has(uri)) { + throw Components.Exception( + `Can't create new ab of type=${type} - already exists`, + Cr.NS_ERROR_UNEXPECTED + ); + } + prefName = "ldap_2.servers.outlook"; + } else { + throw Components.Exception( + "Can't create new ab of type=MAPI_DIRECTORY_TYPE", + Cr.NS_ERROR_UNEXPECTED + ); + } + + Services.prefs.setIntPref( + `${prefName}.dirType`, + Ci.nsIAbManager.MAPI_DIRECTORY_TYPE + ); + Services.prefs.setStringPref( + `${prefName}.description`, + "chrome://messenger/locale/addressbook/addressBook.properties" + ); + Services.prefs.setStringPref(`${prefName}.uri`, uri); + if (uid) { + Services.prefs.setStringPref(`${prefName}.uid`, uid); + } + + if (AppConstants.platform == "macosx") { + let dir = createDirectoryObject(uri, true); + updateSortedDirectoryList(); + Services.obs.notifyObservers(dir, "addrbook-directory-created"); + } else if (AppConstants.platform == "win") { + let outlookInterface = Cc[ + "@mozilla.org/addressbook/outlookinterface;1" + ].getService(Ci.nsIAbOutlookInterface); + for (let folderURI of outlookInterface.getFolderURIs(uri)) { + let dir = createDirectoryObject(folderURI, true); + updateSortedDirectoryList(); + Services.obs.notifyObservers(dir, "addrbook-directory-created"); + } + } + break; + } + case Ci.nsIAbManager.JS_DIRECTORY_TYPE: + case Ci.nsIAbManager.CARDDAV_DIRECTORY_TYPE: { + let file = Services.dirsvc.get("ProfD", Ci.nsIFile); + file.append("abook.sqlite"); + file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644); + + ensureUniquePrefName(); + Services.prefs.setStringPref(`${prefName}.description`, dirName); + Services.prefs.setIntPref(`${prefName}.dirType`, type); + Services.prefs.setStringPref(`${prefName}.filename`, file.leafName); + if (uid) { + Services.prefs.setStringPref(`${prefName}.uid`, uid); + } + + let scheme = + type == Ci.nsIAbManager.JS_DIRECTORY_TYPE + ? "jsaddrbook" + : "jscarddav"; + uri = `${scheme}://${file.leafName}`; + let dir = createDirectoryObject(uri, true); + updateSortedDirectoryList(); + Services.obs.notifyObservers(dir, "addrbook-directory-created"); + break; + } + default: + throw Components.Exception( + `Unexpected directory type: ${type}`, + Cr.NS_ERROR_UNEXPECTED + ); + } + + return prefName; + }, + addAddressBook(dir) { + if ( + !dir.URI || + !dir.dirName || + !dir.UID || + dir.isMailList || + dir.isQuery || + dir.dirPrefId + ) { + throw new Components.Exception( + "Invalid directory", + Cr.NS_ERROR_INVALID_ARG + ); + } + + ensureInitialized(); + if (store.has(dir.URI)) { + throw new Components.Exception( + "Directory already exists", + Cr.NS_ERROR_UNEXPECTED + ); + } + + store.set(dir.URI, dir); + updateSortedDirectoryList(); + Services.obs.notifyObservers(dir, "addrbook-directory-created"); + }, + deleteAddressBook(uri) { + let uriParts = URI_REGEXP.exec(uri); + if (!uriParts) { + throw Components.Exception("", Cr.NS_ERROR_MALFORMED_URI); + } + + let [, scheme, fileName, tail] = uriParts; + if (tail && tail.startsWith("/")) { + let dir; + if (scheme == "jsaddrbook") { + dir = store.get(`${scheme}://${fileName}`); + } else if (scheme == "moz-aboutlookdirectory") { + dir = store.get(`${scheme}:///${tail.split("/")[1]}`); + } + let list = this.getDirectory(uri); + if (dir && list) { + dir.deleteDirectory(list); + return; + } + } + + let dir = store.get(uri); + if (!dir) { + throw new Components.Exception( + `Address book not found: ${uri}`, + Cr.NS_ERROR_UNEXPECTED + ); + } + + let prefName = dir.dirPrefId; + if (prefName) { + let dirType = Services.prefs.getIntPref(`${prefName}.dirType`, 0); + fileName = dir.fileName; + + // Deleting the built-in address books is very bad. + if (["ldap_2.servers.pab", "ldap_2.servers.history"].includes(prefName)) { + throw new Components.Exception( + "Refusing to delete a built-in address book", + Cr.NS_ERROR_FAILURE + ); + } + + for (let name of Services.prefs.getChildList(`${prefName}.`)) { + Services.prefs.clearUserPref(name); + } + if (dirType == Ci.nsIAbManager.MAPI_DIRECTORY_TYPE) { + // The prefs for this directory type are defaults. Setting the dirType + // to -1 ensures the directory is ignored. + Services.prefs.setIntPref(`${prefName}.dirType`, -1); + } + } + + store.delete(uri); + updateSortedDirectoryList(); + + // Clear this reference to the deleted address book. + if (Services.prefs.getStringPref("mail.collect_addressbook") == uri) { + Services.prefs.clearUserPref("mail.collect_addressbook"); + } + + dir.cleanUp().then(() => { + if (fileName) { + let file = Services.dirsvc.get("ProfD", Ci.nsIFile); + file.append(fileName); + if (file.exists()) { + file.remove(false); + } + } + + Services.obs.notifyObservers(dir, "addrbook-directory-deleted"); + }); + }, + mailListNameExists(name) { + ensureInitialized(); + for (let dir of store.values()) { + if (dir.hasMailListWithName(name)) { + return true; + } + } + return false; + }, + /** + * Finds out if the directory name already exists. + * + * @param {string} name - The name of a directory to check for. + */ + directoryNameExists(name) { + ensureInitialized(); + for (let dir of store.values()) { + if (dir.dirName.toLowerCase() === name.toLowerCase()) { + return true; + } + } + return false; + }, + cardForEmailAddress(emailAddress) { + if (!emailAddress) { + return null; + } + + if (addressCacheTimer) { + lazy.clearTimeout(addressCacheTimer); + } + addressCacheTimer = lazy.setTimeout(() => { + addressCacheTimer = null; + addressCache.clear(); + }, 60000); + + if (addressCache.has(emailAddress)) { + return addressCache.get(emailAddress); + } + + for (let directory of sortedDirectoryList) { + try { + let card = directory.cardForEmailAddress(emailAddress); + if (card) { + addressCache.set(emailAddress, card); + return card; + } + } catch (ex) { + // Directories can throw, that's okay. + } + } + + addressCache.set(emailAddress, null); + return null; + }, +}; |