diff options
Diffstat (limited to 'mailnews/addrbook/src')
97 files changed, 31044 insertions, 0 deletions
diff --git a/mailnews/addrbook/src/moz.build b/mailnews/addrbook/src/moz.build new file mode 100644 index 000000000..648958cf2 --- /dev/null +++ b/mailnews/addrbook/src/moz.build @@ -0,0 +1,93 @@ +# vim: set filetype=python: +# 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/. + +EXPORTS += [ + 'nsAbDirProperty.h', + 'nsDirPrefs.h', + 'nsVCardObj.h', +] + +SOURCES += [ + 'nsAbAddressCollector.cpp', + 'nsAbBooleanExpression.cpp', + 'nsAbBSDirectory.cpp', + 'nsAbCardProperty.cpp', + 'nsAbContentHandler.cpp', + 'nsAbDirectoryQuery.cpp', + 'nsAbDirectoryQueryProxy.cpp', + 'nsAbDirFactoryService.cpp', + 'nsAbDirProperty.cpp', + 'nsAbLDIFService.cpp', + 'nsAbManager.cpp', + 'nsAbMDBCard.cpp', + 'nsAbMDBDirectory.cpp', + 'nsAbMDBDirFactory.cpp', + 'nsAbMDBDirProperty.cpp', + 'nsAbQueryStringToExpression.cpp', + 'nsAbView.cpp', + 'nsAddbookProtocolHandler.cpp', + 'nsAddbookUrl.cpp', + 'nsAddrDatabase.cpp', + 'nsDirPrefs.cpp', + 'nsMsgVCardService.cpp', + 'nsVCard.cpp', + 'nsVCardObj.cpp', +] + +if CONFIG['OS_ARCH'] == 'WINNT' and CONFIG['MOZ_MAPI_SUPPORT']: + SOURCES += [ + 'nsAbOutlookDirectory.cpp', + 'nsAbOutlookDirFactory.cpp', + 'nsAbWinHelper.cpp', + 'nsMapiAddressBook.cpp', + 'nsWabAddressBook.cpp', + ] + +if CONFIG['OS_ARCH'] == 'Darwin': + SOURCES += [ + 'nsAbOSXDirFactory.cpp', + ] + + SOURCES += [ + 'nsAbOSXCard.mm', + 'nsAbOSXDirectory.mm', + 'nsAbOSXUtils.mm', + ] + +if CONFIG['MOZ_LDAP_XPCOM']: + SOURCES += [ + 'nsAbBoolExprToLDAPFilter.cpp', + 'nsAbLDAPCard.cpp', + 'nsAbLDAPDirectory.cpp', + 'nsAbLDAPDirectoryModify.cpp', + 'nsAbLDAPDirectoryQuery.cpp', + 'nsAbLDAPDirFactory.cpp', + 'nsAbLDAPListenerBase.cpp', + 'nsAbLDAPReplicationData.cpp', + 'nsAbLDAPReplicationQuery.cpp', + 'nsAbLDAPReplicationService.cpp', + ] + # XXX These files are not being built as they don't work. Bug 311632 should + # fix them. + # nsAbLDAPChangeLogQuery.cpp + # nsAbLDAPChangeLogData.cpp + + EXTRA_COMPONENTS += [ + 'nsAbLDAPAutoCompleteSearch.js', + ] + + DEFINES['MOZ_LDAP_XPCOM'] = True + +EXTRA_COMPONENTS += [ + 'nsAbAutoCompleteMyDomain.js', + 'nsAbAutoCompleteSearch.js', + 'nsAbLDAPAttributeMap.js', +] + +EXTRA_PP_COMPONENTS += [ + 'nsAddrbook.manifest', +] + +FINAL_LIBRARY = 'mail' diff --git a/mailnews/addrbook/src/nsAbAddressCollector.cpp b/mailnews/addrbook/src/nsAbAddressCollector.cpp new file mode 100644 index 000000000..60f359601 --- /dev/null +++ b/mailnews/addrbook/src/nsAbAddressCollector.cpp @@ -0,0 +1,331 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "msgCore.h" // for pre-compiled headers +#include "nsISimpleEnumerator.h" + +#include "nsIAbCard.h" +#include "nsAbBaseCID.h" +#include "nsAbAddressCollector.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsStringGlue.h" +#include "prmem.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsIAbManager.h" +#include "mozilla/mailnews/MimeHeaderParser.h" + +using namespace mozilla::mailnews; + +NS_IMPL_ISUPPORTS(nsAbAddressCollector, nsIAbAddressCollector, nsIObserver) + +#define PREF_MAIL_COLLECT_ADDRESSBOOK "mail.collect_addressbook" + +nsAbAddressCollector::nsAbAddressCollector() +{ +} + +nsAbAddressCollector::~nsAbAddressCollector() +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPrefBranchInt(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) + pPrefBranchInt->RemoveObserver(PREF_MAIL_COLLECT_ADDRESSBOOK, this); +} + +/** + * Returns the first card found with the specified email address. This + * returns an already addrefed pointer to the card if the card is found. + */ +already_AddRefed<nsIAbCard> +nsAbAddressCollector::GetCardForAddress(const nsACString &aEmailAddress, + nsIAbDirectory **aDirectory) +{ + nsresult rv; + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = abManager->GetDirectories(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, nullptr); + + bool hasMore; + nsCOMPtr<nsISupports> supports; + nsCOMPtr<nsIAbDirectory> directory; + nsCOMPtr<nsIAbCard> result; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + rv = enumerator->GetNext(getter_AddRefs(supports)); + NS_ENSURE_SUCCESS(rv, nullptr); + + directory = do_QueryInterface(supports, &rv); + if (NS_FAILED(rv)) + continue; + + // Some implementations may return NS_ERROR_NOT_IMPLEMENTED here, + // so just catch the value and continue. + if (NS_FAILED(directory->CardForEmailAddress(aEmailAddress, + getter_AddRefs(result)))) + { + continue; + } + + if (result) + { + if (aDirectory) + directory.forget(aDirectory); + return result.forget(); + } + } + return nullptr; +} + +NS_IMETHODIMP +nsAbAddressCollector::CollectAddress(const nsACString &aAddresses, + bool aCreateCard, + uint32_t aSendFormat) +{ + // If we've not got a valid directory, no point in going any further + if (!mDirectory) + return NS_OK; + + // note that we're now setting the whole recipient list, + // not just the pretty name of the first recipient. + nsTArray<nsCString> names; + nsTArray<nsCString> addresses; + ExtractAllAddresses(EncodedHeader(aAddresses), + UTF16ArrayAdapter<>(names), UTF16ArrayAdapter<>(addresses)); + uint32_t numAddresses = names.Length(); + + for (uint32_t i = 0; i < numAddresses; i++) + { + // Don't allow collection of addresses with no email address, it makes + // no sense. Whilst we should never get here in most normal cases, we + // should still be careful. + if (addresses[i].IsEmpty()) + continue; + + CollectSingleAddress(addresses[i], names[i], aCreateCard, aSendFormat, + false); + } + return NS_OK; +} + +NS_IMETHODIMP +nsAbAddressCollector::CollectSingleAddress(const nsACString &aEmail, + const nsACString &aDisplayName, + bool aCreateCard, + uint32_t aSendFormat, + bool aSkipCheckExisting) +{ + if (!mDirectory) + return NS_OK; + + nsresult rv; + + nsCOMPtr<nsIAbDirectory> originDirectory; + nsCOMPtr<nsIAbCard> card = (!aSkipCheckExisting) ? + GetCardForAddress(aEmail, getter_AddRefs(originDirectory)) : nullptr; + + if (!card && (aCreateCard || aSkipCheckExisting)) + { + card = do_CreateInstance(NS_ABCARDPROPERTY_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && card) + { + // Set up the fields for the new card. + SetNamesForCard(card, aDisplayName); + AutoCollectScreenName(card, aEmail); + + if (NS_SUCCEEDED(card->SetPrimaryEmail(NS_ConvertUTF8toUTF16(aEmail)))) + { + card->SetPropertyAsUint32(kPreferMailFormatProperty, aSendFormat); + + nsCOMPtr<nsIAbCard> addedCard; + rv = mDirectory->AddCard(card, getter_AddRefs(addedCard)); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to add card"); + } + } + } + else if (card && originDirectory) + { + // It could be that the origin directory is read-only, so don't try and + // write to it if it is. + bool readOnly; + rv = originDirectory->GetReadOnly(&readOnly); + NS_ENSURE_SUCCESS(rv, rv); + + if (readOnly) + return NS_OK; + + // address is already in the AB, so update the names + bool modifiedCard = false; + + nsString displayName; + card->GetDisplayName(displayName); + // If we already have a display name, don't set the names on the card. + if (displayName.IsEmpty() && !aDisplayName.IsEmpty()) + modifiedCard = SetNamesForCard(card, aDisplayName); + + if (aSendFormat != nsIAbPreferMailFormat::unknown) + { + uint32_t currentFormat; + rv = card->GetPropertyAsUint32(kPreferMailFormatProperty, + ¤tFormat); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to get preferred mail format"); + + // we only want to update the AB if the current format is unknown + if (currentFormat == nsIAbPreferMailFormat::unknown && + NS_SUCCEEDED(card->SetPropertyAsUint32(kPreferMailFormatProperty, + aSendFormat))) + modifiedCard = true; + } + + if (modifiedCard) + originDirectory->ModifyCard(card); + } + + return NS_OK; +} + +// Works out the screen name to put on the card for some well-known addresses +void +nsAbAddressCollector::AutoCollectScreenName(nsIAbCard *aCard, + const nsACString &aEmail) +{ + if (!aCard) + return; + + int32_t atPos = aEmail.FindChar('@'); + if (atPos == -1) + return; + + const nsACString& domain = Substring(aEmail, atPos + 1); + + if (domain.IsEmpty()) + return; + // username in + // username@aol.com (America Online) + // username@cs.com (Compuserve) + // username@netscape.net (Netscape webmail) + // are all AIM screennames. autocollect that info. + if (domain.Equals("aol.com") || domain.Equals("cs.com") || + domain.Equals("netscape.net")) + aCard->SetPropertyAsAUTF8String(kScreenNameProperty, Substring(aEmail, 0, atPos)); + else if (domain.Equals("gmail.com") || domain.Equals("googlemail.com")) + aCard->SetPropertyAsAUTF8String(kGtalkProperty, Substring(aEmail, 0, atPos)); +} + +// Returns true if the card was modified successfully. +bool +nsAbAddressCollector::SetNamesForCard(nsIAbCard *aSenderCard, + const nsACString &aFullName) +{ + nsCString firstName; + nsCString lastName; + bool modifiedCard = false; + + if (NS_SUCCEEDED(aSenderCard->SetDisplayName(NS_ConvertUTF8toUTF16(aFullName)))) + modifiedCard = true; + + // Now split up the full name. + SplitFullName(nsCString(aFullName), firstName, lastName); + + if (!firstName.IsEmpty() && + NS_SUCCEEDED(aSenderCard->SetFirstName(NS_ConvertUTF8toUTF16(firstName)))) + modifiedCard = true; + + if (!lastName.IsEmpty() && + NS_SUCCEEDED(aSenderCard->SetLastName(NS_ConvertUTF8toUTF16(lastName)))) + modifiedCard = true; + + if (modifiedCard) + aSenderCard->SetPropertyAsBool("PreferDisplayName", false); + + return modifiedCard; +} + +// Splits the first and last name based on the space between them. +void +nsAbAddressCollector::SplitFullName(const nsCString &aFullName, nsCString &aFirstName, + nsCString &aLastName) +{ + int index = aFullName.RFindChar(' '); + if (index != -1) + { + aLastName = Substring(aFullName, index + 1); + aFirstName = Substring(aFullName, 0, index); + } +} + +// Observes the collected address book pref in case it changes. +NS_IMETHODIMP +nsAbAddressCollector::Observe(nsISupports *aSubject, const char *aTopic, + const char16_t *aData) +{ + nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject); + if (!prefBranch) { + NS_ASSERTION(prefBranch, "failed to get prefs"); + return NS_OK; + } + + SetUpAbFromPrefs(prefBranch); + return NS_OK; +} + +// Initialises the collector with the required items. +nsresult +nsAbAddressCollector::Init(void) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = prefBranch->AddObserver(PREF_MAIL_COLLECT_ADDRESSBOOK, this, false); + NS_ENSURE_SUCCESS(rv, rv); + + SetUpAbFromPrefs(prefBranch); + return NS_OK; +} + +// Performs the necessary changes to set up the collector for the specified +// collected address book. +void +nsAbAddressCollector::SetUpAbFromPrefs(nsIPrefBranch *aPrefBranch) +{ + nsCString abURI; + aPrefBranch->GetCharPref(PREF_MAIL_COLLECT_ADDRESSBOOK, + getter_Copies(abURI)); + + if (abURI.IsEmpty()) + abURI.AssignLiteral(kPersonalAddressbookUri); + + if (abURI == mABURI) + return; + + mDirectory = nullptr; + mABURI = abURI; + + nsresult rv; + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS_VOID(rv); + + rv = abManager->GetDirectory(mABURI, getter_AddRefs(mDirectory)); + NS_ENSURE_SUCCESS_VOID(rv); + + bool readOnly; + rv = mDirectory->GetReadOnly(&readOnly); + NS_ENSURE_SUCCESS_VOID(rv); + + // If the directory is read-only, we can't write to it, so just blank it out + // here, and warn because we shouldn't hit this (UI is wrong). + if (readOnly) + { + NS_ERROR("Address Collection book preferences is set to a read-only book. " + "Address collection will not take place."); + mDirectory = nullptr; + } +} diff --git a/mailnews/addrbook/src/nsAbAddressCollector.h b/mailnews/addrbook/src/nsAbAddressCollector.h new file mode 100644 index 000000000..7ef2236b8 --- /dev/null +++ b/mailnews/addrbook/src/nsAbAddressCollector.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef _nsAbAddressCollector_H_ +#define _nsAbAddressCollector_H_ + +#include "nsIAbAddressCollector.h" +#include "nsCOMPtr.h" +#include "nsIAbDirectory.h" +#include "nsIAbCard.h" +#include "nsIObserver.h" +#include "nsStringGlue.h" + +class nsIPrefBranch; + +class nsAbAddressCollector : public nsIAbAddressCollector, + public nsIObserver +{ +public: + nsAbAddressCollector(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIABADDRESSCOLLECTOR + NS_DECL_NSIOBSERVER + + nsresult Init(); + +private: + virtual ~nsAbAddressCollector(); + already_AddRefed<nsIAbCard> GetCardForAddress(const nsACString &aEmailAddress, + nsIAbDirectory **aDirectory); + void AutoCollectScreenName(nsIAbCard *aCard, const nsACString &aEmail); + bool SetNamesForCard(nsIAbCard *aSenderCard, const nsACString &aFullName); + void SplitFullName(const nsCString &aFullName, nsCString &aFirstName, + nsCString &aLastName); + void SetUpAbFromPrefs(nsIPrefBranch *aPrefBranch); + nsCOMPtr <nsIAbDirectory> mDirectory; + nsCString mABURI; +}; + +#endif // _nsAbAddressCollector_H_ + diff --git a/mailnews/addrbook/src/nsAbAutoCompleteMyDomain.js b/mailnews/addrbook/src/nsAbAutoCompleteMyDomain.js new file mode 100644 index 000000000..1ace4ca50 --- /dev/null +++ b/mailnews/addrbook/src/nsAbAutoCompleteMyDomain.js @@ -0,0 +1,58 @@ +/* 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/. */ + +Components.utils.import("resource:///modules/mailServices.js"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function nsAbAutoCompleteMyDomain() {} + +nsAbAutoCompleteMyDomain.prototype = { + classID: Components.ID("{5b259db2-e451-4de9-8a6f-cfba91402973}"), + QueryInterface: XPCOMUtils.generateQI([ + Components.interfaces.nsIAutoCompleteSearch]), + + cachedIdKey: "", + cachedIdentity: null, + + applicableHeaders: new Set(["addr_to", "addr_cc", "addr_bcc", "addr_reply"]), + + startSearch: function(aString, aSearchParam, aResult, aListener) { + let params = aSearchParam ? JSON.parse(aSearchParam) : {}; + let applicable = ("type" in params) && this.applicableHeaders.has(params.type); + const ACR = Components.interfaces.nsIAutoCompleteResult; + var address = null; + if (applicable && aString && !aString.includes(",")) { + if (("idKey" in params) && (params.idKey != this.cachedIdKey)) { + this.cachedIdentity = MailServices.accounts.getIdentity(params.idKey); + this.cachedIdKey = params.idKey; + } + if (this.cachedIdentity.autocompleteToMyDomain) + address = aString.includes("@") ? aString : + this.cachedIdentity.email.replace(/[^@]*/, aString); + } + + var result = { + searchString: aString, + searchResult: address ? ACR.RESULT_SUCCESS : ACR.RESULT_FAILURE, + defaultIndex: -1, + errorDescription: null, + matchCount: address ? 1 : 0, + getValueAt: function() { return address; }, + getLabelAt: function() { return this.getValueAt(); }, + getCommentAt: function() { return null; }, + getStyleAt: function() { return "default-match"; }, + getImageAt: function() { return null; }, + getFinalCompleteValueAt: function(aIndex) { + return this.getValueAt(aIndex); + }, + removeValueAt: function() {} + }; + aListener.onSearchResult(this, result); + }, + + stopSearch: function() {} +}; + +var components = [nsAbAutoCompleteMyDomain]; +var NSGetFactory = XPCOMUtils.generateNSGetFactory(components); diff --git a/mailnews/addrbook/src/nsAbAutoCompleteSearch.js b/mailnews/addrbook/src/nsAbAutoCompleteSearch.js new file mode 100644 index 000000000..c6c9b8db8 --- /dev/null +++ b/mailnews/addrbook/src/nsAbAutoCompleteSearch.js @@ -0,0 +1,466 @@ +/* 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/. */ + +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource:///modules/mailServices.js"); +Components.utils.import("resource:///modules/ABQueryUtils.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +var ACR = Components.interfaces.nsIAutoCompleteResult; +var nsIAbAutoCompleteResult = Components.interfaces.nsIAbAutoCompleteResult; + +function nsAbAutoCompleteResult(aSearchString) { + // Can't create this in the prototype as we'd get the same array for + // all instances + this._searchResults = []; // final results + this.searchString = aSearchString; + this._collectedValues = new Map(); // temporary unsorted results + // Get model query from pref; this will return mail.addr_book.autocompletequery.format.phonetic + // if mail.addr_book.show_phonetic_fields == true + this.modelQuery = getModelQuery("mail.addr_book.autocompletequery.format"); + // check if the currently active model query has been modified by user + this._modelQueryHasUserValue = modelQueryHasUserValue("mail.addr_book.autocompletequery.format"); +} + +nsAbAutoCompleteResult.prototype = { + _searchResults: null, + + // nsIAutoCompleteResult + + modelQuery: null, + searchString: null, + searchResult: ACR.RESULT_NOMATCH, + defaultIndex: -1, + errorDescription: null, + + get matchCount() { + return this._searchResults.length; + }, + + getValueAt: function getValueAt(aIndex) { + return this._searchResults[aIndex].value; + }, + + getLabelAt: function getLabelAt(aIndex) { + return this.getValueAt(aIndex); + }, + + getCommentAt: function getCommentAt(aIndex) { + return this._searchResults[aIndex].comment; + }, + + getStyleAt: function getStyleAt(aIndex) { + return "local-abook"; + }, + + getImageAt: function getImageAt(aIndex) { + return ""; + }, + + getFinalCompleteValueAt: function(aIndex) { + return this.getValueAt(aIndex); + }, + + removeValueAt: function removeValueAt(aRowIndex, aRemoveFromDB) { + }, + + // nsIAbAutoCompleteResult + + getCardAt: function getCardAt(aIndex) { + return this._searchResults[aIndex].card; + }, + + getEmailToUse: function getEmailToUse(aIndex) { + return this._searchResults[aIndex].emailToUse; + }, + + // nsISupports + + QueryInterface: XPCOMUtils.generateQI([ACR, nsIAbAutoCompleteResult]) +} + +function nsAbAutoCompleteSearch() {} + +nsAbAutoCompleteSearch.prototype = { + // For component registration + classID: Components.ID("2f946df9-114c-41fe-8899-81f10daf4f0c"), + + // This is set from a preference, + // 0 = no comment column, 1 = name of address book this card came from + // Other numbers currently unused (hence default to zero) + _commentColumn: 0, + _parser: MailServices.headerParser, + _abManager: MailServices.ab, + applicableHeaders: new Set(["addr_to", "addr_cc", "addr_bcc", "addr_reply"]), + + // Private methods + + /** + * Returns the popularity index for a given card. This takes account of a + * translation bug whereby Thunderbird 2 stores its values in mork as + * hexadecimal, and Thunderbird 3 stores as decimal. + * + * @param aDirectory The directory that the card is in. + * @param aCard The card to return the popularity index for. + */ + _getPopularityIndex: function _getPopularityIndex(aDirectory, aCard) { + let popularityValue = aCard.getProperty("PopularityIndex", "0"); + let popularityIndex = parseInt(popularityValue); + + // If we haven't parsed it the first time round, parse it as hexadecimal + // and repair so that we don't have to keep repairing. + if (isNaN(popularityIndex)) { + popularityIndex = parseInt(popularityValue, 16); + + // If its still NaN, just give up, we shouldn't ever get here. + if (isNaN(popularityIndex)) + popularityIndex = 0; + + // Now store this change so that we're not changing it each time around. + if (!aDirectory.readOnly) { + aCard.setProperty("PopularityIndex", popularityIndex); + try { + aDirectory.modifyCard(aCard); + } + catch (ex) { + Components.utils.reportError(ex); + } + } + } + return popularityIndex; + }, + + /** + * Gets the score of the (full) address, given the search input. We want + * results that match the beginning of a "word" in the result to score better + * than a result that matches only in the middle of the word. + * + * @param aCard - the card whose score is being decided + * @param aAddress - full lower-cased address, including display name and address + * @param aSearchString - search string provided by user + * @return a score; a higher score is better than a lower one + */ + _getScore: function(aCard, aAddress, aSearchString) { + const BEST = 100; + + // We will firstly check if the search term provided by the user + // is the nick name for the card or at least in the beginning of it. + let nick = aCard.getProperty("NickName", "").toLocaleLowerCase(); + aSearchString = aSearchString.toLocaleLowerCase(); + if (nick == aSearchString) + return BEST + 1; + if (nick.indexOf(aSearchString) == 0) + return BEST; + + // We'll do this case-insensitively and ignore the domain. + let atIdx = aAddress.lastIndexOf("@"); + if (atIdx != -1) // mail lists don't have an @ + aAddress = aAddress.substr(0, atIdx); + let idx = aAddress.indexOf(aSearchString); + if (idx == 0) + return BEST; + if (idx == -1) + return 0; + + // We want to treat firstname, lastname and word boundary(ish) parts of + // the email address the same. E.g. for "John Doe (:xx) <jd.who@example.com>" + // all of these should score the same: "John", "Doe", "xx", + // ":xx", "jd", "who". + let prevCh = aAddress.charAt(idx - 1); + if (/[ :."'(\-_<&]/.test(prevCh)) + return BEST; + + // The match was inside a word -> we don't care about the position. + return 0; + }, + + /** + * Searches cards in the given directory. If a card is matched (and isn't + * a mailing list) then the function will add a result for each email address + * that exists. + * + * @param searchQuery The boolean search query to use. + * @param directory An nsIAbDirectory to search. + * @param result The result element to append results to. + */ + _searchCards: function(searchQuery, directory, result) { + let childCards; + try { + childCards = this._abManager.getDirectory(directory.URI + searchQuery).childCards; + } catch (e) { + Components.utils.reportError("Error running addressbook query '" + searchQuery + "': " + e); + return; + } + + // Cache this values to save going through xpconnect each time + var commentColumn = this._commentColumn == 1 ? directory.dirName : ""; + + // Now iterate through all the cards. + while (childCards.hasMoreElements()) { + var card = childCards.getNext(); + + if (card instanceof Components.interfaces.nsIAbCard) { + if (card.isMailList) + this._addToResult(commentColumn, directory, card, "", true, result); + else { + let email = card.primaryEmail; + if (email) + this._addToResult(commentColumn, directory, card, email, true, result); + + email = card.getProperty("SecondEmail", ""); + if (email) + this._addToResult(commentColumn, directory, card, email, false, result); + } + } + } + }, + + /** + * Checks the parent card and email address of an autocomplete results entry + * from a previous result against the search parameters to see if that entry + * should still be included in the narrowed-down result. + * + * @param aCard The card to check. + * @param aEmailToUse The email address to check against. + * @param aSearchWords Array of words in the multi word search string. + * @return True if the card matches the search parameters, false + * otherwise. + */ + _checkEntry: function _checkEntry(aCard, aEmailToUse, aSearchWords) { + // Joining values of many fields in a single string so that a single + // search query can be fired on all of them at once. Separating them + // using spaces so that field1=> "abc" and field2=> "def" on joining + // shouldn't return true on search for "bcd". + // Note: This should be constructed from model query pref using + // getModelQuery("mail.addr_book.autocompletequery.format") + // but for now we hard-code the default value equivalent of the pref here + // or else bail out before and reconstruct the full c++ query if the pref + // has been customized (modelQueryHasUserValue), so that we won't get here. + let cumulativeFieldText = aCard.displayName + " " + + aCard.firstName + " " + + aCard.lastName + " " + + aEmailToUse + " " + + aCard.getProperty("NickName", ""); + if (aCard.isMailList) + cumulativeFieldText += " " + aCard.getProperty("Notes", ""); + cumulativeFieldText = cumulativeFieldText.toLocaleLowerCase(); + + return aSearchWords.every(String.prototype.includes, + cumulativeFieldText); + }, + + /** + * Checks to see if an emailAddress (name/address) is a duplicate of an + * existing entry already in the results. If the emailAddress is found, it + * will remove the existing element if the popularity of the new card is + * higher than the previous card. + * + * @param directory The directory that the card is in. + * @param card The card that could be a duplicate. + * @param lcEmailAddress The emailAddress (name/address combination) to check + * for duplicates against. Lowercased. + * @param currentResults The current results list. + */ + _checkDuplicate: function (directory, card, lcEmailAddress, currentResults) { + let existingResult = currentResults._collectedValues.get(lcEmailAddress); + if (!existingResult) + return false; + + let popIndex = this._getPopularityIndex(directory, card); + // It's a duplicate, is the new one more popular? + if (popIndex > existingResult.popularity) { + // Yes it is, so delete this element, return false and allow + // _addToResult to sort the new element into the correct place. + currentResults._collectedValues.delete(lcEmailAddress); + return false; + } + // Not more popular, but still a duplicate. Return true and _addToResult + // will just forget about it. + return true; + }, + + /** + * Adds a card to the results list if it isn't a duplicate. The function will + * order the results by popularity. + * + * @param commentColumn The text to be displayed in the comment column + * (if any). + * @param directory The directory that the card is in. + * @param card The card being added to the results. + * @param emailToUse The email address from the card that should be used + * for this result. + * @param isPrimaryEmail Is the emailToUse the primary email? Set to true if + * it is the case. For mailing lists set it to true. + * @param result The result to add the new entry to. + */ + _addToResult: function(commentColumn, directory, card, + emailToUse, isPrimaryEmail, result) { + let mbox = this._parser.makeMailboxObject(card.displayName, + card.isMailList ? card.getProperty("Notes", "") || card.displayName : + emailToUse); + if (!mbox.email) + return; + + let emailAddress = mbox.toString(); + let lcEmailAddress = emailAddress.toLocaleLowerCase(); + + // If it is a duplicate, then just return and don't add it. The + // _checkDuplicate function deals with it all for us. + if (this._checkDuplicate(directory, card, lcEmailAddress, result)) + return; + + result._collectedValues.set(lcEmailAddress, { + value: emailAddress, + comment: commentColumn, + card: card, + isPrimaryEmail: isPrimaryEmail, + emailToUse: emailToUse, + popularity: this._getPopularityIndex(directory, card), + score: this._getScore(card, lcEmailAddress, result.searchString) + }); + }, + + // nsIAutoCompleteSearch + + /** + * Starts a search based on the given parameters. + * + * @see nsIAutoCompleteSearch for parameter details. + * + * It is expected that aSearchParam contains the identity (if any) to use + * for determining if an address book should be autocompleted against. + */ + startSearch: function startSearch(aSearchString, aSearchParam, + aPreviousResult, aListener) { + let params = aSearchParam ? JSON.parse(aSearchParam) : {}; + var result = new nsAbAutoCompleteResult(aSearchString); + if (("type" in params) && !this.applicableHeaders.has(params.type)) { + result.searchResult = ACR.RESULT_IGNORED; + aListener.onSearchResult(this, result); + return; + } + + let fullString = aSearchString && aSearchString.trim().toLocaleLowerCase(); + + // If the search string is empty, or contains a comma, or the user + // hasn't enabled autocomplete, then just return no matches or the + // result ignored. + // The comma check is so that we don't autocomplete against the user + // entering multiple addresses. + if (!fullString || aSearchString.includes(",")) { + result.searchResult = ACR.RESULT_IGNORED; + aListener.onSearchResult(this, result); + return; + } + + // Array of all the terms from the fullString search query + // (separated on the basis of spaces or exact terms on the + // basis of quotes). + let searchWords = getSearchTokens(fullString); + + // Find out about the comment column + try { + this._commentColumn = Services.prefs.getIntPref("mail.autoComplete.commentColumn"); + } catch(e) { } + + if (aPreviousResult instanceof nsIAbAutoCompleteResult && + aSearchString.startsWith(aPreviousResult.searchString) && + aPreviousResult.searchResult == ACR.RESULT_SUCCESS && + !result._modelQueryHasUserValue && + result.modelQuery == aPreviousResult.modelQuery) { + // We have successful previous matches, and model query has not changed since + // previous search, therefore just iterate through the list of previous result + // entries and reduce as appropriate (via _checkEntry function). + // Test for model query change is required: when reverting back from custom to + // default query, result._modelQueryHasUserValue==false, but we must bail out. + // Todo: However, if autocomplete model query has been customized, we fall + // back to using the full query again instead of reducing result list in js; + // The full query might be less performant as it's fired against entire AB, + // so we should try morphing the query for js. We can't use the _checkEntry + // js query yet because it is hardcoded (mimic default model query). + // At least we now allow users to customize their autocomplete model query... + for (let i = 0; i < aPreviousResult.matchCount; ++i) { + let card = aPreviousResult.getCardAt(i); + let email = aPreviousResult.getEmailToUse(i); + if (this._checkEntry(card, email, searchWords)) { + // Add matches into the results array. We re-sort as needed later. + result._searchResults.push({ + value: aPreviousResult.getValueAt(i), + comment: aPreviousResult.getCommentAt(i), + card: card, + isPrimaryEmail: (card.primaryEmail == email), + emailToUse: email, + popularity: parseInt(card.getProperty("PopularityIndex", "0")), + score: this._getScore(card, + aPreviousResult.getValueAt(i).toLocaleLowerCase(), + fullString) + }); + } + } + } + else + { + // Construct the search query from pref; using a query means we can + // optimise on running the search through c++ which is better for string + // comparisons (_checkEntry is relatively slow). + // When user's fullstring search expression is a multiword query, search + // for each word separately so that each result contains all the words + // from the fullstring in the fields of the addressbook card + // (see bug 558931 for explanations). + // Use helper method to split up search query to multi-word search + // query against multiple fields. + let searchWords = getSearchTokens(fullString); + let searchQuery = generateQueryURI(result.modelQuery, searchWords); + + // Now do the searching + let allABs = this._abManager.directories; + + // We're not going to bother searching sub-directories, currently the + // architecture forces all cards that are in mailing lists to be in ABs as + // well, therefore by searching sub-directories (aka mailing lists) we're + // just going to find duplicates. + while (allABs.hasMoreElements()) { + let dir = allABs.getNext(); + if (dir instanceof Components.interfaces.nsIAbDirectory && + dir.useForAutocomplete(("idKey" in params) ? params.idKey : null)) { + this._searchCards(searchQuery, dir, result); + } + } + + result._searchResults = [...result._collectedValues.values()]; + } + + // Sort the results. Scoring may have changed so do it even if this is + // just filtered previous results. + result._searchResults.sort(function(a, b) { + // Order by 1) descending score, then 2) descending popularity, + // then 3) primary email before secondary for the same card, then + // 4) by emails sorted alphabetically. + return (b.score - a.score) || + (b.popularity - a.popularity) || + ((a.card == b.card && a.isPrimaryEmail) ? -1 : 0) || + a.value.localeCompare(b.value); + }); + + if (result.matchCount) { + result.searchResult = ACR.RESULT_SUCCESS; + result.defaultIndex = 0; + } + + aListener.onSearchResult(this, result); + }, + + stopSearch: function stopSearch() { + }, + + // nsISupports + + QueryInterface: XPCOMUtils.generateQI([Components.interfaces + .nsIAutoCompleteSearch]) +}; + +// Module + +var components = [nsAbAutoCompleteSearch]; +var NSGetFactory = XPCOMUtils.generateNSGetFactory(components); diff --git a/mailnews/addrbook/src/nsAbBSDirectory.cpp b/mailnews/addrbook/src/nsAbBSDirectory.cpp new file mode 100644 index 000000000..0d018bbda --- /dev/null +++ b/mailnews/addrbook/src/nsAbBSDirectory.cpp @@ -0,0 +1,323 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsIPrefService.h" +#include "nsAbBSDirectory.h" + +#include "nsDirPrefs.h" +#include "nsAbBaseCID.h" +#include "nsAddrDatabase.h" +#include "nsIAbManager.h" +#include "nsIAbMDBDirectory.h" +#include "nsServiceManagerUtils.h" +#include "nsAbDirFactoryService.h" +#include "nsAbMDBDirFactory.h" +#include "nsArrayEnumerator.h" + +#include "nsCRTGlue.h" + +nsAbBSDirectory::nsAbBSDirectory() +: mInitialized(false) +, mServers(13) +{ +} + +nsAbBSDirectory::~nsAbBSDirectory() +{ +} + +NS_IMETHODIMP nsAbBSDirectory::Init(const char *aURI) +{ + mURI = aURI; + return NS_OK; +} + +NS_IMPL_ISUPPORTS_INHERITED0(nsAbBSDirectory, nsAbDirProperty) + +nsresult nsAbBSDirectory::CreateDirectoriesFromFactory(const nsACString &aURI, + DIR_Server *aServer, + bool aNotify) +{ + nsresult rv; + + // Get the directory factory service + nsCOMPtr<nsIAbDirFactoryService> dirFactoryService = + do_GetService(NS_ABDIRFACTORYSERVICE_CONTRACTID,&rv); + NS_ENSURE_SUCCESS (rv, rv); + + // Get the directory factory from the URI + nsCOMPtr<nsIAbDirFactory> dirFactory; + rv = dirFactoryService->GetDirFactory(aURI, getter_AddRefs(dirFactory)); + NS_ENSURE_SUCCESS (rv, rv); + + // Create the directories + nsCOMPtr<nsISimpleEnumerator> newDirEnumerator; + rv = dirFactory->GetDirectories(NS_ConvertUTF8toUTF16(aServer->description), + aURI, + nsDependentCString(aServer->prefName), + getter_AddRefs(newDirEnumerator)); + NS_ENSURE_SUCCESS (rv, rv); + + // Enumerate through the directories adding them + // to the sub directories array + bool hasMore; + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + + while (NS_SUCCEEDED(newDirEnumerator->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr<nsISupports> newDirSupports; + rv = newDirEnumerator->GetNext(getter_AddRefs(newDirSupports)); + if(NS_FAILED(rv)) + continue; + + nsCOMPtr<nsIAbDirectory> childDir = do_QueryInterface(newDirSupports, &rv); + if(NS_FAILED(rv)) + continue; + + // Define a relationship between the preference + // entry and the directory + mServers.Put(childDir, aServer); + + mSubDirectories.AppendObject(childDir); + + if (aNotify && abManager) + abManager->NotifyDirectoryItemAdded(this, childDir); + } + + return NS_OK; +} + +NS_IMETHODIMP nsAbBSDirectory::GetChildNodes(nsISimpleEnumerator* *aResult) +{ + nsresult rv = EnsureInitialized(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_NewArrayEnumerator(aResult, mSubDirectories); +} + +nsresult nsAbBSDirectory::EnsureInitialized() +{ + if (mInitialized) + return NS_OK; + + nsresult rv; + nsCOMPtr<nsIAbDirFactoryService> dirFactoryService = + do_GetService(NS_ABDIRFACTORYSERVICE_CONTRACTID,&rv); + NS_ENSURE_SUCCESS (rv, rv); + + nsTArray<DIR_Server*> *directories = DIR_GetDirectories(); + if (!directories) + return NS_ERROR_FAILURE; + + int32_t count = directories->Length(); + for (int32_t i = 0; i < count; i++) + { + DIR_Server *server = directories->ElementAt(i); + + // if this is a 4.x, local .na2 addressbook (PABDirectory) + // we must skip it. + // mozilla can't handle 4.x .na2 addressbooks + // note, the filename might be na2 for 4.x LDAP directories + // (we used the .na2 file for replication), and we don't want to skip + // those. see bug #127007 + uint32_t fileNameLen = strlen(server->fileName); + if (((fileNameLen > kABFileName_PreviousSuffixLen) && + strcmp(server->fileName + fileNameLen - kABFileName_PreviousSuffixLen, + kABFileName_PreviousSuffix) == 0) && + (server->dirType == PABDirectory)) + continue; + + // Set the uri property + nsAutoCString URI (server->uri); + // This is in case the uri is never set + // in the nsDirPref.cpp code. + if (!server->uri) + { + URI = NS_LITERAL_CSTRING(kMDBDirectoryRoot); + URI += nsDependentCString(server->fileName); + } + + /* + * Check that we are not converting from a + * a 4.x address book file e.g. pab.na2 + * check if the URI ends with ".na2" + */ + if (StringEndsWith(URI, NS_LITERAL_CSTRING(kABFileName_PreviousSuffix))) + URI.Replace(kMDBDirectoryRootLen, URI.Length() - kMDBDirectoryRootLen, server->fileName); + + // Create the directories + rv = CreateDirectoriesFromFactory(URI, server, false /* notify */); + + // If we failed, this could be because something has set a pref for us + // which is now broke (e.g. no factory present). So just ignore this one + // and move on. + if (NS_FAILED(rv)) + NS_WARNING("CreateDirectoriesFromFactory failed - Invalid factory?"); + } + + mInitialized = true; + // sort directories by position... + return NS_OK; +} + +NS_IMETHODIMP nsAbBSDirectory::CreateNewDirectory(const nsAString &aDirName, + const nsACString &aURI, + uint32_t aType, + const nsACString &aPrefName, + nsACString &aResult) +{ + nsresult rv = EnsureInitialized(); + NS_ENSURE_SUCCESS(rv, rv); + + /* + * TODO + * This procedure is still MDB specific + * due to the dependence on the current + * nsDirPref.cpp code + */ + + nsCString URI(aURI); + + /* + * The creation of the address book in the preferences + * is very MDB implementation specific. + * If the fileName attribute is null then it will + * create an appropriate file name. + * Somehow have to resolve this issue so that it + * is more general. + * + */ + DIR_Server* server = nullptr; + rv = DIR_AddNewAddressBook(aDirName, EmptyCString(), URI, + (DirectoryType)aType, aPrefName, &server); + NS_ENSURE_SUCCESS (rv, rv); + + if (aType == PABDirectory) { + // Add the URI property + URI.AssignLiteral(kMDBDirectoryRoot); + URI.Append(nsDependentCString(server->fileName)); + } + + aResult.Assign(server->prefName); + + rv = CreateDirectoriesFromFactory(URI, server, true /* notify */); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +NS_IMETHODIMP nsAbBSDirectory::CreateDirectoryByURI(const nsAString &aDisplayName, + const nsACString &aURI) +{ + nsresult rv = EnsureInitialized(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString fileName; + if (StringBeginsWith(aURI, NS_LITERAL_CSTRING(kMDBDirectoryRoot))) + fileName = Substring(aURI, kMDBDirectoryRootLen); + + DIR_Server * server = nullptr; + rv = DIR_AddNewAddressBook(aDisplayName, fileName, aURI, + PABDirectory, EmptyCString(), &server); + NS_ENSURE_SUCCESS(rv,rv); + + rv = CreateDirectoriesFromFactory(aURI, server, true /* notify */); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +NS_IMETHODIMP nsAbBSDirectory::DeleteDirectory(nsIAbDirectory *directory) +{ + NS_ENSURE_ARG_POINTER(directory); + + nsresult rv = EnsureInitialized(); + NS_ENSURE_SUCCESS(rv, rv); + + DIR_Server *server = nullptr; + mServers.Get(directory, &server); + + if (!server) + return NS_ERROR_FAILURE; + + struct GetDirectories + { + GetDirectories(DIR_Server* aServer) : mServer(aServer) { } + + nsCOMArray<nsIAbDirectory> directories; + DIR_Server* mServer; + }; + GetDirectories getDirectories(server); + for (auto iter = mServers.Iter(); !iter.Done(); iter.Next()) { + if (iter.UserData() == getDirectories.mServer) { + nsCOMPtr<nsIAbDirectory> abDir = do_QueryInterface(iter.Key()); + getDirectories.directories.AppendObject(abDir); + } + } + + DIR_DeleteServerFromList(server); + + nsCOMPtr<nsIAbDirFactoryService> dirFactoryService = + do_GetService(NS_ABDIRFACTORYSERVICE_CONTRACTID,&rv); + NS_ENSURE_SUCCESS (rv, rv); + + uint32_t count = getDirectories.directories.Count(); + + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID); + + for (uint32_t i = 0; i < count; i++) { + nsCOMPtr<nsIAbDirectory> d = getDirectories.directories[i]; + + mServers.Remove(d); + mSubDirectories.RemoveObject(d); + + if (abManager) + abManager->NotifyDirectoryDeleted(this, d); + + nsCString uri; + rv = d->GetURI(uri); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirFactory> dirFactory; + rv = dirFactoryService->GetDirFactory(uri, getter_AddRefs(dirFactory)); + if (NS_FAILED(rv)) + continue; + + rv = dirFactory->DeleteDirectory(d); + } + + return rv; +} + +NS_IMETHODIMP nsAbBSDirectory::HasDirectory(nsIAbDirectory *dir, bool *hasDir) +{ + if (!hasDir) + return NS_ERROR_NULL_POINTER; + + nsresult rv = EnsureInitialized(); + NS_ENSURE_SUCCESS(rv, rv); + + DIR_Server *dirServer = nullptr; + mServers.Get(dir, &dirServer); + return DIR_ContainsServer(dirServer, hasDir); +} + +NS_IMETHODIMP nsAbBSDirectory::UseForAutocomplete(const nsACString &aIdentityKey, + bool *aResult) +{ + // For the "root" directory (kAllDirectoryRoot) always return true so that + // we can search sub directories that may or may not be local. + NS_ENSURE_ARG_POINTER(aResult); + *aResult = true; + return NS_OK; +} + +NS_IMETHODIMP nsAbBSDirectory::GetURI(nsACString &aURI) +{ + if (mURI.IsEmpty()) + return NS_ERROR_NOT_INITIALIZED; + + aURI = mURI; + return NS_OK; +} + diff --git a/mailnews/addrbook/src/nsAbBSDirectory.h b/mailnews/addrbook/src/nsAbBSDirectory.h new file mode 100644 index 000000000..bc550dbf5 --- /dev/null +++ b/mailnews/addrbook/src/nsAbBSDirectory.h @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + + +#ifndef nsAbBSDirectory_h__ +#define nsAbBSDirectory_h__ + +#include "mozilla/Attributes.h" +#include "nsAbDirProperty.h" + +#include "nsDataHashtable.h" +#include "nsCOMArray.h" + +class nsAbBSDirectory : public nsAbDirProperty +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + nsAbBSDirectory(); + + // nsIAbDirectory methods + NS_IMETHOD Init(const char *aURI) override; + NS_IMETHOD GetChildNodes(nsISimpleEnumerator* *result) override; + NS_IMETHOD CreateNewDirectory(const nsAString &aDirName, + const nsACString &aURI, + uint32_t aType, + const nsACString &aPrefName, + nsACString &aResult) override; + NS_IMETHOD CreateDirectoryByURI(const nsAString &aDisplayName, + const nsACString &aURI) override; + NS_IMETHOD DeleteDirectory(nsIAbDirectory *directory) override; + NS_IMETHOD HasDirectory(nsIAbDirectory *dir, bool *hasDir) override; + NS_IMETHOD UseForAutocomplete(const nsACString &aIdentityKey, bool *aResult) override; + NS_IMETHOD GetURI(nsACString &aURI) override; + +protected: + virtual ~nsAbBSDirectory(); + nsresult EnsureInitialized(); + nsresult CreateDirectoriesFromFactory(const nsACString &aURI, + DIR_Server* aServer, bool aNotify); + +protected: + bool mInitialized; + nsCOMArray<nsIAbDirectory> mSubDirectories; + nsDataHashtable<nsISupportsHashKey, DIR_Server*> mServers; +}; + +#endif diff --git a/mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.cpp b/mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.cpp new file mode 100644 index 000000000..679ee792d --- /dev/null +++ b/mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.cpp @@ -0,0 +1,246 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#include "nsIAbLDAPAttributeMap.h" +#include "nsAbBoolExprToLDAPFilter.h" +#include "nsStringGlue.h" +#include "nsIArray.h" +#include "nsArrayUtils.h" + +const int nsAbBoolExprToLDAPFilter::TRANSLATE_CARD_PROPERTY = 1 << 0 ; +const int nsAbBoolExprToLDAPFilter::ALLOW_NON_CONVERTABLE_CARD_PROPERTY = 1 << 1 ; + +nsresult nsAbBoolExprToLDAPFilter::Convert ( + nsIAbLDAPAttributeMap* map, + nsIAbBooleanExpression* expression, + nsCString& filter, + int flags) +{ + nsCString f; + nsresult rv = FilterExpression (map, expression, f, flags); + NS_ENSURE_SUCCESS(rv, rv); + + filter = f; + return rv; +} + +nsresult nsAbBoolExprToLDAPFilter::FilterExpression ( + nsIAbLDAPAttributeMap* map, + nsIAbBooleanExpression* expression, + nsCString& filter, + int flags) +{ + nsCOMPtr<nsIArray> childExpressions; + nsresult rv = expression->GetExpressions(getter_AddRefs(childExpressions)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t count; + rv = childExpressions->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + + if (count == 0) + return NS_OK; + + nsAbBooleanOperationType operation; + rv = expression->GetOperation(&operation); + NS_ENSURE_SUCCESS(rv, rv); + + /* + * 3rd party query integration with Mozilla is achieved + * by calling nsAbLDAPDirectoryQuery::DoQuery(). Thus + * we can arrive here with a query asking for all the + * ldap attributes using the card:nsIAbCard interface. + * + * So we need to check that we are not creating a condition + * filter against this expression otherwise we will end up with an invalid + * filter equal to "(|)". + */ + + if (count == 1 ) + { + nsCOMPtr<nsIAbBooleanConditionString> + childCondition(do_QueryElementAt(childExpressions, 1, &rv)); + if (NS_SUCCEEDED(rv)) + { + nsCString name; + rv = childCondition->GetName (getter_Copies (name)); + NS_ENSURE_SUCCESS(rv, rv); + + if(name.Equals("card:nsIAbCard")) + return NS_OK; + } + } + + filter.AppendLiteral("("); + switch (operation) + { + case nsIAbBooleanOperationTypes::AND: + filter.AppendLiteral("&"); + rv = FilterExpressions (map, childExpressions, filter, flags); + break; + case nsIAbBooleanOperationTypes::OR: + filter.AppendLiteral("|"); + rv = FilterExpressions (map, childExpressions, filter, flags); + break; + case nsIAbBooleanOperationTypes::NOT: + if (count > 1) + return NS_ERROR_FAILURE; + filter.AppendLiteral("!"); + rv = FilterExpressions (map, childExpressions, filter, flags); + break; + default: + break; + } + filter.AppendLiteral(")"); + + return rv; +} + +nsresult nsAbBoolExprToLDAPFilter::FilterExpressions ( + nsIAbLDAPAttributeMap *map, + nsIArray* expressions, + nsCString& filter, + int flags) +{ + uint32_t count; + nsresult rv = expressions->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbBooleanConditionString> childCondition; + nsCOMPtr<nsIAbBooleanExpression> childExpression; + for (uint32_t i = 0; i < count; i++) + { + childCondition = do_QueryElementAt(expressions, i, &rv); + if (NS_SUCCEEDED(rv)) + { + rv = FilterCondition (map, childCondition, filter, flags); + NS_ENSURE_SUCCESS(rv, rv); + continue; + } + + childExpression = do_QueryElementAt(expressions, i, &rv); + if (NS_SUCCEEDED(rv)) + { + rv = FilterExpression (map, childExpression, filter, flags); + NS_ENSURE_SUCCESS(rv, rv); + continue; + } + } + + return rv; +} + +nsresult nsAbBoolExprToLDAPFilter::FilterCondition ( + nsIAbLDAPAttributeMap* map, + nsIAbBooleanConditionString* condition, + nsCString& filter, + int flags) +{ + nsCString name; + nsresult rv = condition->GetName(getter_Copies (name)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString ldapAttr(name); + if (flags & TRANSLATE_CARD_PROPERTY) + { + rv = map->GetFirstAttribute (name, ldapAttr); + if (!(flags & ALLOW_NON_CONVERTABLE_CARD_PROPERTY) && + !ATTRMAP_FOUND_ATTR(rv, ldapAttr)) + return NS_OK; + } + + nsAbBooleanConditionType conditionType; + rv = condition->GetCondition(&conditionType); + NS_ENSURE_SUCCESS(rv, rv); + + nsString value; + rv = condition->GetValue (getter_Copies (value)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ConvertUTF16toUTF8 vUTF8 (value); + + switch (conditionType) + { + case nsIAbBooleanConditionTypes::DoesNotExist: + filter.AppendLiteral("(!("); + filter.Append(ldapAttr); + filter.AppendLiteral("=*))"); + break; + case nsIAbBooleanConditionTypes::Exists: + filter.AppendLiteral("("); + filter.Append(ldapAttr); + filter.AppendLiteral("=*)"); + break; + case nsIAbBooleanConditionTypes::Contains: + filter.AppendLiteral("("); + filter.Append(ldapAttr); + filter.Append("=*"); + filter.Append(vUTF8); + filter.AppendLiteral("*)"); + break; + case nsIAbBooleanConditionTypes::DoesNotContain: + filter.AppendLiteral("(!("); + filter.Append(ldapAttr); + filter.AppendLiteral("=*"); + filter.Append(vUTF8); + filter.AppendLiteral("*))"); + break; + case nsIAbBooleanConditionTypes::Is: + filter.AppendLiteral("("); + filter.Append(ldapAttr); + filter.AppendLiteral("="); + filter.Append(vUTF8); + filter.AppendLiteral(")"); + break; + case nsIAbBooleanConditionTypes::IsNot: + filter.AppendLiteral("(!("); + filter.Append(ldapAttr); + filter.AppendLiteral("="); + filter.Append(vUTF8); + filter.AppendLiteral("))"); + break; + case nsIAbBooleanConditionTypes::BeginsWith: + filter.AppendLiteral("("); + filter.Append(ldapAttr); + filter.AppendLiteral("="); + filter.Append(vUTF8); + filter.AppendLiteral("*)"); + break; + case nsIAbBooleanConditionTypes::EndsWith: + filter.AppendLiteral("("); + filter.Append(ldapAttr); + filter.AppendLiteral("=*"); + filter.Append(vUTF8); + filter.AppendLiteral(")"); + break; + case nsIAbBooleanConditionTypes::LessThan: + filter.AppendLiteral("("); + filter.Append(ldapAttr); + filter.AppendLiteral("<="); + filter.Append(vUTF8); + filter.AppendLiteral(")"); + break; + case nsIAbBooleanConditionTypes::GreaterThan: + filter.AppendLiteral("("); + filter.Append(ldapAttr); + filter.AppendLiteral(">="); + filter.Append(vUTF8); + filter.AppendLiteral(")"); + break; + case nsIAbBooleanConditionTypes::SoundsLike: + filter.AppendLiteral("("); + filter.Append(ldapAttr); + filter.AppendLiteral("~="); + filter.Append(vUTF8); + filter.AppendLiteral(")"); + break; + case nsIAbBooleanConditionTypes::RegExp: + break; + default: + break; + } + + return rv; +} + diff --git a/mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.h b/mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.h new file mode 100644 index 000000000..5ea892595 --- /dev/null +++ b/mailnews/addrbook/src/nsAbBoolExprToLDAPFilter.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsBooleanExpressionToLDAPFilter_h__ +#define nsBooleanExpressionToLDAPFilter_h__ + +#include "nsIAbBooleanExpression.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" + +class nsIAbLDAPAttributeMap; + +class nsAbBoolExprToLDAPFilter +{ +public: + static const int TRANSLATE_CARD_PROPERTY ; + static const int ALLOW_NON_CONVERTABLE_CARD_PROPERTY ; + + static nsresult Convert ( + nsIAbLDAPAttributeMap* map, + nsIAbBooleanExpression* expression, + nsCString& filter, + int flags = TRANSLATE_CARD_PROPERTY); + +protected: + static nsresult FilterExpression ( + nsIAbLDAPAttributeMap* map, + nsIAbBooleanExpression* expression, + nsCString& filter, + int flags); + static nsresult FilterExpressions ( + nsIAbLDAPAttributeMap* map, + nsIArray* expressions, + nsCString& filter, + int flags); + static nsresult FilterCondition ( + nsIAbLDAPAttributeMap* map, + nsIAbBooleanConditionString* condition, + nsCString& filter, + int flags); +}; + +#endif diff --git a/mailnews/addrbook/src/nsAbBooleanExpression.cpp b/mailnews/addrbook/src/nsAbBooleanExpression.cpp new file mode 100644 index 000000000..a1a39c1fa --- /dev/null +++ b/mailnews/addrbook/src/nsAbBooleanExpression.cpp @@ -0,0 +1,132 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#include "nsAbBooleanExpression.h" +#include "nsComponentManagerUtils.h" + +NS_IMPL_ISUPPORTS(nsAbBooleanConditionString, nsIAbBooleanConditionString) + +nsAbBooleanConditionString::nsAbBooleanConditionString() : + mCondition (nsIAbBooleanConditionTypes::Exists) +{ +} + +nsAbBooleanConditionString::~nsAbBooleanConditionString() +{ +} + +/* attribute nsAbBooleanConditionType condition; */ +NS_IMETHODIMP nsAbBooleanConditionString::GetCondition(nsAbBooleanConditionType *aCondition) +{ + if (!aCondition) + return NS_ERROR_NULL_POINTER; + + *aCondition = mCondition; + + return NS_OK; +} +NS_IMETHODIMP nsAbBooleanConditionString::SetCondition(nsAbBooleanConditionType aCondition) +{ + mCondition = aCondition; + + return NS_OK; +} + +/* attribute string name; */ +NS_IMETHODIMP nsAbBooleanConditionString::GetName(char** aName) +{ + if (!aName) + return NS_ERROR_NULL_POINTER; + + *aName = mName.IsEmpty() ? 0 : ToNewCString(mName); + + return NS_OK; + +} +NS_IMETHODIMP nsAbBooleanConditionString::SetName(const char* aName) +{ + if (!aName) + return NS_ERROR_NULL_POINTER; + + mName = aName; + + return NS_OK; +} + +/* attribute wstring value; */ +NS_IMETHODIMP nsAbBooleanConditionString::GetValue(char16_t** aValue) +{ + if (!aValue) + return NS_ERROR_NULL_POINTER; + + *aValue = ToNewUnicode(mValue); + + return NS_OK; +} +NS_IMETHODIMP nsAbBooleanConditionString::SetValue(const char16_t * aValue) +{ + if (!aValue) + return NS_ERROR_NULL_POINTER; + + mValue = aValue; + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsAbBooleanExpression, nsIAbBooleanExpression) + +nsAbBooleanExpression::nsAbBooleanExpression() : + mOperation (nsIAbBooleanOperationTypes::AND) +{ +} + +nsAbBooleanExpression::~nsAbBooleanExpression() +{ +} + +/* attribute nsAbBooleanOperationType operation; */ +NS_IMETHODIMP nsAbBooleanExpression::GetOperation(nsAbBooleanOperationType *aOperation) +{ + if (!aOperation) + return NS_ERROR_NULL_POINTER; + + *aOperation = mOperation; + + return NS_OK; +} +NS_IMETHODIMP nsAbBooleanExpression::SetOperation(nsAbBooleanOperationType aOperation) +{ + mOperation = aOperation; + + return NS_OK; +} + +/* attribute nsIArray expressions; */ +NS_IMETHODIMP nsAbBooleanExpression::GetExpressions(nsIArray **aExpressions) +{ + if (!aExpressions) + return NS_ERROR_NULL_POINTER; + + if (!mExpressions) + { + mExpressions = do_CreateInstance(NS_ARRAY_CONTRACTID); + + if (!mExpressions) + return NS_ERROR_OUT_OF_MEMORY; + } + + NS_ADDREF(*aExpressions = mExpressions); + return NS_OK; +} + +NS_IMETHODIMP nsAbBooleanExpression::SetExpressions(nsIArray *aExpressions) +{ + if (!aExpressions) + return NS_ERROR_NULL_POINTER; + + mExpressions = aExpressions; + + return NS_OK; +} diff --git a/mailnews/addrbook/src/nsAbBooleanExpression.h b/mailnews/addrbook/src/nsAbBooleanExpression.h new file mode 100644 index 000000000..697caf5f5 --- /dev/null +++ b/mailnews/addrbook/src/nsAbBooleanExpression.h @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsAbBooleanExpression_h__ +#define nsAbBooleanExpression_h__ + +#include "nsIAbBooleanExpression.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" +#include "nsIArray.h" + +class nsAbBooleanConditionString : public nsIAbBooleanConditionString +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIABBOOLEANCONDITIONSTRING + + nsAbBooleanConditionString(); + +protected: + virtual ~nsAbBooleanConditionString(); + nsAbBooleanConditionType mCondition; + nsCString mName; + nsString mValue; +}; + +class nsAbBooleanExpression: public nsIAbBooleanExpression +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIABBOOLEANEXPRESSION + + nsAbBooleanExpression(); + +protected: + virtual ~nsAbBooleanExpression(); + nsAbBooleanOperationType mOperation; + nsCOMPtr<nsIArray> mExpressions; +}; + +#endif diff --git a/mailnews/addrbook/src/nsAbCardProperty.cpp b/mailnews/addrbook/src/nsAbCardProperty.cpp new file mode 100644 index 000000000..2c40a4034 --- /dev/null +++ b/mailnews/addrbook/src/nsAbCardProperty.cpp @@ -0,0 +1,1193 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbCardProperty.h" +#include "nsAbBaseCID.h" +#include "nsIPrefService.h" +#include "nsIAddrDatabase.h" +#include "plbase64.h" +#include "nsIStringBundle.h" +#include "plstr.h" +#include "nsMsgUtils.h" +#include "nsINetUtil.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsMemory.h" +#include "nsVCardObj.h" +#include "nsIMutableArray.h" +#include "nsArrayUtils.h" +#include "mozITXTToHTMLConv.h" +#include "nsIAbManager.h" + +#include "nsVariant.h" +#include "nsIProperty.h" +#include "nsCOMArray.h" +#include "nsArrayEnumerator.h" +#include "prmem.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Services.h" +using namespace mozilla; + +#define PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST "mail.addr_book.lastnamefirst" + +const char sAddrbookProperties[] = "chrome://messenger/locale/addressbook/addressBook.properties"; + +enum EAppendType { + eAppendLine, + eAppendLabel, + eAppendCityStateZip +}; + +struct AppendItem { + const char *mColumn; + const char* mLabel; + EAppendType mAppendType; +}; + +static const AppendItem NAME_ATTRS_ARRAY[] = { + {kDisplayNameProperty, "propertyDisplayName", eAppendLabel}, + {kNicknameProperty, "propertyNickname", eAppendLabel}, + {kPriEmailProperty, "", eAppendLine}, +#ifndef MOZ_THUNDERBIRD + {k2ndEmailProperty, "", eAppendLine}, + {kScreenNameProperty, "propertyScreenName", eAppendLabel} +#else + {k2ndEmailProperty, "", eAppendLine} +#endif +}; + +static const AppendItem PHONE_ATTRS_ARRAY[] = { + {kWorkPhoneProperty, "propertyWork", eAppendLabel}, + {kHomePhoneProperty, "propertyHome", eAppendLabel}, + {kFaxProperty, "propertyFax", eAppendLabel}, + {kPagerProperty, "propertyPager", eAppendLabel}, + {kCellularProperty, "propertyCellular", eAppendLabel} +}; + +static const AppendItem HOME_ATTRS_ARRAY[] = { + {kHomeAddressProperty, "", eAppendLine}, + {kHomeAddress2Property, "", eAppendLine}, + {kHomeCityProperty, "", eAppendCityStateZip}, + {kHomeCountryProperty, "", eAppendLine}, + {kHomeWebPageProperty, "", eAppendLine} +}; + +static const AppendItem WORK_ATTRS_ARRAY[] = { + {kJobTitleProperty, "", eAppendLine}, + {kDepartmentProperty, "", eAppendLine}, + {kCompanyProperty, "", eAppendLine}, + {kWorkAddressProperty, "", eAppendLine}, + {kWorkAddress2Property, "", eAppendLine}, + {kWorkCityProperty, "", eAppendCityStateZip}, + {kWorkCountryProperty, "", eAppendLine}, + {kWorkWebPageProperty, "", eAppendLine} +}; + +static const AppendItem CUSTOM_ATTRS_ARRAY[] = { + {kCustom1Property, "propertyCustom1", eAppendLabel}, + {kCustom2Property, "propertyCustom2", eAppendLabel}, + {kCustom3Property, "propertyCustom3", eAppendLabel}, + {kCustom4Property, "propertyCustom4", eAppendLabel}, + {kNotesProperty, "", eAppendLine} +}; + +#ifdef MOZ_THUNDERBIRD + +static const AppendItem CHAT_ATTRS_ARRAY[] = { + {kGtalkProperty, "propertyGtalk", eAppendLabel}, + {kAIMProperty, "propertyAIM", eAppendLabel}, + {kYahooProperty, "propertyYahoo", eAppendLabel}, + {kSkypeProperty, "propertySkype", eAppendLabel}, + {kQQProperty, "propertyQQ", eAppendLabel}, + {kMSNProperty, "propertyMSN", eAppendLabel}, + {kICQProperty, "propertyICQ", eAppendLabel}, + {kXMPPProperty, "propertyXMPP", eAppendLabel}, + {kIRCProperty, "propertyIRC", eAppendLabel} +}; +#endif + +nsAbCardProperty::nsAbCardProperty() + : m_IsMailList(false) +{ + // Initialize some default properties + SetPropertyAsUint32(kPreferMailFormatProperty, nsIAbPreferMailFormat::unknown); + SetPropertyAsUint32(kPopularityIndexProperty, 0); + // Uninitialized... + SetPropertyAsUint32(kLastModifiedDateProperty, 0); +} + +nsAbCardProperty::~nsAbCardProperty(void) +{ +} + +NS_IMPL_ISUPPORTS(nsAbCardProperty, nsIAbCard, nsIAbItem) + +NS_IMETHODIMP nsAbCardProperty::GetUuid(nsACString &uuid) +{ + // If we have indeterminate sub-ids, return an empty uuid. + if (m_directoryId.Equals("") || m_localId.Equals("")) + { + uuid.Truncate(); + return NS_OK; + } + + nsresult rv; + nsCOMPtr<nsIAbManager> manager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return manager->GenerateUUID(m_directoryId, m_localId, uuid); +} + +NS_IMETHODIMP nsAbCardProperty::GetDirectoryId(nsACString &dirId) +{ + dirId = m_directoryId; + return NS_OK; +} + +NS_IMETHODIMP nsAbCardProperty::SetDirectoryId(const nsACString &aDirId) +{ + m_directoryId = aDirId; + return NS_OK; +} + +NS_IMETHODIMP nsAbCardProperty::GetLocalId(nsACString &localId) +{ + localId = m_localId; + return NS_OK; +} + +NS_IMETHODIMP nsAbCardProperty::SetLocalId(const nsACString &aLocalId) +{ + m_localId = aLocalId; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP nsAbCardProperty::GetIsMailList(bool *aIsMailList) +{ + *aIsMailList = m_IsMailList; + return NS_OK; +} + +NS_IMETHODIMP nsAbCardProperty::SetIsMailList(bool aIsMailList) +{ + m_IsMailList = aIsMailList; + return NS_OK; +} + +NS_IMETHODIMP nsAbCardProperty::GetMailListURI(char **aMailListURI) +{ + if (aMailListURI) + { + *aMailListURI = ToNewCString(m_MailListURI); + return (*aMailListURI) ? NS_OK : NS_ERROR_OUT_OF_MEMORY; + } + else + return NS_ERROR_NULL_POINTER; +} + +NS_IMETHODIMP nsAbCardProperty::SetMailListURI(const char *aMailListURI) +{ + if (aMailListURI) + { + m_MailListURI = aMailListURI; + return NS_OK; + } + else + return NS_ERROR_NULL_POINTER; +} + +/////////////////////////////////////////////////////////////////////////////// +// Property bag portion of nsAbCardProperty +/////////////////////////////////////////////////////////////////////////////// + +class nsAbSimpleProperty final : public nsIProperty { +public: + nsAbSimpleProperty(const nsACString& aName, nsIVariant* aValue) + : mName(aName), mValue(aValue) + { + } + + NS_DECL_ISUPPORTS + NS_DECL_NSIPROPERTY +protected: + ~nsAbSimpleProperty() {} + nsCString mName; + nsCOMPtr<nsIVariant> mValue; +}; + +NS_IMPL_ISUPPORTS(nsAbSimpleProperty, nsIProperty) + +NS_IMETHODIMP +nsAbSimpleProperty::GetName(nsAString& aName) +{ + aName.Assign(NS_ConvertUTF8toUTF16(mName)); + return NS_OK; +} + +NS_IMETHODIMP +nsAbSimpleProperty::GetValue(nsIVariant* *aValue) +{ + NS_IF_ADDREF(*aValue = mValue); + return NS_OK; +} + +NS_IMETHODIMP nsAbCardProperty::GetProperties(nsISimpleEnumerator **props) +{ + nsCOMArray<nsIProperty> propertyArray(m_properties.Count()); + for (auto iter = m_properties.Iter(); !iter.Done(); iter.Next()) { + propertyArray.AppendObject(new nsAbSimpleProperty(iter.Key(), + iter.UserData())); + } + return NS_NewArrayEnumerator(props, propertyArray); +} + +NS_IMETHODIMP nsAbCardProperty::GetProperty(const nsACString &name, + nsIVariant *defaultValue, + nsIVariant **value) +{ + if (!m_properties.Get(name, value)) + NS_ADDREF(*value = defaultValue); + return NS_OK; +} + +NS_IMETHODIMP nsAbCardProperty::GetPropertyAsAString(const char *name, nsAString &value) +{ + NS_ENSURE_ARG_POINTER(name); + + nsCOMPtr<nsIVariant> variant; + return m_properties.Get(nsDependentCString(name), getter_AddRefs(variant)) ? + variant->GetAsAString(value) : NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP nsAbCardProperty::GetPropertyAsAUTF8String(const char *name, nsACString &value) +{ + NS_ENSURE_ARG_POINTER(name); + + nsCOMPtr<nsIVariant> variant; + return m_properties.Get(nsDependentCString(name), getter_AddRefs(variant)) ? + variant->GetAsAUTF8String(value) : NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP nsAbCardProperty::GetPropertyAsUint32(const char *name, uint32_t *value) +{ + NS_ENSURE_ARG_POINTER(name); + + nsCOMPtr<nsIVariant> variant; + return m_properties.Get(nsDependentCString(name), getter_AddRefs(variant)) ? + variant->GetAsUint32(value) : NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP nsAbCardProperty::GetPropertyAsBool(const char *name, bool *value) +{ + NS_ENSURE_ARG_POINTER(name); + + nsCOMPtr<nsIVariant> variant; + return m_properties.Get(nsDependentCString(name), getter_AddRefs(variant)) ? + variant->GetAsBool(value) : NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP nsAbCardProperty::SetProperty(const nsACString &name, nsIVariant *value) +{ + m_properties.Put(name, value); + return NS_OK; +} + +NS_IMETHODIMP nsAbCardProperty::SetPropertyAsAString(const char *name, const nsAString &value) +{ + NS_ENSURE_ARG_POINTER(name); + + nsCOMPtr<nsIWritableVariant> variant = new nsVariant(); + variant->SetAsAString(value); + m_properties.Put(nsDependentCString(name), variant); + return NS_OK; +} + +NS_IMETHODIMP nsAbCardProperty::SetPropertyAsAUTF8String(const char *name, const nsACString &value) +{ + NS_ENSURE_ARG_POINTER(name); + + nsCOMPtr<nsIWritableVariant> variant = new nsVariant(); + variant->SetAsAUTF8String(value); + m_properties.Put(nsDependentCString(name), variant); + return NS_OK; +} + +NS_IMETHODIMP nsAbCardProperty::SetPropertyAsUint32(const char *name, uint32_t value) +{ + NS_ENSURE_ARG_POINTER(name); + + nsCOMPtr<nsIWritableVariant> variant = new nsVariant(); + variant->SetAsUint32(value); + m_properties.Put(nsDependentCString(name), variant); + return NS_OK; +} + +NS_IMETHODIMP nsAbCardProperty::SetPropertyAsBool(const char *name, bool value) +{ + NS_ENSURE_ARG_POINTER(name); + + nsCOMPtr<nsIWritableVariant> variant = new nsVariant(); + variant->SetAsBool(value); + m_properties.Put(nsDependentCString(name), variant); + return NS_OK; +} + +NS_IMETHODIMP nsAbCardProperty::DeleteProperty(const nsACString &name) +{ + m_properties.Remove(name); + return NS_OK; +} + +NS_IMETHODIMP nsAbCardProperty::GetFirstName(nsAString &aString) +{ + nsresult rv = GetPropertyAsAString(kFirstNameProperty, aString); + if (rv == NS_ERROR_NOT_AVAILABLE) + { + aString.Truncate(); + return NS_OK; + } + return rv; +} + +NS_IMETHODIMP nsAbCardProperty::SetFirstName(const nsAString &aString) +{ + return SetPropertyAsAString(kFirstNameProperty, aString); +} + +NS_IMETHODIMP nsAbCardProperty::GetLastName(nsAString &aString) +{ + nsresult rv = GetPropertyAsAString(kLastNameProperty, aString); + if (rv == NS_ERROR_NOT_AVAILABLE) + { + aString.Truncate(); + return NS_OK; + } + return rv; +} + +NS_IMETHODIMP nsAbCardProperty::SetLastName(const nsAString &aString) +{ + return SetPropertyAsAString(kLastNameProperty, aString); +} + +NS_IMETHODIMP nsAbCardProperty::GetDisplayName(nsAString &aString) +{ + nsresult rv = GetPropertyAsAString(kDisplayNameProperty, aString); + if (rv == NS_ERROR_NOT_AVAILABLE) + { + aString.Truncate(); + return NS_OK; + } + return rv; +} + +NS_IMETHODIMP nsAbCardProperty::SetDisplayName(const nsAString &aString) +{ + return SetPropertyAsAString(kDisplayNameProperty, aString); +} + +NS_IMETHODIMP nsAbCardProperty::GetPrimaryEmail(nsAString &aString) +{ + nsresult rv = GetPropertyAsAString(kPriEmailProperty, aString); + if (rv == NS_ERROR_NOT_AVAILABLE) + { + aString.Truncate(); + return NS_OK; + } + return rv; +} + +NS_IMETHODIMP nsAbCardProperty::SetPrimaryEmail(const nsAString &aString) +{ + return SetPropertyAsAString(kPriEmailProperty, aString); +} + +NS_IMETHODIMP nsAbCardProperty::HasEmailAddress(const nsACString &aEmailAddress, + bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = false; + + nsCString emailAddress; + nsresult rv = GetPropertyAsAUTF8String(kPriEmailProperty, emailAddress); + if (rv != NS_ERROR_NOT_AVAILABLE && + emailAddress.Equals(aEmailAddress, nsCaseInsensitiveCStringComparator())) + { + *aResult = true; + return NS_OK; + } + + rv = GetPropertyAsAUTF8String(k2ndEmailProperty, emailAddress); + if (rv != NS_ERROR_NOT_AVAILABLE && + emailAddress.Equals(aEmailAddress, nsCaseInsensitiveCStringComparator())) + *aResult = true; + + return NS_OK; +} + +// This function may be overridden by derived classes for +// nsAb*Card specific implementations. +NS_IMETHODIMP nsAbCardProperty::Copy(nsIAbCard* srcCard) +{ + NS_ENSURE_ARG_POINTER(srcCard); + + nsCOMPtr<nsISimpleEnumerator> properties; + nsresult rv = srcCard->GetProperties(getter_AddRefs(properties)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + nsCOMPtr<nsISupports> result; + while (NS_SUCCEEDED(rv = properties->HasMoreElements(&hasMore)) && hasMore) + { + rv = properties->GetNext(getter_AddRefs(result)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIProperty> property = do_QueryInterface(result, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString name; + property->GetName(name); + nsCOMPtr<nsIVariant> value; + property->GetValue(getter_AddRefs(value)); + + SetProperty(NS_ConvertUTF16toUTF8(name), value); + } + NS_ENSURE_SUCCESS(rv, rv); + + bool isMailList; + srcCard->GetIsMailList(&isMailList); + SetIsMailList(isMailList); + + nsCString mailListURI; + srcCard->GetMailListURI(getter_Copies(mailListURI)); + SetMailListURI(mailListURI.get()); + + return NS_OK; +} + +NS_IMETHODIMP nsAbCardProperty::Equals(nsIAbCard *card, bool *result) +{ + *result = (card == this); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////// +// The following methods are other views of a card +//////////////////////////////////////////////////////////////////////////////// + +// XXX: Use the category manager instead of this file to implement these +NS_IMETHODIMP nsAbCardProperty::TranslateTo(const nsACString &type, nsACString &result) +{ + if (type.EqualsLiteral("base64xml")) + return ConvertToBase64EncodedXML(result); + else if (type.EqualsLiteral("xml")) + { + nsString utf16String; + nsresult rv = ConvertToXMLPrintData(utf16String); + NS_ENSURE_SUCCESS(rv, rv); + result = NS_ConvertUTF16toUTF8(utf16String); + return NS_OK; + } + else if (type.EqualsLiteral("vcard")) + return ConvertToEscapedVCard(result); + + return NS_ERROR_ILLEGAL_VALUE; +} +// +static VObject* myAddPropValue(VObject *o, const char *propName, const char16_t *propValue, bool *aCardHasData) +{ + if (aCardHasData) + *aCardHasData = true; + return addPropValue(o, propName, NS_ConvertUTF16toUTF8(propValue).get()); +} + +nsresult nsAbCardProperty::ConvertToEscapedVCard(nsACString &aResult) +{ + nsString str; + nsresult rv; + bool vCardHasData = false; + VObject* vObj = newVObject(VCCardProp); + VObject* t; + + // [comment from 4.x] + // Big flame coming....so Vobject is not designed at all to work with an array of + // attribute values. It wants you to have all of the attributes easily available. You + // cannot add one attribute at a time as you find them to the vobject. Why? Because + // it creates a property for a particular type like phone number and then that property + // has multiple values. This implementation is not pretty. I can hear my algos prof + // yelling from here.....I have to do a linear search through my attributes array for + // EACH vcard property we want to set. *sigh* One day I will have time to come back + // to this function and remedy this O(m*n) function where n = # attribute values and + // m = # of vcard properties.... + + (void)GetDisplayName(str); + if (!str.IsEmpty()) { + myAddPropValue(vObj, VCFullNameProp, str.get(), &vCardHasData); + } + + (void)GetLastName(str); + if (!str.IsEmpty()) { + t = isAPropertyOf(vObj, VCNameProp); + if (!t) + t = addProp(vObj, VCNameProp); + myAddPropValue(t, VCFamilyNameProp, str.get(), &vCardHasData); + } + + (void)GetFirstName(str); + if (!str.IsEmpty()) { + t = isAPropertyOf(vObj, VCNameProp); + if (!t) + t = addProp(vObj, VCNameProp); + myAddPropValue(t, VCGivenNameProp, str.get(), &vCardHasData); + } + + rv = GetPropertyAsAString(kCompanyProperty, str); + if (NS_SUCCEEDED(rv) && !str.IsEmpty()) + { + t = isAPropertyOf(vObj, VCOrgProp); + if (!t) + t = addProp(vObj, VCOrgProp); + myAddPropValue(t, VCOrgNameProp, str.get(), &vCardHasData); + } + + rv = GetPropertyAsAString(kDepartmentProperty, str); + if (NS_SUCCEEDED(rv) && !str.IsEmpty()) + { + t = isAPropertyOf(vObj, VCOrgProp); + if (!t) + t = addProp(vObj, VCOrgProp); + myAddPropValue(t, VCOrgUnitProp, str.get(), &vCardHasData); + } + + rv = GetPropertyAsAString(kWorkAddress2Property, str); + if (NS_SUCCEEDED(rv) && !str.IsEmpty()) + { + t = isAPropertyOf(vObj, VCAdrProp); + if (!t) + t = addProp(vObj, VCAdrProp); + myAddPropValue(t, VCPostalBoxProp, str.get(), &vCardHasData); + } + + rv = GetPropertyAsAString(kWorkAddressProperty, str); + if (NS_SUCCEEDED(rv) && !str.IsEmpty()) + { + t = isAPropertyOf(vObj, VCAdrProp); + if (!t) + t = addProp(vObj, VCAdrProp); + myAddPropValue(t, VCStreetAddressProp, str.get(), &vCardHasData); + } + + rv = GetPropertyAsAString(kWorkCityProperty, str); + if (NS_SUCCEEDED(rv) && !str.IsEmpty()) + { + t = isAPropertyOf(vObj, VCAdrProp); + if (!t) + t = addProp(vObj, VCAdrProp); + myAddPropValue(t, VCCityProp, str.get(), &vCardHasData); + } + + rv = GetPropertyAsAString(kWorkStateProperty, str); + if (NS_SUCCEEDED(rv) && !str.IsEmpty()) + { + t = isAPropertyOf(vObj, VCAdrProp); + if (!t) + t = addProp(vObj, VCAdrProp); + myAddPropValue(t, VCRegionProp, str.get(), &vCardHasData); + } + + rv = GetPropertyAsAString(kWorkZipCodeProperty, str); + if (NS_SUCCEEDED(rv) && !str.IsEmpty()) + { + t = isAPropertyOf(vObj, VCAdrProp); + if (!t) + t = addProp(vObj, VCAdrProp); + myAddPropValue(t, VCPostalCodeProp, str.get(), &vCardHasData); + } + + rv = GetPropertyAsAString(kWorkCountryProperty, str); + if (NS_SUCCEEDED(rv) && !str.IsEmpty()) + { + t = isAPropertyOf(vObj, VCAdrProp); + if (!t) + t = addProp(vObj, VCAdrProp); + myAddPropValue(t, VCCountryNameProp, str.get(), &vCardHasData); + } + else + { + // only add this if VCAdrProp already exists + t = isAPropertyOf(vObj, VCAdrProp); + if (t) + { + addProp(t, VCDomesticProp); + } + } + + (void)GetPrimaryEmail(str); + if (!str.IsEmpty()) + { + t = myAddPropValue(vObj, VCEmailAddressProp, str.get(), &vCardHasData); + addProp(t, VCInternetProp); + } + + rv = GetPropertyAsAString(kJobTitleProperty, str); + if (NS_SUCCEEDED(rv) && !str.IsEmpty()) + { + myAddPropValue(vObj, VCTitleProp, str.get(), &vCardHasData); + } + + rv = GetPropertyAsAString(kWorkPhoneProperty, str); + if (NS_SUCCEEDED(rv) && !str.IsEmpty()) + { + t = myAddPropValue(vObj, VCTelephoneProp, str.get(), &vCardHasData); + addProp(t, VCWorkProp); + } + + rv = GetPropertyAsAString(kFaxProperty, str); + if (NS_SUCCEEDED(rv) && !str.IsEmpty()) + { + t = myAddPropValue(vObj, VCTelephoneProp, str.get(), &vCardHasData); + addProp(t, VCFaxProp); + } + + rv = GetPropertyAsAString(kPagerProperty, str); + if (NS_SUCCEEDED(rv) && !str.IsEmpty()) + { + t = myAddPropValue(vObj, VCTelephoneProp, str.get(), &vCardHasData); + addProp(t, VCPagerProp); + } + + rv = GetPropertyAsAString(kHomePhoneProperty, str); + if (NS_SUCCEEDED(rv) && !str.IsEmpty()) + { + t = myAddPropValue(vObj, VCTelephoneProp, str.get(), &vCardHasData); + addProp(t, VCHomeProp); + } + + rv = GetPropertyAsAString(kCellularProperty, str); + if (NS_SUCCEEDED(rv) && !str.IsEmpty()) + { + t = myAddPropValue(vObj, VCTelephoneProp, str.get(), &vCardHasData); + addProp(t, VCCellularProp); + } + + rv = GetPropertyAsAString(kNotesProperty, str); + if (NS_SUCCEEDED(rv) && !str.IsEmpty()) + { + myAddPropValue(vObj, VCNoteProp, str.get(), &vCardHasData); + } + + uint32_t format; + rv = GetPropertyAsUint32(kPreferMailFormatProperty, &format); + if (NS_SUCCEEDED(rv) && format == nsIAbPreferMailFormat::html) { + myAddPropValue(vObj, VCUseHTML, u"TRUE", &vCardHasData); + } + else if (NS_SUCCEEDED(rv) && format == nsIAbPreferMailFormat::plaintext) { + myAddPropValue(vObj, VCUseHTML, u"FALSE", &vCardHasData); + } + + rv = GetPropertyAsAString(kWorkWebPageProperty, str); + if (NS_SUCCEEDED(rv) && !str.IsEmpty()) + { + myAddPropValue(vObj, VCURLProp, str.get(), &vCardHasData); + } + + myAddPropValue(vObj, VCVersionProp, u"2.1", nullptr); + + if (!vCardHasData) { + aResult.Truncate(); + cleanVObject(vObj); + return NS_OK; + } + + int len = 0; + char *vCard = writeMemVObject(0, &len, vObj); + if (vObj) + cleanVObject(vObj); + + nsCString escResult; + MsgEscapeString(nsDependentCString(vCard), nsINetUtil::ESCAPE_URL_PATH, escResult); + aResult = escResult; + return NS_OK; +} + +nsresult nsAbCardProperty::ConvertToBase64EncodedXML(nsACString &result) +{ + nsresult rv; + nsString xmlStr; + + xmlStr.AppendLiteral("<?xml version=\"1.0\"?>\n" + "<?xml-stylesheet type=\"text/css\" href=\"chrome://messagebody/content/addressbook/print.css\"?>\n" + "<directory>\n"); + + // Get Address Book string and set it as title of XML document + nsCOMPtr<nsIStringBundle> bundle; + nsCOMPtr<nsIStringBundleService> stringBundleService = + mozilla::services::GetStringBundleService(); + if (stringBundleService) { + rv = stringBundleService->CreateBundle(sAddrbookProperties, getter_AddRefs(bundle)); + if (NS_SUCCEEDED(rv)) { + nsString addrBook; + rv = bundle->GetStringFromName(u"addressBook", getter_Copies(addrBook)); + if (NS_SUCCEEDED(rv)) { + xmlStr.AppendLiteral("<title xmlns=\"http://www.w3.org/1999/xhtml\">"); + xmlStr.Append(addrBook); + xmlStr.AppendLiteral("</title>\n"); + } + } + } + + nsString xmlSubstr; + rv = ConvertToXMLPrintData(xmlSubstr); + NS_ENSURE_SUCCESS(rv,rv); + + xmlStr.Append(xmlSubstr); + xmlStr.AppendLiteral("</directory>\n"); + + char *tmpRes = PL_Base64Encode(NS_ConvertUTF16toUTF8(xmlStr).get(), 0, nullptr); + result.Assign(tmpRes); + PR_Free(tmpRes); + return NS_OK; +} + +nsresult nsAbCardProperty::ConvertToXMLPrintData(nsAString &aXMLSubstr) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + int32_t generatedNameFormat; + rv = prefBranch->GetIntPref(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, &generatedNameFormat); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIStringBundleService> stringBundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(stringBundleService, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> bundle; + rv = stringBundleService->CreateBundle(sAddrbookProperties, getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv,rv); + + nsString generatedName; + rv = GenerateName(generatedNameFormat, bundle, generatedName); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<mozITXTToHTMLConv> conv = do_CreateInstance(MOZ_TXTTOHTMLCONV_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsString xmlStr; + xmlStr.SetLength(4096); // to reduce allocations. should be enough for most cards + xmlStr.AssignLiteral("<GeneratedName>\n"); + + // use ScanTXT to convert < > & to safe values. + nsString safeText; + if (!generatedName.IsEmpty()) { + rv = conv->ScanTXT(generatedName.get(), mozITXTToHTMLConv::kEntities, + getter_Copies(safeText)); + NS_ENSURE_SUCCESS(rv,rv); + } + + if (safeText.IsEmpty()) { + nsAutoString primaryEmail; + GetPrimaryEmail(primaryEmail); + + // use ScanTXT to convert < > & to safe values. + rv = conv->ScanTXT(primaryEmail.get(), mozITXTToHTMLConv::kEntities, + getter_Copies(safeText)); + NS_ENSURE_SUCCESS(rv,rv); + } + xmlStr.Append(safeText); + + xmlStr.AppendLiteral("</GeneratedName>\n" + "<table><tr><td>"); + + rv = AppendSection(NAME_ATTRS_ARRAY, sizeof(NAME_ATTRS_ARRAY)/sizeof(AppendItem), EmptyString(), bundle, conv, xmlStr); + + xmlStr.AppendLiteral("</td></tr><tr><td>"); + + rv = AppendSection(PHONE_ATTRS_ARRAY, sizeof(PHONE_ATTRS_ARRAY)/sizeof(AppendItem), NS_LITERAL_STRING("headingPhone"), bundle, conv, xmlStr); + + if (!m_IsMailList) { + rv = AppendSection(CUSTOM_ATTRS_ARRAY, sizeof(CUSTOM_ATTRS_ARRAY)/sizeof(AppendItem), NS_LITERAL_STRING("headingOther"), bundle, conv, xmlStr); +#ifdef MOZ_THUNDERBIRD + rv = AppendSection(CHAT_ATTRS_ARRAY, sizeof(CHAT_ATTRS_ARRAY)/sizeof(AppendItem), NS_LITERAL_STRING("headingChat"), bundle, conv, xmlStr); +#endif + } + else { + rv = AppendSection(CUSTOM_ATTRS_ARRAY, sizeof(CUSTOM_ATTRS_ARRAY)/sizeof(AppendItem), NS_LITERAL_STRING("headingDescription"), + bundle, conv, xmlStr); + + xmlStr.AppendLiteral("<section><sectiontitle>"); + + nsString headingAddresses; + rv = bundle->GetStringFromName(u"headingAddresses", getter_Copies(headingAddresses)); + NS_ENSURE_SUCCESS(rv, rv); + + xmlStr.Append(headingAddresses); + xmlStr.AppendLiteral("</sectiontitle>"); + + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIAbDirectory> mailList = nullptr; + rv = abManager->GetDirectory(m_MailListURI, getter_AddRefs(mailList)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> addresses; + rv = mailList->GetAddressLists(getter_AddRefs(addresses)); + if (addresses) { + uint32_t total = 0; + addresses->GetLength(&total); + if (total) { + uint32_t i; + nsAutoString displayName; + nsAutoString primaryEmail; + for (i = 0; i < total; i++) { + nsCOMPtr <nsIAbCard> listCard = do_QueryElementAt(addresses, i, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + xmlStr.AppendLiteral("<PrimaryEmail>\n"); + + rv = listCard->GetDisplayName(displayName); + NS_ENSURE_SUCCESS(rv,rv); + + // use ScanTXT to convert < > & to safe values. + nsString safeText; + rv = conv->ScanTXT(displayName.get(), mozITXTToHTMLConv::kEntities, + getter_Copies(safeText)); + NS_ENSURE_SUCCESS(rv,rv); + xmlStr.Append(safeText); + + xmlStr.AppendLiteral(" <"); + + listCard->GetPrimaryEmail(primaryEmail); + + // use ScanTXT to convert < > & to safe values. + rv = conv->ScanTXT(primaryEmail.get(), mozITXTToHTMLConv::kEntities, + getter_Copies(safeText)); + NS_ENSURE_SUCCESS(rv,rv); + xmlStr.Append(safeText); + + xmlStr.AppendLiteral("></PrimaryEmail>\n"); + } + } + } + xmlStr.AppendLiteral("</section>"); + } + + xmlStr.AppendLiteral("</td><td>"); + + rv = AppendSection(HOME_ATTRS_ARRAY, sizeof(HOME_ATTRS_ARRAY)/sizeof(AppendItem), NS_LITERAL_STRING("headingHome"), bundle, conv, xmlStr); + rv = AppendSection(WORK_ATTRS_ARRAY, sizeof(WORK_ATTRS_ARRAY)/sizeof(AppendItem), NS_LITERAL_STRING("headingWork"), bundle, conv, xmlStr); + + xmlStr.AppendLiteral("</td></tr></table>"); + + aXMLSubstr = xmlStr; + + return NS_OK; +} + +nsresult nsAbCardProperty::AppendSection(const AppendItem *aArray, int16_t aCount, const nsString& aHeading, + nsIStringBundle *aBundle, + mozITXTToHTMLConv *aConv, + nsString &aResult) +{ + nsresult rv = NS_OK; + + aResult.AppendLiteral("<section>"); + + nsString attrValue; + bool sectionIsEmpty = true; + + int16_t i = 0; + for (i=0;i<aCount;i++) { + rv = GetPropertyAsAString(aArray[i].mColumn, attrValue); + if (NS_SUCCEEDED(rv) && !attrValue.IsEmpty()) + sectionIsEmpty = false; + } + + if (!sectionIsEmpty && !aHeading.IsEmpty()) { + nsString heading; + rv = aBundle->GetStringFromName(aHeading.get(), getter_Copies(heading)); + NS_ENSURE_SUCCESS(rv, rv); + + aResult.AppendLiteral("<sectiontitle>"); + aResult.Append(heading); + aResult.AppendLiteral("</sectiontitle>"); + } + + for (i=0;i<aCount;i++) { + switch (aArray[i].mAppendType) { + case eAppendLine: + rv = AppendLine(aArray[i], aConv, aResult); + break; + case eAppendLabel: + rv = AppendLabel(aArray[i], aBundle, aConv, aResult); + break; + case eAppendCityStateZip: + rv = AppendCityStateZip(aArray[i], aBundle, aConv, aResult); + break; + default: + rv = NS_ERROR_FAILURE; + break; + } + + if (NS_FAILED(rv)) { + NS_WARNING("append item failed"); + break; + } + } + aResult.AppendLiteral("</section>"); + + return rv; +} + +nsresult nsAbCardProperty::AppendLine(const AppendItem &aItem, + mozITXTToHTMLConv *aConv, + nsString &aResult) +{ + NS_ENSURE_ARG_POINTER(aConv); + + nsString attrValue; + nsresult rv = GetPropertyAsAString(aItem.mColumn, attrValue); + + if (NS_FAILED(rv) || attrValue.IsEmpty()) + return NS_OK; + + aResult.Append(char16_t('<')); + aResult.Append(NS_ConvertUTF8toUTF16(aItem.mColumn)); + aResult.Append(char16_t('>')); + + // use ScanTXT to convert < > & to safe values. + nsString safeText; + rv = aConv->ScanTXT(attrValue.get(), mozITXTToHTMLConv::kEntities, getter_Copies(safeText)); + NS_ENSURE_SUCCESS(rv,rv); + aResult.Append(safeText); + + aResult.AppendLiteral("</"); + aResult.Append(NS_ConvertUTF8toUTF16(aItem.mColumn)); + aResult.Append(char16_t('>')); + + return NS_OK; +} + +nsresult nsAbCardProperty::AppendLabel(const AppendItem &aItem, + nsIStringBundle *aBundle, + mozITXTToHTMLConv *aConv, + nsString &aResult) +{ + NS_ENSURE_ARG_POINTER(aBundle); + + nsresult rv; + nsString label, attrValue; + + rv = GetPropertyAsAString(aItem.mColumn, attrValue); + + if (NS_FAILED(rv) || attrValue.IsEmpty()) + return NS_OK; + + rv = aBundle->GetStringFromName(NS_ConvertUTF8toUTF16(aItem.mLabel).get(), getter_Copies(label)); + NS_ENSURE_SUCCESS(rv, rv); + + aResult.AppendLiteral("<labelrow><label>"); + + aResult.Append(label); + aResult.AppendLiteral(": </label>"); + + rv = AppendLine(aItem, aConv, aResult); + NS_ENSURE_SUCCESS(rv,rv); + + aResult.AppendLiteral("</labelrow>"); + + return NS_OK; +} + +nsresult nsAbCardProperty::AppendCityStateZip(const AppendItem &aItem, + nsIStringBundle *aBundle, + mozITXTToHTMLConv *aConv, + nsString &aResult) +{ + NS_ENSURE_ARG_POINTER(aBundle); + + nsresult rv; + AppendItem item; + const char *statePropName, *zipPropName; + + if (strcmp(aItem.mColumn, kHomeCityProperty) == 0) { + statePropName = kHomeStateProperty; + zipPropName = kHomeZipCodeProperty; + } + else { + statePropName = kWorkStateProperty; + zipPropName = kWorkZipCodeProperty; + } + + nsAutoString cityResult, stateResult, zipResult; + + rv = AppendLine(aItem, aConv, cityResult); + NS_ENSURE_SUCCESS(rv,rv); + + item.mColumn = statePropName; + item.mLabel = ""; + + rv = AppendLine(item, aConv, stateResult); + NS_ENSURE_SUCCESS(rv,rv); + + item.mColumn = zipPropName; + + rv = AppendLine(item, aConv, zipResult); + NS_ENSURE_SUCCESS(rv,rv); + + nsString formattedString; + + if (!cityResult.IsEmpty() && !stateResult.IsEmpty() && !zipResult.IsEmpty()) { + const char16_t *formatStrings[] = { cityResult.get(), stateResult.get(), zipResult.get() }; + rv = aBundle->FormatStringFromName(u"cityAndStateAndZip", formatStrings, ArrayLength(formatStrings), getter_Copies(formattedString)); + NS_ENSURE_SUCCESS(rv,rv); + } + else if (!cityResult.IsEmpty() && !stateResult.IsEmpty() && zipResult.IsEmpty()) { + const char16_t *formatStrings[] = { cityResult.get(), stateResult.get() }; + rv = aBundle->FormatStringFromName(u"cityAndStateNoZip", formatStrings, ArrayLength(formatStrings), getter_Copies(formattedString)); + NS_ENSURE_SUCCESS(rv,rv); + } + else if ((!cityResult.IsEmpty() && stateResult.IsEmpty() && !zipResult.IsEmpty()) || + (cityResult.IsEmpty() && !stateResult.IsEmpty() && !zipResult.IsEmpty())) { + const char16_t *formatStrings[] = { cityResult.IsEmpty() ? stateResult.get() : cityResult.get(), zipResult.get() }; + rv = aBundle->FormatStringFromName(u"cityOrStateAndZip", formatStrings, ArrayLength(formatStrings), getter_Copies(formattedString)); + NS_ENSURE_SUCCESS(rv,rv); + } + else { + if (!cityResult.IsEmpty()) + formattedString = cityResult; + else if (!stateResult.IsEmpty()) + formattedString = stateResult; + else + formattedString = zipResult; + } + + aResult.Append(formattedString); + + return NS_OK; +} + +NS_IMETHODIMP nsAbCardProperty::GenerateName(int32_t aGenerateFormat, + nsIStringBundle* aBundle, + nsAString &aResult) +{ + aResult.Truncate(); + + // Cache the first and last names + nsAutoString firstName, lastName; + GetFirstName(firstName); + GetLastName(lastName); + + // No need to check for aBundle present straight away, only do that if we're + // actually going to use it. + if (aGenerateFormat == GENERATE_DISPLAY_NAME) + GetDisplayName(aResult); + else if (lastName.IsEmpty()) + aResult = firstName; + else if (firstName.IsEmpty()) + aResult = lastName; + else { + nsresult rv; + nsCOMPtr<nsIStringBundle> bundle(aBundle); + if (!bundle) { + nsCOMPtr<nsIStringBundleService> stringBundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(stringBundleService, NS_ERROR_UNEXPECTED); + + rv = stringBundleService->CreateBundle(sAddrbookProperties, + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsString result; + + if (aGenerateFormat == GENERATE_LAST_FIRST_ORDER) { + const char16_t *stringParams[2] = {lastName.get(), firstName.get()}; + + rv = bundle->FormatStringFromName(u"lastFirstFormat", + stringParams, 2, getter_Copies(result)); + } + else { + const char16_t *stringParams[2] = {firstName.get(), lastName.get()}; + + rv = bundle->FormatStringFromName(u"firstLastFormat", + stringParams, 2, getter_Copies(result)); + } + NS_ENSURE_SUCCESS(rv, rv); + + aResult.Assign(result); + } + + if (aResult.IsEmpty()) + { + // The normal names have failed, does this card have a company name? If so, + // use that instead, because that is likely to be more meaningful than an + // email address. + // + // If this errors, the string isn't found and we'll fall into the next + // check. + (void) GetPropertyAsAString(kCompanyProperty, aResult); + } + + if (aResult.IsEmpty()) + { + // see bug #211078 + // if there is no generated name at this point + // use the userid from the email address + // it is better than nothing. + GetPrimaryEmail(aResult); + int32_t index = aResult.FindChar('@'); + if (index != -1) + aResult.SetLength(index); + } + + return NS_OK; +} + +NS_IMETHODIMP nsAbCardProperty::GeneratePhoneticName(bool aLastNameFirst, + nsAString &aResult) +{ + nsAutoString firstName, lastName; + GetPropertyAsAString(kPhoneticFirstNameProperty, firstName); + GetPropertyAsAString(kPhoneticLastNameProperty, lastName); + + if (aLastNameFirst) + { + aResult = lastName; + aResult += firstName; + } + else + { + aResult = firstName; + aResult += lastName; + } + + return NS_OK; +} + +NS_IMETHODIMP nsAbCardProperty::GenerateChatName(nsAString &aResult) +{ + aResult.Truncate(); + +#define CHECK_CHAT_PROPERTY(aProtocol) \ + if (NS_SUCCEEDED(GetPropertyAsAString(k##aProtocol##Property, aResult)) && \ + !aResult.IsEmpty()) \ + return NS_OK + CHECK_CHAT_PROPERTY(Gtalk); + CHECK_CHAT_PROPERTY(AIM); + CHECK_CHAT_PROPERTY(Yahoo); + CHECK_CHAT_PROPERTY(Skype); + CHECK_CHAT_PROPERTY(QQ); + CHECK_CHAT_PROPERTY(MSN); + CHECK_CHAT_PROPERTY(ICQ); + CHECK_CHAT_PROPERTY(XMPP); + CHECK_CHAT_PROPERTY(IRC); + return NS_OK; +} diff --git a/mailnews/addrbook/src/nsAbCardProperty.h b/mailnews/addrbook/src/nsAbCardProperty.h new file mode 100644 index 000000000..46ba50079 --- /dev/null +++ b/mailnews/addrbook/src/nsAbCardProperty.h @@ -0,0 +1,59 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/******************************************************************************************************** + + Interface for representing Address Book Person Card Property + +*********************************************************************************************************/ + +#ifndef nsAbCardProperty_h__ +#define nsAbCardProperty_h__ + +#include "nsIAbCard.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" + +#include "nsInterfaceHashtable.h" +#include "nsIVariant.h" + +class nsIStringBundle; +class mozITXTToHTMLConv; +struct AppendItem; + + /* + * Address Book Card Property + */ + +class nsAbCardProperty: public nsIAbCard +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIABCARD + NS_DECL_NSIABITEM + + nsAbCardProperty(); + +protected: + virtual ~nsAbCardProperty(); + bool m_IsMailList; + nsCString m_MailListURI; + + // Store most of the properties here + nsInterfaceHashtable<nsCStringHashKey, nsIVariant> m_properties; + + nsCString m_directoryId, m_localId; +private: + nsresult AppendSection(const AppendItem *aArray, int16_t aCount, const nsString& aHeading, nsIStringBundle *aBundle, mozITXTToHTMLConv *aConv, nsString &aResult); + nsresult AppendLine(const AppendItem &aItem, mozITXTToHTMLConv *aConv, nsString &aResult); + nsresult AppendLabel(const AppendItem &aItem, nsIStringBundle *aBundle, mozITXTToHTMLConv *aConv, nsString &aResult); + nsresult AppendCityStateZip(const AppendItem &aItem, nsIStringBundle *aBundle, mozITXTToHTMLConv *aConv, nsString &aResult); + + nsresult ConvertToBase64EncodedXML(nsACString &result); + nsresult ConvertToXMLPrintData(nsAString &result); + nsresult ConvertToEscapedVCard(nsACString &result); +}; + +#endif diff --git a/mailnews/addrbook/src/nsAbContentHandler.cpp b/mailnews/addrbook/src/nsAbContentHandler.cpp new file mode 100644 index 000000000..6e283d82b --- /dev/null +++ b/mailnews/addrbook/src/nsAbContentHandler.cpp @@ -0,0 +1,184 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbContentHandler.h" +#include "nsAbBaseCID.h" +#include "nsNetUtil.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsNullPrincipal.h" +#include "nsISupportsPrimitives.h" +#include "plstr.h" +#include "nsPIDOMWindow.h" +#include "mozIDOMWindow.h" +#include "nsMsgUtils.h" +#include "nsIMsgVCardService.h" +#include "nsIAbCard.h" +#include "nsIAbManager.h" +#include "nsVCard.h" +#include "nsIChannel.h" +// +// nsAbContentHandler +// +nsAbContentHandler::nsAbContentHandler() +{ +} + +nsAbContentHandler::~nsAbContentHandler() +{ +} + +NS_IMPL_ISUPPORTS(nsAbContentHandler, nsIContentHandler, + nsIStreamLoaderObserver) + +NS_IMETHODIMP +nsAbContentHandler::HandleContent(const char *aContentType, + nsIInterfaceRequestor *aWindowContext, + nsIRequest *request) +{ + NS_ENSURE_ARG_POINTER(request); + + nsresult rv = NS_OK; + + // First of all, get the content type and make sure it is a content type we know how to handle! + if (PL_strcasecmp(aContentType, "application/x-addvcard") == 0) { + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIChannel> aChannel = do_QueryInterface(request); + if (!aChannel) return NS_ERROR_FAILURE; + + rv = aChannel->GetURI(getter_AddRefs(uri)); + if (uri) + { + nsAutoCString path; + rv = uri->GetPath(path); + NS_ENSURE_SUCCESS(rv,rv); + + const char *startOfVCard = strstr(path.get(), "add?vcard="); + if (startOfVCard) + { + nsCString unescapedData; + + // XXX todo, explain why we is escaped twice + MsgUnescapeString(nsDependentCString(startOfVCard + strlen("add?vcard=")), + 0, unescapedData); + + if (!aWindowContext) + return NS_ERROR_FAILURE; + + nsCOMPtr<mozIDOMWindowProxy> domWindow = do_GetInterface(aWindowContext); + NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); + nsCOMPtr<nsPIDOMWindowOuter> parentWindow = nsPIDOMWindowOuter::From(domWindow); + parentWindow = parentWindow->GetOuterWindow(); + NS_ENSURE_ARG_POINTER(parentWindow); + + nsCOMPtr<nsIAbManager> ab = + do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIAbCard> cardFromVCard; + rv = ab->EscapedVCardToAbCard(unescapedData.get(), + getter_AddRefs(cardFromVCard)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsInterfacePointer> ifptr = + do_CreateInstance(NS_SUPPORTS_INTERFACE_POINTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + ifptr->SetData(cardFromVCard); + ifptr->SetDataIID(&NS_GET_IID(nsIAbCard)); + + nsCOMPtr<nsPIDOMWindowOuter> dialogWindow; + + rv = parentWindow->OpenDialog( + NS_LITERAL_STRING("chrome://messenger/content/addressbook/abNewCardDialog.xul"), + EmptyString(), + NS_LITERAL_STRING("chrome,resizable=no,titlebar,modal,centerscreen"), + ifptr, getter_AddRefs(dialogWindow)); + NS_ENSURE_SUCCESS(rv, rv); + } + rv = NS_OK; + } + } + else if (PL_strcasecmp(aContentType, "text/x-vcard") == 0) { + // create a vcard stream listener that can parse the data stream + // and bring up the appropriate UI + + // (1) cancel the current load operation. We'll restart it + request->Cancel(NS_ERROR_ABORT); + // get the url we were trying to open + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIChannel> channel = do_QueryInterface(request); + NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE); + + rv = channel->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrincipal> nullPrincipal = + do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // create a stream loader to handle the v-card data + nsCOMPtr<nsIStreamLoader> streamLoader; + rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), + uri, + this, + nullPrincipal, + nsILoadInfo::SEC_NORMAL, + nsIContentPolicy::TYPE_OTHER); + NS_ENSURE_SUCCESS(rv, rv); + + } + else // The content-type was not application/x-addvcard... + return NS_ERROR_WONT_HANDLE_CONTENT; + + return rv; +} + +NS_IMETHODIMP +nsAbContentHandler::OnStreamComplete(nsIStreamLoader *aLoader, + nsISupports *aContext, nsresult aStatus, + uint32_t datalen, const uint8_t *data) +{ + NS_ENSURE_ARG_POINTER(aContext); + NS_ENSURE_SUCCESS(aStatus, aStatus); // don't process the vcard if we got a status error + nsresult rv = NS_OK; + + // take our vCard string and open up an address book window based on it + nsCOMPtr<nsIMsgVCardService> vCardService = do_GetService(NS_MSGVCARDSERVICE_CONTRACTID); + if (vCardService) + { + nsAutoPtr<VObject> vObj(vCardService->Parse_MIME((const char *)data, datalen)); + if (vObj) + { + int32_t len = 0; + nsCString vCard; + vCard.Adopt(vCardService->WriteMemoryVObjects(0, &len, vObj, false)); + + nsCOMPtr<nsIAbManager> ab = + do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIAbCard> cardFromVCard; + rv = ab->EscapedVCardToAbCard(vCard.get(), + getter_AddRefs(cardFromVCard)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<mozIDOMWindowProxy> domWindow = do_GetInterface(aContext); + NS_ENSURE_TRUE(domWindow, NS_ERROR_FAILURE); + nsCOMPtr<nsPIDOMWindowOuter> parentWindow = nsPIDOMWindowOuter::From(domWindow); + parentWindow = parentWindow->GetOuterWindow(); + NS_ENSURE_ARG_POINTER(parentWindow); + + nsCOMPtr<nsPIDOMWindowOuter> dialogWindow; + rv = parentWindow->OpenDialog( + NS_LITERAL_STRING("chrome://messenger/content/addressbook/abNewCardDialog.xul"), + EmptyString(), + NS_LITERAL_STRING("chrome,resizable=no,titlebar,modal,centerscreen"), + cardFromVCard, getter_AddRefs(dialogWindow)); + } + } + + return rv; +} diff --git a/mailnews/addrbook/src/nsAbContentHandler.h b/mailnews/addrbook/src/nsAbContentHandler.h new file mode 100644 index 000000000..e5405ae0e --- /dev/null +++ b/mailnews/addrbook/src/nsAbContentHandler.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef __nsAbContentHandler_h +#define __nsAbContentHandler_h + +#include "nsIStreamLoader.h" +#include "nsIContentHandler.h" + +class nsAbContentHandler : public nsIContentHandler, + public nsIStreamLoaderObserver +{ +public: + nsAbContentHandler(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICONTENTHANDLER + NS_DECL_NSISTREAMLOADEROBSERVER + +private: + virtual ~nsAbContentHandler(); +}; + +#endif diff --git a/mailnews/addrbook/src/nsAbDirFactoryService.cpp b/mailnews/addrbook/src/nsAbDirFactoryService.cpp new file mode 100644 index 000000000..4ddf8e635 --- /dev/null +++ b/mailnews/addrbook/src/nsAbDirFactoryService.cpp @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIIOService.h" +#include "nsNetCID.h" +#include "nsMemory.h" +#include "nsStringGlue.h" +#include "plstr.h" + +#include "nsAbBaseCID.h" +#include "nsAbDirFactoryService.h" +#include "nsIAbDirFactory.h" +#include "mozilla/Services.h" + +NS_IMPL_ISUPPORTS(nsAbDirFactoryService, nsIAbDirFactoryService) + +nsAbDirFactoryService::nsAbDirFactoryService() +{ +} + +nsAbDirFactoryService::~nsAbDirFactoryService() +{ +} + +/* nsIAbDirFactory getDirFactory (in string uri); */ +NS_IMETHODIMP +nsAbDirFactoryService::GetDirFactory(const nsACString &aURI, + nsIAbDirFactory** aDirFactory) +{ + NS_ENSURE_ARG_POINTER(aDirFactory); + + nsresult rv; + + // Obtain the network IO service + nsCOMPtr<nsIIOService> nsService = + mozilla::services::GetIOService(); + NS_ENSURE_TRUE(nsService, NS_ERROR_UNEXPECTED); + + // Extract the scheme + nsAutoCString scheme; + rv = nsService->ExtractScheme(aURI, scheme); + NS_ENSURE_SUCCESS(rv, rv); + + // Try to find a factory using the component manager. + nsAutoCString contractID; + contractID.AssignLiteral(NS_AB_DIRECTORY_FACTORY_CONTRACTID_PREFIX); + contractID.Append(scheme); + + return CallCreateInstance(contractID.get(), aDirFactory); +} diff --git a/mailnews/addrbook/src/nsAbDirFactoryService.h b/mailnews/addrbook/src/nsAbDirFactoryService.h new file mode 100644 index 000000000..77396fbaa --- /dev/null +++ b/mailnews/addrbook/src/nsAbDirFactoryService.h @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsAbDirFactoryService_h__ +#define nsAbDirFactoryService_h__ + +#include "nsIAbDirFactoryService.h" + +class nsAbDirFactoryService : public nsIAbDirFactoryService +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIABDIRFACTORYSERVICE + + nsAbDirFactoryService(); + +private: + virtual ~nsAbDirFactoryService(); +}; + +#endif diff --git a/mailnews/addrbook/src/nsAbDirProperty.cpp b/mailnews/addrbook/src/nsAbDirProperty.cpp new file mode 100644 index 000000000..fbae06220 --- /dev/null +++ b/mailnews/addrbook/src/nsAbDirProperty.cpp @@ -0,0 +1,593 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbDirProperty.h" +#include "nsAbBaseCID.h" +#include "nsIAbCard.h" +#include "nsDirPrefs.h" +#include "nsIPrefService.h" +#include "nsIPrefLocalizedString.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "prmem.h" +#include "nsIAbManager.h" +#include "nsArrayUtils.h" + +// From nsDirPrefs +#define kDefaultPosition 1 + +nsAbDirProperty::nsAbDirProperty(void) + : m_LastModifiedDate(0), + mIsValidURI(false), + mIsQueryURI(false) +{ + m_IsMailList = false; +} + +nsAbDirProperty::~nsAbDirProperty(void) +{ +#if 0 + // this code causes a regression #138647 + // don't turn it on until you figure it out + if (m_AddressList) { + uint32_t count; + nsresult rv; + rv = m_AddressList->GetLength(&count); + NS_ASSERTION(NS_SUCCEEDED(rv), "Count failed"); + int32_t i; + for (i = count - 1; i >= 0; i--) + m_AddressList->RemoveElementAt(i); + } +#endif +} + +NS_IMPL_ISUPPORTS(nsAbDirProperty, nsIAbDirectory, nsISupportsWeakReference, + nsIAbCollection, nsIAbItem) + +NS_IMETHODIMP nsAbDirProperty::GetUuid(nsACString &uuid) +{ + // XXX: not all directories have a dirPrefId... + nsresult rv = GetDirPrefId(uuid); + NS_ENSURE_SUCCESS(rv, rv); + uuid.Append('&'); + nsString dirName; + GetDirName(dirName); + uuid.Append(NS_ConvertUTF16toUTF8(dirName)); + return rv; +} + +NS_IMETHODIMP nsAbDirProperty::GenerateName(int32_t aGenerateFormat, + nsIStringBundle *aBundle, + nsAString &name) +{ + return GetDirName(name); +} + +NS_IMETHODIMP nsAbDirProperty::GetPropertiesChromeURI(nsACString &aResult) +{ + aResult.AssignLiteral("chrome://messenger/content/addressbook/abAddressBookNameDialog.xul"); + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::GetDirName(nsAString &aDirName) +{ + if (m_DirPrefId.IsEmpty()) + { + aDirName = m_ListDirName; + return NS_OK; + } + + nsCString dirName; + nsresult rv = GetLocalizedStringValue("description", EmptyCString(), dirName); + NS_ENSURE_SUCCESS(rv, rv); + + // In TB 2 only some prefs had chrome:// URIs. We had code in place that would + // only get the localized string pref for the particular address books that + // were built-in. + // Additionally, nsIPrefBranch::getComplexValue will only get a non-user-set, + // non-locked pref value if it is a chrome:// URI and will get the string + // value at that chrome URI. This breaks extensions/autoconfig that want to + // set default pref values and allow users to change directory names. + // + // Now we have to support this, and so if for whatever reason we fail to get + // the localized version, then we try and get the non-localized version + // instead. If the string value is empty, then we'll just get the empty value + // back here. + if (dirName.IsEmpty()) + { + rv = GetStringValue("description", EmptyCString(), dirName); + NS_ENSURE_SUCCESS(rv, rv); + } + + CopyUTF8toUTF16(dirName, aDirName); + return NS_OK; +} + +// XXX Although mailing lists could use the NotifyItemPropertyChanged +// mechanism here, it requires some rework on how we write/save data +// relating to mailing lists, so we're just using the old method of a +// local variable to store the mailing list name. +NS_IMETHODIMP nsAbDirProperty::SetDirName(const nsAString &aDirName) +{ + if (m_DirPrefId.IsEmpty()) + { + m_ListDirName = aDirName; + return NS_OK; + } + + // Store the old value. + nsString oldDirName; + nsresult rv = GetDirName(oldDirName); + NS_ENSURE_SUCCESS(rv, rv); + + // Save the new value + rv = SetLocalizedStringValue("description", NS_ConvertUTF16toUTF8(aDirName)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv)) + // We inherit from nsIAbDirectory, so this static cast should be safe. + abManager->NotifyItemPropertyChanged(static_cast<nsIAbDirectory*>(this), + "DirName", oldDirName.get(), + nsString(aDirName).get()); + + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::GetDirType(int32_t *aDirType) +{ + return GetIntValue("dirType", LDAPDirectory, aDirType); +} + +NS_IMETHODIMP nsAbDirProperty::GetFileName(nsACString &aFileName) +{ + return GetStringValue("filename", EmptyCString(), aFileName); +} + +NS_IMETHODIMP nsAbDirProperty::GetURI(nsACString &aURI) +{ + // XXX Should we complete this for Mailing Lists? + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbDirProperty::GetPosition(int32_t *aPosition) +{ + return GetIntValue("position", kDefaultPosition, aPosition); +} + +NS_IMETHODIMP nsAbDirProperty::GetLastModifiedDate(uint32_t *aLastModifiedDate) +{ + NS_ENSURE_ARG_POINTER(aLastModifiedDate); + *aLastModifiedDate = m_LastModifiedDate; + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::SetLastModifiedDate(uint32_t aLastModifiedDate) +{ + if (aLastModifiedDate) + { + m_LastModifiedDate = aLastModifiedDate; + } + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::GetListNickName(nsAString &aListNickName) +{ + aListNickName = m_ListNickName; + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::SetListNickName(const nsAString &aListNickName) +{ + m_ListNickName = aListNickName; + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::GetDescription(nsAString &aDescription) +{ + aDescription = m_Description; + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::SetDescription(const nsAString &aDescription) +{ + m_Description = aDescription; + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::GetIsMailList(bool *aIsMailList) +{ + *aIsMailList = m_IsMailList; + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::SetIsMailList(bool aIsMailList) +{ + m_IsMailList = aIsMailList; + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::GetAddressLists(nsIMutableArray * *aAddressLists) +{ + if (!m_AddressList) + { + nsresult rv; + m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + *aAddressLists = m_AddressList; + NS_ADDREF(*aAddressLists); + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::SetAddressLists(nsIMutableArray * aAddressLists) +{ + m_AddressList = aAddressLists; + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::CopyMailList(nsIAbDirectory* srcList) +{ + SetIsMailList(true); + + nsString str; + srcList->GetDirName(str); + SetDirName(str); + srcList->GetListNickName(str); + SetListNickName(str); + srcList->GetDescription(str); + SetDescription(str); + + nsCOMPtr<nsIMutableArray> pAddressLists; + srcList->GetAddressLists(getter_AddRefs(pAddressLists)); + SetAddressLists(pAddressLists); + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::GetIsQuery(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + // Mailing lists are not queries by default, individual directory types + // will override this. + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +nsAbDirProperty::Init(const char *aURI) +{ + mURINoQuery = aURI; + mURI = aURI; + mIsValidURI = true; + + int32_t searchCharLocation = mURINoQuery.FindChar('?'); + if (searchCharLocation >= 0) + { + mQueryString = Substring(mURINoQuery, searchCharLocation + 1); + mURINoQuery.SetLength(searchCharLocation); + mIsQueryURI = true; + } + + return NS_OK; +} + +// nsIAbDirectory NOT IMPLEMENTED methods +NS_IMETHODIMP +nsAbDirProperty::GetChildNodes(nsISimpleEnumerator **childList) +{ return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +nsAbDirProperty::GetChildCards(nsISimpleEnumerator **childCards) +{ return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +nsAbDirProperty::DeleteDirectory(nsIAbDirectory *directory) +{ return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +nsAbDirProperty::HasCard(nsIAbCard *cards, bool *hasCard) +{ return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +nsAbDirProperty::HasDirectory(nsIAbDirectory *dir, bool *hasDir) +{ return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +nsAbDirProperty::HasMailListWithName(const char16_t *aName, bool *aHasList) +{ + NS_ENSURE_ARG_POINTER(aName); + NS_ENSURE_ARG_POINTER(aHasList); + + *aHasList = false; + + bool supportsLists = false; + nsresult rv = GetSupportsMailingLists(&supportsLists); + if (NS_FAILED(rv) || !supportsLists) + return NS_OK; + + if (m_IsMailList) + return NS_OK; + + nsCOMPtr<nsIMutableArray> addressLists; + rv = GetAddressLists(getter_AddRefs(addressLists)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t listCount = 0; + rv = addressLists->GetLength(&listCount); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < listCount; i++) + { + nsCOMPtr<nsIAbDirectory> listDir(do_QueryElementAt(addressLists, i, &rv)); + if (NS_SUCCEEDED(rv) && listDir) + { + nsAutoString listName; + rv = listDir->GetDirName(listName); + if (NS_SUCCEEDED(rv) && listName.Equals(aName)) + { + *aHasList = true; + return NS_OK; + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsAbDirProperty::CreateNewDirectory(const nsAString &aDirName, + const nsACString &aURI, + uint32_t aType, + const nsACString &aPrefName, + nsACString &aResult) +{ return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP +nsAbDirProperty::CreateDirectoryByURI(const nsAString &aDisplayName, + const nsACString &aURI) +{ return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP nsAbDirProperty::AddMailList(nsIAbDirectory *list, nsIAbDirectory **addedList) +{ return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP nsAbDirProperty::EditMailListToDatabase(nsIAbCard *listCard) +{ return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP nsAbDirProperty::AddCard(nsIAbCard *childCard, nsIAbCard **addedCard) +{ return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP nsAbDirProperty::ModifyCard(nsIAbCard *aModifiedCard) +{ return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP nsAbDirProperty::DeleteCards(nsIArray *cards) +{ return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP nsAbDirProperty::DropCard(nsIAbCard *childCard, bool needToCopyCard) +{ return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP nsAbDirProperty::CardForEmailAddress(const nsACString &aEmailAddress, + nsIAbCard ** aAbCard) +{ return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP nsAbDirProperty::GetCardFromProperty(const char *aProperty, + const nsACString &aValue, + bool caseSensitive, + nsIAbCard **result) +{ return NS_ERROR_NOT_IMPLEMENTED; } + +NS_IMETHODIMP nsAbDirProperty::GetCardsFromProperty(const char *aProperty, + const nsACString &aValue, + bool caseSensitive, + nsISimpleEnumerator **result) +{ return NS_ERROR_NOT_IMPLEMENTED; } + + +NS_IMETHODIMP nsAbDirProperty::GetSupportsMailingLists(bool *aSupportsMailingsLists) +{ + NS_ENSURE_ARG_POINTER(aSupportsMailingsLists); + // We don't currently support nested mailing lists, so only return true if + // we're not a mailing list. + *aSupportsMailingsLists = !m_IsMailList; + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::GetReadOnly(bool *aReadOnly) +{ + NS_ENSURE_ARG_POINTER(aReadOnly); + // Default is that we are writable. Any implementation that is read-only must + // override this method. + *aReadOnly = false; + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::GetIsRemote(bool *aIsRemote) +{ + NS_ENSURE_ARG_POINTER(aIsRemote); + *aIsRemote = false; + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::GetIsSecure(bool *aIsSecure) +{ + NS_ENSURE_ARG_POINTER(aIsSecure); + *aIsSecure = false; + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::UseForAutocomplete(const nsACString &aIdentityKey, + bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + // Is local autocomplete enabled? + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch(do_GetService(NS_PREFSERVICE_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + return prefBranch->GetBoolPref("mail.enable_autocomplete", aResult); +} + +NS_IMETHODIMP nsAbDirProperty::GetDirPrefId(nsACString &aDirPrefId) +{ + aDirPrefId = m_DirPrefId; + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::SetDirPrefId(const nsACString &aDirPrefId) +{ + if (!m_DirPrefId.Equals(aDirPrefId)) + { + m_DirPrefId.Assign(aDirPrefId); + // Clear the directory pref branch so that it is re-initialized next + // time its required. + m_DirectoryPrefs = nullptr; + } + return NS_OK; +} + +nsresult nsAbDirProperty::InitDirectoryPrefs() +{ + if (m_DirPrefId.IsEmpty()) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv; + nsCOMPtr<nsIPrefService> prefService(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString realPrefId(m_DirPrefId); + realPrefId.Append('.'); + + return prefService->GetBranch(realPrefId.get(), getter_AddRefs(m_DirectoryPrefs)); +} + +NS_IMETHODIMP nsAbDirProperty::GetIntValue(const char *aName, + int32_t aDefaultValue, + int32_t *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs())) + return NS_ERROR_NOT_INITIALIZED; + + if (NS_FAILED(m_DirectoryPrefs->GetIntPref(aName, aResult))) + *aResult = aDefaultValue; + + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::GetBoolValue(const char *aName, + bool aDefaultValue, + bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs())) + return NS_ERROR_NOT_INITIALIZED; + + if (NS_FAILED(m_DirectoryPrefs->GetBoolPref(aName, aResult))) + *aResult = aDefaultValue; + + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::GetStringValue(const char *aName, + const nsACString &aDefaultValue, + nsACString &aResult) +{ + if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs())) + return NS_ERROR_NOT_INITIALIZED; + + nsCString value; + + /* unfortunately, there may be some prefs out there which look like (null) */ + if (NS_SUCCEEDED(m_DirectoryPrefs->GetCharPref(aName, getter_Copies(value))) && + !value.EqualsLiteral("(null")) + aResult = value; + else + aResult = aDefaultValue; + + return NS_OK; +} +/* + * Get localized unicode string pref from properties file, convert into an + * UTF8 string since address book prefs store as UTF8 strings. So far there + * are 2 default prefs stored in addressbook.properties. + * "ldap_2.servers.pab.description" + * "ldap_2.servers.history.description" + */ +NS_IMETHODIMP nsAbDirProperty::GetLocalizedStringValue(const char *aName, + const nsACString &aDefaultValue, + nsACString &aResult) +{ + if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs())) + return NS_ERROR_NOT_INITIALIZED; + + nsString wvalue; + nsCOMPtr<nsIPrefLocalizedString> locStr; + + nsresult rv = m_DirectoryPrefs->GetComplexValue(aName, + NS_GET_IID(nsIPrefLocalizedString), + getter_AddRefs(locStr)); + if (NS_SUCCEEDED(rv)) + { + rv = locStr->ToString(getter_Copies(wvalue)); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (wvalue.IsEmpty()) + aResult = aDefaultValue; + else + CopyUTF16toUTF8(wvalue, aResult); + + return NS_OK; +} + +NS_IMETHODIMP nsAbDirProperty::SetIntValue(const char *aName, + int32_t aValue) +{ + if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs())) + return NS_ERROR_NOT_INITIALIZED; + + return m_DirectoryPrefs->SetIntPref(aName, aValue); +} + +NS_IMETHODIMP nsAbDirProperty::SetBoolValue(const char *aName, + bool aValue) +{ + if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs())) + return NS_ERROR_NOT_INITIALIZED; + + return m_DirectoryPrefs->SetBoolPref(aName, aValue); +} + +NS_IMETHODIMP nsAbDirProperty::SetStringValue(const char *aName, + const nsACString &aValue) +{ + if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs())) + return NS_ERROR_NOT_INITIALIZED; + + return m_DirectoryPrefs->SetCharPref(aName, nsCString(aValue).get()); +} + +NS_IMETHODIMP nsAbDirProperty::SetLocalizedStringValue(const char *aName, + const nsACString &aValue) +{ + if (!m_DirectoryPrefs && NS_FAILED(InitDirectoryPrefs())) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv; + nsCOMPtr<nsIPrefLocalizedString> locStr( + do_CreateInstance(NS_PREFLOCALIZEDSTRING_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = locStr->SetData(NS_ConvertUTF8toUTF16(aValue).get()); + NS_ENSURE_SUCCESS(rv, rv); + + return m_DirectoryPrefs->SetComplexValue(aName, + NS_GET_IID(nsIPrefLocalizedString), + locStr); +} diff --git a/mailnews/addrbook/src/nsAbDirProperty.h b/mailnews/addrbook/src/nsAbDirProperty.h new file mode 100644 index 000000000..99d16a133 --- /dev/null +++ b/mailnews/addrbook/src/nsAbDirProperty.h @@ -0,0 +1,72 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/******************************************************************************************************** + + Interface for representing Address Book Directory + +*********************************************************************************************************/ + +#ifndef nsAbDirProperty_h__ +#define nsAbDirProperty_h__ + +#include "nsIAbDirectory.h" /* include the interface we are going to support */ +#include "nsIAbCard.h" +#include "nsCOMPtr.h" +#include "nsDirPrefs.h" +#include "nsIAddrDatabase.h" +#include "nsStringGlue.h" +#include "nsIPrefBranch.h" +#include "nsIMutableArray.h" +#include "nsWeakReference.h" + + /* + * Address Book Directory + */ + +class nsAbDirProperty: public nsIAbDirectory, + public nsSupportsWeakReference +{ +public: + nsAbDirProperty(void); + + NS_DECL_ISUPPORTS + NS_DECL_NSIABITEM + NS_DECL_NSIABCOLLECTION + NS_DECL_NSIABDIRECTORY + +protected: + virtual ~nsAbDirProperty(void); + + /** + * Initialise the directory prefs for this branch + */ + nsresult InitDirectoryPrefs(); + + uint32_t m_LastModifiedDate; + + nsString m_ListDirName; + nsString m_ListName; + nsString m_ListNickName; + nsString m_Description; + bool m_IsMailList; + + nsCString mURI; + nsCString mQueryString; + nsCString mURINoQuery; + bool mIsValidURI; + bool mIsQueryURI; + + + /* + * Note that any derived implementations should ensure that this item + * (m_DirPrefId) is correctly initialised correctly + */ + nsCString m_DirPrefId; // ie,"ldap_2.servers.pab" + + nsCOMPtr<nsIPrefBranch> m_DirectoryPrefs; + nsCOMPtr<nsIMutableArray> m_AddressList; +}; +#endif diff --git a/mailnews/addrbook/src/nsAbDirectoryQuery.cpp b/mailnews/addrbook/src/nsAbDirectoryQuery.cpp new file mode 100644 index 000000000..9c9826ac8 --- /dev/null +++ b/mailnews/addrbook/src/nsAbDirectoryQuery.cpp @@ -0,0 +1,528 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbDirectoryQuery.h" +#include "nsAbDirectoryQueryProxy.h" +#include "nsAbUtils.h" +#include "nsAbBooleanExpression.h" +#include "nsArrayUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsStringGlue.h" +#include "nsUnicharUtils.h" +#include "nsIAbDirSearchListener.h" +#include "nsISimpleEnumerator.h" +#include "nsMsgUtils.h" + +NS_IMPL_ISUPPORTS(nsAbDirectoryQuerySimpleBooleanExpression, nsIAbBooleanExpression) + +nsAbDirectoryQuerySimpleBooleanExpression::nsAbDirectoryQuerySimpleBooleanExpression() : + mOperation (nsIAbBooleanOperationTypes::AND) +{ +} + +nsAbDirectoryQuerySimpleBooleanExpression::~nsAbDirectoryQuerySimpleBooleanExpression() +{ +} + +/* attribute nsAbBooleanOperationType operation; */ +NS_IMETHODIMP nsAbDirectoryQuerySimpleBooleanExpression::GetOperation(nsAbBooleanOperationType *aOperation) +{ + if (!aOperation) + return NS_ERROR_NULL_POINTER; + + *aOperation = mOperation; + + return NS_OK; +} +NS_IMETHODIMP nsAbDirectoryQuerySimpleBooleanExpression::SetOperation(nsAbBooleanOperationType aOperation) +{ + if (aOperation != nsIAbBooleanOperationTypes::AND && + aOperation != nsIAbBooleanOperationTypes::OR) + return NS_ERROR_FAILURE; + + mOperation = aOperation; + + return NS_OK; +} + +/* attribute nsIArray expressions; */ +NS_IMETHODIMP nsAbDirectoryQuerySimpleBooleanExpression::GetExpressions(nsIArray **aExpressions) +{ + if (!aExpressions) + return NS_ERROR_NULL_POINTER; + + if (!mExpressions) + { + mExpressions = do_CreateInstance(NS_ARRAY_CONTRACTID); + if (!mExpressions) + return NS_ERROR_OUT_OF_MEMORY; + } + + NS_ADDREF(*aExpressions = mExpressions); + return NS_OK; +} + +NS_IMETHODIMP nsAbDirectoryQuerySimpleBooleanExpression::SetExpressions(nsIArray *aExpressions) +{ + if (!aExpressions) + return NS_ERROR_NULL_POINTER; + + // Ensure all the items are of the right type. + nsresult rv; + uint32_t count; + rv = aExpressions->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbBooleanConditionString> queryExpression; + + for (uint32_t i = 0; i < count; ++i) + { + queryExpression = do_QueryElementAt(aExpressions, i, &rv); + if (NS_FAILED(rv)) + return NS_ERROR_ILLEGAL_VALUE; + } + + // Values ok, so we can just save and return. + mExpressions = aExpressions; + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsAbDirectoryQueryArguments, nsIAbDirectoryQueryArguments) + +nsAbDirectoryQueryArguments::nsAbDirectoryQueryArguments() : + mQuerySubDirectories(true) +{ +} + +nsAbDirectoryQueryArguments::~nsAbDirectoryQueryArguments() +{ +} + +/* attribute nsISupports matchItems; */ +NS_IMETHODIMP nsAbDirectoryQueryArguments::GetExpression(nsISupports** aExpression) +{ + if (!aExpression) + return NS_ERROR_NULL_POINTER; + + NS_IF_ADDREF(*aExpression = mExpression); + return NS_OK; +} + +NS_IMETHODIMP nsAbDirectoryQueryArguments::SetExpression(nsISupports* aExpression) +{ + mExpression = aExpression; + return NS_OK; +} + +/* attribute boolean querySubDirectories; */ +NS_IMETHODIMP nsAbDirectoryQueryArguments::GetQuerySubDirectories(bool* aQuerySubDirectories) +{ + NS_ENSURE_ARG_POINTER(aQuerySubDirectories); + *aQuerySubDirectories = mQuerySubDirectories; + return NS_OK; +} + +NS_IMETHODIMP nsAbDirectoryQueryArguments::SetQuerySubDirectories(bool aQuerySubDirectories) +{ + mQuerySubDirectories = aQuerySubDirectories; + return NS_OK; +} + +NS_IMETHODIMP nsAbDirectoryQueryArguments::GetTypeSpecificArg(nsISupports** aArg) +{ + NS_ENSURE_ARG_POINTER(aArg); + + NS_IF_ADDREF(*aArg = mTypeSpecificArg); + return NS_OK; +} + +NS_IMETHODIMP nsAbDirectoryQueryArguments::SetTypeSpecificArg(nsISupports* aArg) +{ + mTypeSpecificArg = aArg; + return NS_OK; +} + +NS_IMETHODIMP nsAbDirectoryQueryArguments::GetFilter(nsACString & aFilter) +{ + aFilter.Assign(mFilter); + return NS_OK; +} + +NS_IMETHODIMP nsAbDirectoryQueryArguments::SetFilter(const nsACString & aFilter) +{ + mFilter.Assign(aFilter); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsAbDirectoryQueryPropertyValue, nsIAbDirectoryQueryPropertyValue) + +nsAbDirectoryQueryPropertyValue::nsAbDirectoryQueryPropertyValue() +{ +} + +nsAbDirectoryQueryPropertyValue::nsAbDirectoryQueryPropertyValue(const char* aName, + const char16_t* aValue) +{ + mName = aName; + mValue = aValue; +} + +nsAbDirectoryQueryPropertyValue::nsAbDirectoryQueryPropertyValue(const char* aName, + nsISupports* aValueISupports) +{ + mName = aName; + mValueISupports = aValueISupports; +} + +nsAbDirectoryQueryPropertyValue::~nsAbDirectoryQueryPropertyValue() +{ +} + +/* read only attribute string name; */ +NS_IMETHODIMP nsAbDirectoryQueryPropertyValue::GetName(char* *aName) +{ + *aName = mName.IsEmpty() ? 0 : ToNewCString(mName); + + return NS_OK; +} + +/* read only attribute wstring value; */ +NS_IMETHODIMP nsAbDirectoryQueryPropertyValue::GetValue(char16_t* *aValue) +{ + *aValue = ToNewUnicode(mValue); + if (!(*aValue)) + return NS_ERROR_OUT_OF_MEMORY; + else + return NS_OK; +} + +/* readonly attribute nsISupports valueISupports; */ +NS_IMETHODIMP nsAbDirectoryQueryPropertyValue::GetValueISupports(nsISupports* *aValueISupports) +{ + if (!mValueISupports) + return NS_ERROR_NULL_POINTER; + + NS_IF_ADDREF(*aValueISupports = mValueISupports); + return NS_OK; +} + +/* Implementation file */ +NS_IMPL_ISUPPORTS(nsAbDirectoryQuery, nsIAbDirectoryQuery) + +nsAbDirectoryQuery::nsAbDirectoryQuery() +{ +} + +nsAbDirectoryQuery::~nsAbDirectoryQuery() +{ +} + +NS_IMETHODIMP nsAbDirectoryQuery::DoQuery(nsIAbDirectory *aDirectory, + nsIAbDirectoryQueryArguments* arguments, + nsIAbDirSearchListener* listener, + int32_t resultLimit, int32_t timeOut, + int32_t* _retval) +{ + NS_ENSURE_ARG_POINTER(aDirectory); + + nsCOMPtr<nsISupports> supportsExpression; + nsresult rv = arguments->GetExpression(getter_AddRefs(supportsExpression)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbBooleanExpression> expression(do_QueryInterface(supportsExpression, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool doSubDirectories; + rv = arguments->GetQuerySubDirectories(&doSubDirectories); + NS_ENSURE_SUCCESS(rv, rv); + + rv = query(aDirectory, expression, listener, doSubDirectories, &resultLimit); + + rv = NS_FAILED(rv) ? queryError(listener) : queryFinished(listener); + + *_retval = 0; + return rv; +} + +/* void stopQuery (in long contextID); */ +NS_IMETHODIMP nsAbDirectoryQuery::StopQuery(int32_t contextID) +{ + return NS_OK; +} + + +nsresult nsAbDirectoryQuery::query(nsIAbDirectory* directory, + nsIAbBooleanExpression* expression, + nsIAbDirSearchListener* listener, + bool doSubDirectories, + int32_t* resultLimit) +{ + if (*resultLimit == 0) + return NS_OK; + + nsresult rv = queryCards(directory, expression, listener, resultLimit); + NS_ENSURE_SUCCESS(rv, rv); + + if (doSubDirectories && resultLimit != 0) + { + rv = queryChildren(directory, expression, listener, doSubDirectories, + resultLimit); + NS_ENSURE_SUCCESS(rv, rv); + } + + return rv; +} + +nsresult nsAbDirectoryQuery::queryChildren(nsIAbDirectory* directory, + nsIAbBooleanExpression* expression, + nsIAbDirSearchListener* listener, + bool doSubDirectories, + int32_t* resultLimit) +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsISimpleEnumerator> subDirectories; + rv = directory->GetChildNodes(getter_AddRefs(subDirectories)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED(rv = subDirectories->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr<nsISupports> item; + rv = subDirectories->GetNext (getter_AddRefs (item)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> subDirectory(do_QueryInterface(item, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = query(subDirectory, expression, listener, doSubDirectories, resultLimit); + NS_ENSURE_SUCCESS(rv, rv); + + } + return NS_OK; +} + +nsresult nsAbDirectoryQuery::queryCards(nsIAbDirectory* directory, + nsIAbBooleanExpression* expression, + nsIAbDirSearchListener* listener, + int32_t* resultLimit) +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsISimpleEnumerator> cards; + rv = directory->GetChildCards(getter_AddRefs(cards)); + if (NS_FAILED(rv)) + { + if (rv != NS_ERROR_NOT_IMPLEMENTED) + NS_ENSURE_SUCCESS(rv, rv); + else + return NS_OK; + } + + if (!cards) + return NS_OK; + + bool more; + while (NS_SUCCEEDED(cards->HasMoreElements(&more)) && more) + { + nsCOMPtr<nsISupports> item; + rv = cards->GetNext(getter_AddRefs(item)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbCard> card(do_QueryInterface(item, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = matchCard (card, expression, listener, resultLimit); + NS_ENSURE_SUCCESS(rv, rv); + + if (*resultLimit == 0) + return NS_OK; + } + + return NS_OK; +} + +nsresult nsAbDirectoryQuery::matchCard(nsIAbCard* card, + nsIAbBooleanExpression* expression, + nsIAbDirSearchListener* listener, + int32_t* resultLimit) +{ + bool matchFound = false; + nsresult rv = matchCardExpression(card, expression, &matchFound); + NS_ENSURE_SUCCESS(rv, rv); + + if (matchFound) + { + (*resultLimit)--; + rv = queryMatch(card, listener); + NS_ENSURE_SUCCESS(rv, rv); + } + + return rv; +} + +nsresult nsAbDirectoryQuery::matchCardExpression(nsIAbCard* card, + nsIAbBooleanExpression* expression, + bool* result) +{ + nsAbBooleanOperationType operation; + nsresult rv = expression->GetOperation (&operation); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIArray> childExpressions; + rv = expression->GetExpressions (getter_AddRefs (childExpressions)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t count; + rv = childExpressions->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + + if (operation == nsIAbBooleanOperationTypes::NOT && + count > 1) + return NS_ERROR_FAILURE; + + bool value = *result = false; + nsCOMPtr<nsIAbBooleanConditionString> childCondition; + nsCOMPtr<nsIAbBooleanExpression> childExpression; + + for (uint32_t i = 0; i < count; i++) + { + childCondition = do_QueryElementAt(childExpressions, i, &rv); + if (NS_SUCCEEDED(rv)) + { + rv = matchCardCondition (card, childCondition, &value); + NS_ENSURE_SUCCESS(rv, rv); + } + else + { + childExpression = do_QueryElementAt(childExpressions, i, &rv); + if (NS_SUCCEEDED(rv)) + { + rv = matchCardExpression (card, childExpression, &value); + NS_ENSURE_SUCCESS(rv, rv); + } + else + return NS_ERROR_FAILURE; + } + if (operation == nsIAbBooleanOperationTypes::OR && value) + break; + else if (operation == nsIAbBooleanOperationTypes::AND && !value) + break; + else if (operation == nsIAbBooleanOperationTypes::NOT) + value = !value; + } + *result = value; + + return NS_OK; +} + +nsresult nsAbDirectoryQuery::matchCardCondition(nsIAbCard* card, + nsIAbBooleanConditionString* condition, + bool* matchFound) +{ + nsAbBooleanConditionType conditionType; + nsresult rv = condition->GetCondition (&conditionType); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString name; + rv = condition->GetName (getter_Copies (name)); + NS_ENSURE_SUCCESS(rv, rv); + + if (name.Equals ("card:nsIAbCard")) + { + *matchFound = (conditionType == nsIAbBooleanConditionTypes::Exists); + return NS_OK; + } + + nsString matchValue; + rv = condition->GetValue (getter_Copies (matchValue)); + NS_ENSURE_SUCCESS(rv, rv); + + if (name.EqualsLiteral("IsMailList")) + { + bool isMailList; + rv = card->GetIsMailList(&isMailList); + NS_ENSURE_SUCCESS(rv, rv); + + // Only equals is supported. + if (conditionType != nsIAbBooleanConditionTypes::Is) + return NS_ERROR_FAILURE; + + *matchFound = isMailList ? matchValue.EqualsLiteral("TRUE") : + matchValue.EqualsLiteral("FALSE"); + return NS_OK; + } + + nsString value; + (void)card->GetPropertyAsAString(name.get(), value); + + if (value.IsEmpty()) + { + *matchFound = (conditionType == nsIAbBooleanConditionTypes::DoesNotExist) ? + true : false; + return NS_OK; + } + + /* TODO + * What about allowing choice between case insensitive + * and case sensitive comparisons? + * + */ + switch (conditionType) + { + case nsIAbBooleanConditionTypes::Exists: + *matchFound = true; + break; + case nsIAbBooleanConditionTypes::Contains: + *matchFound = CaseInsensitiveFindInReadable(matchValue, value); + break; + case nsIAbBooleanConditionTypes::DoesNotContain: + *matchFound = !CaseInsensitiveFindInReadable(matchValue, value); + break; + case nsIAbBooleanConditionTypes::Is: + *matchFound = value.Equals(matchValue, nsCaseInsensitiveStringComparator()); + break; + case nsIAbBooleanConditionTypes::IsNot: + *matchFound = !value.Equals(matchValue, nsCaseInsensitiveStringComparator()); + break; + case nsIAbBooleanConditionTypes::BeginsWith: + *matchFound = StringBeginsWith(value, matchValue, nsCaseInsensitiveStringComparator()); + break; + case nsIAbBooleanConditionTypes::LessThan: + *matchFound = Compare(value, matchValue, nsCaseInsensitiveStringComparator()) < 0; + break; + case nsIAbBooleanConditionTypes::GreaterThan: + *matchFound = Compare(value, matchValue, nsCaseInsensitiveStringComparator()) > 0; + break; + case nsIAbBooleanConditionTypes::EndsWith: + *matchFound = StringEndsWith(value, matchValue, nsCaseInsensitiveStringComparator()); + break; + case nsIAbBooleanConditionTypes::SoundsLike: + case nsIAbBooleanConditionTypes::RegExp: + *matchFound = false; + break; + default: + *matchFound = false; + } + + return rv; +} + +nsresult nsAbDirectoryQuery::queryMatch(nsIAbCard* card, + nsIAbDirSearchListener* listener) +{ + return listener->OnSearchFoundCard(card); +} + +nsresult nsAbDirectoryQuery::queryFinished(nsIAbDirSearchListener* listener) +{ + return listener->OnSearchFinished(nsIAbDirectoryQueryResultListener::queryResultComplete, EmptyString()); +} + +nsresult nsAbDirectoryQuery::queryError(nsIAbDirSearchListener* listener) +{ + return listener->OnSearchFinished(nsIAbDirectoryQueryResultListener::queryResultError, EmptyString()); +} diff --git a/mailnews/addrbook/src/nsAbDirectoryQuery.h b/mailnews/addrbook/src/nsAbDirectoryQuery.h new file mode 100644 index 000000000..99aa943aa --- /dev/null +++ b/mailnews/addrbook/src/nsAbDirectoryQuery.h @@ -0,0 +1,113 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsAbDirectoryQuery_h__ +#define nsAbDirectoryQuery_h__ + +#include "nsIAbDirectoryQuery.h" +#include "nsIAbDirectory.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" +#include "nsIArray.h" +#include "nsIAbBooleanExpression.h" + +class nsAbDirectoryQuerySimpleBooleanExpression : public nsIAbBooleanExpression +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIABBOOLEANEXPRESSION + + nsAbDirectoryQuerySimpleBooleanExpression(); + +private: + virtual ~nsAbDirectoryQuerySimpleBooleanExpression(); + +public: + nsCOMPtr<nsIArray> mExpressions; + nsAbBooleanOperationType mOperation; +}; + + +class nsAbDirectoryQueryArguments : public nsIAbDirectoryQueryArguments +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIABDIRECTORYQUERYARGUMENTS + + nsAbDirectoryQueryArguments(); + +private: + virtual ~nsAbDirectoryQueryArguments(); + +protected: + nsCOMPtr<nsISupports> mExpression; + nsCOMPtr<nsISupports> mTypeSpecificArg; + bool mQuerySubDirectories; + nsCString mFilter; +}; + + +class nsAbDirectoryQueryPropertyValue : public nsIAbDirectoryQueryPropertyValue +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIABDIRECTORYQUERYPROPERTYVALUE + + nsAbDirectoryQueryPropertyValue(); + nsAbDirectoryQueryPropertyValue(const char* aName, + const char16_t* aValue); + nsAbDirectoryQueryPropertyValue(const char* aName, + nsISupports* aValueISupports); + +protected: + virtual ~nsAbDirectoryQueryPropertyValue(); + nsCString mName; + nsString mValue; + nsCOMPtr<nsISupports> mValueISupports; +}; + + +class nsAbDirectoryQuery : public nsIAbDirectoryQuery +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIABDIRECTORYQUERY + + nsAbDirectoryQuery(); + +protected: + virtual ~nsAbDirectoryQuery(); + nsresult query(nsIAbDirectory* directory, + nsIAbBooleanExpression* expression, + nsIAbDirSearchListener* listener, + bool doSubDirectories, + int32_t* resultLimit); + nsresult queryChildren(nsIAbDirectory* directory, + nsIAbBooleanExpression* expression, + nsIAbDirSearchListener* listener, + bool doSubDirectories, + int32_t* resultLimit); + nsresult queryCards(nsIAbDirectory* directory, + nsIAbBooleanExpression* expression, + nsIAbDirSearchListener* listener, + int32_t* resultLimit); + nsresult matchCard(nsIAbCard* card, + nsIAbBooleanExpression* expression, + nsIAbDirSearchListener* listener, + int32_t* resultLimit); + nsresult matchCardExpression(nsIAbCard* card, + nsIAbBooleanExpression* expression, + bool* result); + nsresult matchCardCondition(nsIAbCard* card, + nsIAbBooleanConditionString* condition, + bool* matchFound); + + nsresult queryMatch (nsIAbCard* card, + nsIAbDirSearchListener* listener); + nsresult queryFinished(nsIAbDirSearchListener* listener); + nsresult queryError(nsIAbDirSearchListener* listener); +}; + +#endif diff --git a/mailnews/addrbook/src/nsAbDirectoryQueryProxy.cpp b/mailnews/addrbook/src/nsAbDirectoryQueryProxy.cpp new file mode 100644 index 000000000..553f3b903 --- /dev/null +++ b/mailnews/addrbook/src/nsAbDirectoryQueryProxy.cpp @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#include "nsAbDirectoryQuery.h" +#include "nsAbDirectoryQueryProxy.h" + +NS_IMPL_ISUPPORTS(nsAbDirectoryQueryProxy, nsIAbDirectoryQueryProxy, nsIAbDirectoryQuery) + +nsAbDirectoryQueryProxy::nsAbDirectoryQueryProxy() : + mInitiated (false) +{ +} + +nsAbDirectoryQueryProxy::~nsAbDirectoryQueryProxy() +{ +} + +/* void initiate (in nsIAbDirectory directory); */ +NS_IMETHODIMP nsAbDirectoryQueryProxy::Initiate() +{ + if (mInitiated) + return NS_OK; + + mDirectoryQuery = new nsAbDirectoryQuery(); + + mInitiated = true; + + return NS_OK; +} + + diff --git a/mailnews/addrbook/src/nsAbDirectoryQueryProxy.h b/mailnews/addrbook/src/nsAbDirectoryQueryProxy.h new file mode 100644 index 000000000..89a323c75 --- /dev/null +++ b/mailnews/addrbook/src/nsAbDirectoryQueryProxy.h @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsAbDirectoryQueryProxy_h__ +#define nsAbDirectoryQueryProxy_h__ + +#include "nsIAbDirectoryQueryProxy.h" +#include "nsCOMPtr.h" + +class nsAbDirectoryQueryProxy : public nsIAbDirectoryQueryProxy +{ +public: + NS_DECL_ISUPPORTS + NS_FORWARD_NSIABDIRECTORYQUERY(mDirectoryQuery->) + NS_DECL_NSIABDIRECTORYQUERYPROXY + + nsAbDirectoryQueryProxy(); + +protected: + virtual ~nsAbDirectoryQueryProxy(); + bool mInitiated; + nsCOMPtr<nsIAbDirectoryQuery> mDirectoryQuery; +}; + +#endif diff --git a/mailnews/addrbook/src/nsAbLDAPAttributeMap.js b/mailnews/addrbook/src/nsAbLDAPAttributeMap.js new file mode 100644 index 000000000..8ff2406d2 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPAttributeMap.js @@ -0,0 +1,247 @@ +/* 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/. */ + +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +var NS_ABLDAPATTRIBUTEMAP_CID = Components.ID( + "{127b341a-bdda-4270-85e1-edff569a9b85}"); +var NS_ABLDAPATTRIBUTEMAPSERVICE_CID = Components.ID( + "{4ed7d5e1-8800-40da-9e78-c4f509d7ac5e}"); + +function nsAbLDAPAttributeMap() { + this.mPropertyMap = {}; + this.mAttrMap = {}; +} + +nsAbLDAPAttributeMap.prototype = { + classID: NS_ABLDAPATTRIBUTEMAP_CID, + + getAttributeList: function getAttributeList(aProperty) { + + if (!(aProperty in this.mPropertyMap)) { + return null; + } + + // return the joined list + return this.mPropertyMap[aProperty].join(","); + }, + + getAttributes: function getAttributes(aProperty, aCount, aAttrs) { + + // fail if no entry for this + if (!(aProperty in this.mPropertyMap)) { + throw Components.results.NS_ERROR_FAILURE; + } + + aAttrs = this.mPropertyMap[aProperty]; + aCount = aAttrs.length; + return aAttrs; + }, + + getFirstAttribute: function getFirstAttribute(aProperty) { + + // fail if no entry for this + if (!(aProperty in this.mPropertyMap)) { + return null; + } + + return this.mPropertyMap[aProperty][0]; + }, + + setAttributeList: function setAttributeList(aProperty, aAttributeList, + aAllowInconsistencies) { + + var attrs = aAttributeList.split(","); + + // check to make sure this call won't allow multiple mappings to be + // created, if requested + if (!aAllowInconsistencies) { + for (var attr of attrs) { + if (attr in this.mAttrMap && this.mAttrMap[attr] != aProperty) { + throw Components.results.NS_ERROR_FAILURE; + } + } + } + + // delete any attr mappings created by the existing property map entry + if (aProperty in this.mPropertyMap) { + for (attr of this.mPropertyMap[aProperty]) { + delete this.mAttrMap[attr]; + } + } + + // add these attrs to the attrmap + for (attr of attrs) { + this.mAttrMap[attr] = aProperty; + } + + // add them to the property map + this.mPropertyMap[aProperty] = attrs; + }, + + getProperty: function getProperty(aAttribute) { + + if (!(aAttribute in this.mAttrMap)) { + return null; + } + + return this.mAttrMap[aAttribute]; + }, + + getAllCardAttributes: function getAllCardAttributes() { + var attrs = []; + for (var prop in this.mPropertyMap) { + let attrArray = this.mPropertyMap[prop]; + attrs = attrs.concat(attrArray); + } + + if (!attrs.length) { + throw Components.results.NS_ERROR_FAILURE; + } + + return attrs.join(","); + }, + + getAllCardProperties: function getAllCardProperties(aCount) { + + var props = []; + for (var prop in this.mPropertyMap) { + props.push(prop); + } + + aCount.value = props.length; + return props; + }, + + setFromPrefs: function setFromPrefs(aPrefBranchName) { + // get the right pref branch + let branch = Services.prefs.getBranch(aPrefBranchName + "."); + + // get the list of children + var childCount = {}; + var children = branch.getChildList("", childCount); + + // do the actual sets + for (var child of children) { + this.setAttributeList(child, branch.getCharPref(child), true); + } + + // ensure that everything is kosher + this.checkState(); + }, + + setCardPropertiesFromLDAPMessage: function + setCardPropertiesFromLDAPMessage(aMessage, aCard) { + + var cardValueWasSet = false; + + var msgAttrCount = {}; + var msgAttrs = aMessage.getAttributes(msgAttrCount); + + // downcase the array for comparison + function toLower(a) { return a.toLowerCase(); } + msgAttrs = msgAttrs.map(toLower); + + // deal with each addressbook property + for (var prop in this.mPropertyMap) { + + // go through the list of possible attrs in precedence order + for (var attr of this.mPropertyMap[prop]) { + + attr = attr.toLowerCase(); + + // find the first attr that exists in this message + if (msgAttrs.indexOf(attr) != -1) { + + try { + var values = aMessage.getValues(attr, {}); + // strip out the optional label from the labeledURI + if (attr == "labeleduri" && values[0]) { + var index = values[0].indexOf(" "); + if (index != -1) + values[0] = values[0].substring(0, index); + } + aCard.setProperty(prop, values[0]); + + cardValueWasSet = true; + break; + } catch (ex) { + // ignore any errors getting message values or setting card values + } + } + } + } + + if (!cardValueWasSet) { + throw Components.results.NS_ERROR_FAILURE; + } + + return; + }, + + checkState: function checkState() { + + var attrsSeen = []; + + for (var prop in this.mPropertyMap) { + let attrArray = this.mPropertyMap[prop]; + for (var attr of attrArray) { + + // multiple attributes that mapped to the empty string are permitted + if (!attr.length) { + continue; + } + + // if we've seen this before, there's a problem + if (attrsSeen.indexOf(attr) != -1) { + throw Components.results.NS_ERROR_FAILURE; + } + + // remember that we've seen it now + attrsSeen.push(attr); + } + } + + return; + }, + + QueryInterface: XPCOMUtils + .generateQI([Components.interfaces.nsIAbLDAPAttributeMap]) +} + +function nsAbLDAPAttributeMapService() { +} + +nsAbLDAPAttributeMapService.prototype = { + + classID: NS_ABLDAPATTRIBUTEMAPSERVICE_CID, + + mAttrMaps: {}, + + getMapForPrefBranch: function getMapForPrefBranch(aPrefBranchName) { + + // if we've already got this map, return it + if (aPrefBranchName in this.mAttrMaps) { + return this.mAttrMaps[aPrefBranchName]; + } + + // otherwise, try and create it + var attrMap = new nsAbLDAPAttributeMap(); + attrMap.setFromPrefs("ldap_2.servers.default.attrmap"); + attrMap.setFromPrefs(aPrefBranchName + ".attrmap"); + + // cache + this.mAttrMaps[aPrefBranchName] = attrMap; + + // and return + return attrMap; + }, + + QueryInterface: XPCOMUtils + .generateQI([Components.interfaces.nsIAbLDAPAttributeMapService]) +} + +var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsAbLDAPAttributeMap, nsAbLDAPAttributeMapService]); + diff --git a/mailnews/addrbook/src/nsAbLDAPAutoCompleteSearch.js b/mailnews/addrbook/src/nsAbLDAPAutoCompleteSearch.js new file mode 100644 index 000000000..1c62d7e97 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPAutoCompleteSearch.js @@ -0,0 +1,325 @@ +/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +Components.utils.import("resource:///modules/mailServices.js"); +Components.utils.import("resource://gre/modules/Services.jsm"); +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +var ACR = Components.interfaces.nsIAutoCompleteResult; +var nsIAbAutoCompleteResult = Components.interfaces.nsIAbAutoCompleteResult; +var nsIAbDirectoryQueryResultListener = + Components.interfaces.nsIAbDirectoryQueryResultListener; + +// nsAbLDAPAutoCompleteResult +// Derived from nsIAbAutoCompleteResult, provides a LDAP specific result +// implementation. + +function nsAbLDAPAutoCompleteResult(aSearchString) { + // Can't create this in the prototype as we'd get the same array for + // all instances + this._searchResults = []; + this.searchString = aSearchString; +} + +nsAbLDAPAutoCompleteResult.prototype = { + _searchResults: null, + _commentColumn: "", + + // nsIAutoCompleteResult + + searchString: null, + searchResult: ACR.RESULT_NOMATCH, + defaultIndex: -1, + errorDescription: null, + + get matchCount() { + return this._searchResults.length; + }, + + getLabelAt: function getLabelAt(aIndex) { + return this.getValueAt(aIndex); + }, + + getValueAt: function getValueAt(aIndex) { + return this._searchResults[aIndex].value; + }, + + getCommentAt: function getCommentAt(aIndex) { + return this._commentColumn; + }, + + getStyleAt: function getStyleAt(aIndex) { + return this.searchResult == ACR.RESULT_FAILURE ? "remote-err" : + "remote-abook"; + }, + + getImageAt: function getImageAt(aIndex) { + return ""; + }, + + getFinalCompleteValueAt: function(aIndex) { + return this.getValueAt(aIndex); + }, + + removeValueAt: function removeValueAt(aRowIndex, aRemoveFromDB) { + }, + + // nsIAbAutoCompleteResult + + getCardAt: function getCardAt(aIndex) { + return this._searchResults[aIndex].card; + }, + + // nsISupports + + QueryInterface: XPCOMUtils.generateQI([ACR, nsIAbAutoCompleteResult]) +} + +function nsAbLDAPAutoCompleteSearch() { + Services.obs.addObserver(this, "quit-application", false); + this._timer = Components.classes["@mozilla.org/timer;1"] + .createInstance(Components.interfaces.nsITimer); +} + +nsAbLDAPAutoCompleteSearch.prototype = { + // For component registration + classID: Components.ID("227e6482-fe9f-441f-9b7d-7b60375e7449"), + + // A short-lived LDAP directory cache. + // To avoid recreating components as the user completes, we maintain the most + // recently used address book, nsAbLDAPDirectoryQuery and search context. + // However the cache is discarded if it has not been used for a minute. + // This is done to avoid problems with LDAP sessions timing out and hanging. + _query: null, + _book: null, + _attributes: null, + _context: -1, + _timer: null, + + // The current search result. + _result: null, + // The listener to pass back results to. + _listener: null, + + _parser: MailServices.headerParser, + + applicableHeaders: new Set(["addr_to", "addr_cc", "addr_bcc", "addr_reply"]), + + // Private methods + + _checkDuplicate: function _checkDuplicate(card, emailAddress) { + var lcEmailAddress = emailAddress.toLocaleLowerCase(); + + return this._result._searchResults.some(function(result) { + return result.value.toLocaleLowerCase() == lcEmailAddress; + }); + }, + + _addToResult: function(card) { + let mbox = this._parser.makeMailboxObject(card.displayName, + card.isMailList ? card.getProperty("Notes", "") || card.displayName : + card.primaryEmail); + if (!mbox.email) + return; + + let emailAddress = mbox.toString(); + + // If it is a duplicate, then just return and don't add it. The + // _checkDuplicate function deals with it all for us. + if (this._checkDuplicate(card, emailAddress)) + return; + + // Find out where to insert the card. + var insertPosition = 0; + + // Next sort on full address + while (insertPosition < this._result._searchResults.length && + emailAddress > this._result._searchResults[insertPosition].value) + ++insertPosition; + + this._result._searchResults.splice(insertPosition, 0, { + value: emailAddress, + card: card, + }); + }, + + // nsIObserver + + observe: function observer(subject, topic, data) { + if (topic == "quit-application") { + Services.obs.removeObserver(this, "quit-application"); + } else if (topic != "timer-callback") { + return; + } + + // Force the individual query items to null, so that the memory + // gets collected straight away. + this.stopSearch(); + this._book = null; + this._context = -1; + this._query = null; + this._attributes = null; + }, + + // nsIAutoCompleteSearch + + startSearch: function startSearch(aSearchString, aParam, + aPreviousResult, aListener) { + let params = JSON.parse(aParam) || {}; + let applicable = !("type" in params) || this.applicableHeaders.has(params.type); + + this._result = new nsAbLDAPAutoCompleteResult(aSearchString); + aSearchString = aSearchString.toLocaleLowerCase(); + + // If the search string isn't value, or contains a comma, or the user + // hasn't enabled autocomplete, then just return no matches / or the + // result ignored. + // The comma check is so that we don't autocomplete against the user + // entering multiple addresses. + if (!applicable || !aSearchString || aSearchString.includes(",")) { + this._result.searchResult = ACR.RESULT_IGNORED; + aListener.onSearchResult(this, this._result); + return; + } + + // The rules here: If the current identity has a directoryServer set, then + // use that, otherwise, try the global preference instead. + var acDirURI = null; + var identity; + + if ("idKey" in params) { + try { + identity = MailServices.accounts.getIdentity(params.idKey); + } + catch(ex) { + Components.utils.reportError("Couldn't get specified identity, " + + "falling back to global settings"); + } + } + + // Does the current identity override the global preference? + if (identity && identity.overrideGlobalPref) + acDirURI = identity.directoryServer; + else { + // Try the global one + if (Services.prefs.getBoolPref("ldap_2.autoComplete.useDirectory")) + acDirURI = Services.prefs.getCharPref("ldap_2.autoComplete.directoryServer"); + } + + if (!acDirURI) { + // No directory to search, send a no match and return. + aListener.onSearchResult(this, this._result); + return; + } + + this.stopSearch(); + + // If we don't already have a cached query for this URI, build a new one. + acDirURI = "moz-abldapdirectory://" + acDirURI; + if (!this._book || this._book.URI != acDirURI) { + this._query = + Components.classes["@mozilla.org/addressbook/ldap-directory-query;1"] + .createInstance(Components.interfaces.nsIAbDirectoryQuery); + this._book = MailServices.ab.getDirectory(acDirURI) + .QueryInterface(Components.interfaces.nsIAbLDAPDirectory); + + // Create a minimal map just for the display name and primary email. + this._attributes = + Components.classes["@mozilla.org/addressbook/ldap-attribute-map;1"] + .createInstance(Components.interfaces.nsIAbLDAPAttributeMap); + this._attributes.setAttributeList("DisplayName", + this._book.attributeMap.getAttributeList("DisplayName", {}), true); + this._attributes.setAttributeList("PrimaryEmail", + this._book.attributeMap.getAttributeList("PrimaryEmail", {}), true); + } + + this._result._commentColumn = this._book.dirName; + this._listener = aListener; + this._timer.init(this, 60000, Components.interfaces.nsITimer.TYPE_ONE_SHOT); + + var args = + Components.classes["@mozilla.org/addressbook/directory/query-arguments;1"] + .createInstance(Components.interfaces.nsIAbDirectoryQueryArguments); + + var filterTemplate = this._book.getStringValue("autoComplete.filterTemplate", ""); + + // Use default value when preference is not set or it contains empty string + if (!filterTemplate) + filterTemplate = "(|(cn=%v1*%v2-*)(mail=%v1*%v2-*)(sn=%v1*%v2-*))"; + + // Create filter from filter template and search string + var ldapSvc = Components.classes["@mozilla.org/network/ldap-service;1"] + .getService(Components.interfaces.nsILDAPService); + var filter = ldapSvc.createFilter(1024, filterTemplate, "", "", "", aSearchString); + if (!filter) + throw new Error("Filter string is empty, check if filterTemplate variable is valid in prefs.js."); + args.typeSpecificArg = this._attributes; + args.querySubDirectories = true; + args.filter = filter; + + // Start the actual search + this._context = + this._query.doQuery(this._book, args, this, this._book.maxHits, 0); + }, + + stopSearch: function stopSearch() { + if (this._listener) { + this._query.stopQuery(this._context); + this._listener = null; + } + }, + + // nsIAbDirSearchListener + + onSearchFinished: function onSearchFinished(aResult, aErrorMsg) { + if (!this._listener) + return; + + if (aResult == nsIAbDirectoryQueryResultListener.queryResultComplete) { + if (this._result.matchCount) { + this._result.searchResult = ACR.RESULT_SUCCESS; + this._result.defaultIndex = 0; + } + else + this._result.searchResult = ACR.RESULT_NOMATCH; + } + else if (aResult == nsIAbDirectoryQueryResultListener.queryResultError) { + this._result.searchResult = ACR.RESULT_FAILURE; + this._result.defaultIndex = 0; + } + // const long queryResultStopped = 2; + // const long queryResultError = 3; + this._listener.onSearchResult(this, this._result); + this._listener = null; + }, + + onSearchFoundCard: function onSearchFoundCard(aCard) { + if (!this._listener) + return; + + this._addToResult(aCard); + + /* XXX autocomplete doesn't expect you to rearrange while searching + if (this._result.matchCount) + this._result.searchResult = ACR.RESULT_SUCCESS_ONGOING; + else + this._result.searchResult = ACR.RESULT_NOMATCH_ONGOING; + + this._listener.onSearchResult(this, this._result); + */ + }, + + // nsISupports + + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIObserver, + Components.interfaces + .nsIAutoCompleteSearch, + Components.interfaces + .nsIAbDirSearchListener]) +}; + +// Module + +var NSGetFactory = XPCOMUtils.generateNSGetFactory([nsAbLDAPAutoCompleteSearch]); diff --git a/mailnews/addrbook/src/nsAbLDAPCard.cpp b/mailnews/addrbook/src/nsAbLDAPCard.cpp new file mode 100644 index 000000000..6dcfdedbb --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPCard.cpp @@ -0,0 +1,297 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbLDAPCard.h" +#include "nsIMutableArray.h" +#include "nsCOMPtr.h" +#include "nsILDAPModification.h" +#include "nsILDAPBERValue.h" +#include "nsILDAPMessage.h" +#include "nsIAbLDAPAttributeMap.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsAbBaseCID.h" +#include "nsAbUtils.h" +#include "nsILDAPErrors.h" + +#include <stdio.h> + +#define kDNColumn "DN" + +nsAbLDAPCard::nsAbLDAPCard() +{ +} + +nsAbLDAPCard::~nsAbLDAPCard() +{ +} + +NS_IMPL_ISUPPORTS_INHERITED(nsAbLDAPCard, nsAbCardProperty, nsIAbLDAPCard) + +/* Retrieves the changes to the LDAP card and stores them in an LDAP + * update message. + * + * Calling this method changes the LDAP card, it updates the + * meta-properties (m_*) to reflect what the LDAP contents will be once + * the update has been performed. This allows you to do multiple (successful) + * consecutive edits on a card in a search result. If the meta-properties + * were not updated, incorrect assuptions would be made about what object + * classes to add, or what attributes to clear. + * + * XXX: We need to take care when integrating this code with the asynchronous + * update dialogs, as the current code in nsAbLDAPDirectory has a problem + * when an update fails: the modified card still gets stored and shown to + * the user instead of being discarded. There is one especially tricky case: + * when you do an update on a card which changes its DN, you have two + * operations (rename, then update the other attributes). If the rename + * operation succeeds and not the update of the attributes, you are + * "somewhere in between" the original card and the updated card. +*/ +NS_IMETHODIMP nsAbLDAPCard::GetLDAPMessageInfo( + nsIAbLDAPAttributeMap *aAttributeMap, + const uint32_t aClassCount, + const char **aClasses, + int32_t aType, + nsIArray **aLDAPAddMessageInfo) +{ + NS_ENSURE_ARG_POINTER(aAttributeMap); + NS_ENSURE_ARG_POINTER(aClasses); + NS_ENSURE_ARG_POINTER(aLDAPAddMessageInfo); + + nsresult rv; + nsCOMPtr<nsIMutableArray> modArray = + do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Add any missing object classes. We never remove any object + // classes: if an entry has additional object classes, it's probably + // for a good reason. + nsAutoCString oclass; + for (uint32_t i = 0; i < aClassCount; ++i) + { + oclass.Assign(nsDependentCString(aClasses[i])); + ToLowerCase(oclass); + + if (!m_objectClass.Contains(oclass)) + { + m_objectClass.AppendElement(oclass); + printf("LDAP : adding objectClass %s\n", oclass.get()); + } + } + + nsCOMPtr<nsILDAPModification> mod = + do_CreateInstance("@mozilla.org/network/ldap-modification;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> values = + do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < m_objectClass.Length(); ++i) + { + nsCOMPtr<nsILDAPBERValue> value = + do_CreateInstance("@mozilla.org/network/ldap-ber-value;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = value->SetFromUTF8(m_objectClass.ElementAt(i)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = values->AppendElement(value, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = mod->SetUpModification(aType, NS_LITERAL_CSTRING("objectClass"), values); + NS_ENSURE_SUCCESS(rv, rv); + + modArray->AppendElement(mod, false); + + // Add card properties + CharPtrArrayGuard props; + rv = aAttributeMap->GetAllCardProperties(props.GetSizeAddr(), + props.GetArrayAddr()); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString attr; + nsCString propvalue; + for (uint32_t i = 0; i < props.GetSize(); ++i) + { + // Skip some attributes that don't map to LDAP. + // + // BirthYear : by default this is mapped to 'birthyear', + // which is not part of mozillaAbPersonAlpha + // + // LastModifiedDate : by default this is mapped to 'modifytimestamp', + // which cannot be modified + // + // PreferMailFormat : by default this is mapped to 'mozillaUseHtmlMail', + // which is a boolean, not plaintext/html/unknown + if (!strcmp(props[i], kBirthYearProperty) || + !strcmp(props[i], kLastModifiedDateProperty) || + !strcmp(props[i], kPreferMailFormatProperty)) + continue; + + rv = aAttributeMap->GetFirstAttribute(nsDependentCString(props[i]), + attr); + NS_ENSURE_SUCCESS(rv, rv); + ToLowerCase(attr); + + // If the property is not mapped to an attribute, skip it. + if (attr.IsEmpty()) + continue; + + nsCOMPtr<nsILDAPModification> mod = + do_CreateInstance("@mozilla.org/network/ldap-modification;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + size_t index = m_attributes.IndexOf(attr); + + rv = GetPropertyAsAUTF8String(props[i], propvalue); + + if (NS_SUCCEEDED(rv) &&!propvalue.IsEmpty()) + { + // If the new value is not empty, add/update it + nsCOMPtr<nsILDAPBERValue> value = + do_CreateInstance("@mozilla.org/network/ldap-ber-value;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = value->SetFromUTF8(propvalue); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mod->SetUpModificationOneValue(aType, attr, value); + NS_ENSURE_SUCCESS(rv, rv); + + printf("LDAP : setting attribute %s (%s) to '%s'\n", attr.get(), + props[i], propvalue.get()); + modArray->AppendElement(mod, false); + if (index != m_attributes.NoIndex) + m_attributes.AppendElement(attr); + + } + else if (aType == nsILDAPModification::MOD_REPLACE && + index != m_attributes.NoIndex) + { + // If the new value is empty, we are performing an update + // and the attribute was previously set, clear it + nsCOMPtr<nsIMutableArray> novalues = + do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mod->SetUpModification(aType, attr, novalues); + NS_ENSURE_SUCCESS(rv, rv); + + printf("LDAP : removing attribute %s (%s)\n", attr.get(), props[i]); + modArray->AppendElement(mod, false); + m_attributes.RemoveElementAt(index); + } + } + + NS_ADDREF(*aLDAPAddMessageInfo = modArray); + + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPCard::BuildRdn(nsIAbLDAPAttributeMap *aAttributeMap, + const uint32_t aAttrCount, + const char **aAttributes, + nsACString &aRdn) +{ + NS_ENSURE_ARG_POINTER(aAttributeMap); + NS_ENSURE_ARG_POINTER(aAttributes); + + nsresult rv; + nsCString attr; + nsAutoCString prop; + nsCString propvalue; + + aRdn.Truncate(); + for (uint32_t i = 0; i < aAttrCount; ++i) + { + attr.Assign(nsDependentCString(aAttributes[i])); + + // Lookup the property corresponding to the attribute + rv = aAttributeMap->GetProperty(attr, prop); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the property value + rv = GetPropertyAsAUTF8String(prop.get(), propvalue); + + // XXX The case where an attribute needed to build the Relative + // Distinguished Name is not set needs to be handled by the caller, + // so as to let the user know what is missing. + if (NS_FAILED(rv) || propvalue.IsEmpty()) + { + NS_ERROR("nsAbLDAPCard::BuildRdn: a required attribute is not set"); + return NS_ERROR_NOT_INITIALIZED; + } + + aRdn.Append(attr); + aRdn.AppendLiteral("="); + aRdn.Append(propvalue); + if (i < aAttrCount - 1) + aRdn.AppendLiteral("+"); + } + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPCard::GetDn(nsACString &aDN) +{ + return GetPropertyAsAUTF8String(kDNColumn, aDN); +} + +NS_IMETHODIMP nsAbLDAPCard::SetDn(const nsACString &aDN) +{ + SetLocalId(aDN); + return SetPropertyAsAUTF8String(kDNColumn, aDN); +} + +NS_IMETHODIMP nsAbLDAPCard::SetMetaProperties(nsILDAPMessage *aMessage) +{ + NS_ENSURE_ARG_POINTER(aMessage); + + // Get DN + nsAutoCString dn; + nsresult rv = aMessage->GetDn(dn); + NS_ENSURE_SUCCESS(rv, rv); + + SetDn(dn); + + // Get the list of set attributes + CharPtrArrayGuard attrs; + rv = aMessage->GetAttributes(attrs.GetSizeAddr(), attrs.GetArrayAddr()); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString attr; + m_attributes.Clear(); + for (uint32_t i = 0; i < attrs.GetSize(); ++i) + { + attr.Assign(nsDependentCString(attrs[i])); + ToLowerCase(attr); + m_attributes.AppendElement(attr); + } + + // Get the objectClass values + m_objectClass.Clear(); + PRUnicharPtrArrayGuard vals; + rv = aMessage->GetValues("objectClass", vals.GetSizeAddr(), + vals.GetArrayAddr()); + + // objectClass is not always included in search result entries and + // nsILDAPMessage::GetValues returns NS_ERROR_LDAP_DECODING_ERROR if the + // requested attribute doesn't exist. + if (rv == NS_ERROR_LDAP_DECODING_ERROR) + return NS_OK; + + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString oclass; + for (uint32_t i = 0; i < vals.GetSize(); ++i) + { + oclass.Assign(NS_LossyConvertUTF16toASCII(nsDependentString(vals[i]))); + ToLowerCase(oclass); + m_objectClass.AppendElement(oclass); + } + + return NS_OK; +} diff --git a/mailnews/addrbook/src/nsAbLDAPCard.h b/mailnews/addrbook/src/nsAbLDAPCard.h new file mode 100644 index 000000000..ef11b50e4 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPCard.h @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsAbLDAPCard_h__ +#define nsAbLDAPCard_h__ + +#include "nsAbCardProperty.h" +#include "nsIAbLDAPCard.h" +#include "nsTArray.h" + +class nsIMutableArray; + +class nsAbLDAPCard : public nsAbCardProperty, + public nsIAbLDAPCard +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIABLDAPCARD + + nsAbLDAPCard(); + +protected: + virtual ~nsAbLDAPCard(); + nsTArray<nsCString> m_attributes; + nsTArray<nsCString> m_objectClass; +}; + +#endif diff --git a/mailnews/addrbook/src/nsAbLDAPChangeLogData.cpp b/mailnews/addrbook/src/nsAbLDAPChangeLogData.cpp new file mode 100644 index 000000000..cc4c04250 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPChangeLogData.cpp @@ -0,0 +1,542 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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/. */ + +#include "nsAbLDAPChangeLogData.h" +#include "nsAbLDAPChangeLogQuery.h" +#include "nsILDAPMessage.h" +#include "nsIAbCard.h" +#include "nsIAddrBookSession.h" +#include "nsAbBaseCID.h" +#include "nsAbUtils.h" +#include "nsAbMDBCard.h" +#include "nsAbLDAPCard.h" +#include "nsIAuthPrompt.h" +#include "nsIStringBundle.h" +#include "nsIWindowWatcher.h" +#include "nsUnicharUtils.h" +#include "plstr.h" +#include "nsILDAPErrors.h" +#include "prmem.h" +#include "mozilla/Services.h" + +// Defined here since to be used +// only locally to this file. +enum UpdateOp { + NO_OP, + ENTRY_ADD, + ENTRY_DELETE, + ENTRY_MODIFY +}; + +nsAbLDAPProcessChangeLogData::nsAbLDAPProcessChangeLogData() +: mUseChangeLog(false), + mChangeLogEntriesCount(0), + mEntriesAddedQueryCount(0) +{ + mRootDSEEntry.firstChangeNumber = 0; + mRootDSEEntry.lastChangeNumber = 0; +} + +nsAbLDAPProcessChangeLogData::~nsAbLDAPProcessChangeLogData() +{ + +} + +NS_IMETHODIMP nsAbLDAPProcessChangeLogData::Init(nsIAbLDAPReplicationQuery * query, nsIWebProgressListener *progressListener) +{ + NS_ENSURE_ARG_POINTER(query); + + // Here we are assuming that the caller will pass a nsAbLDAPChangeLogQuery object, + // an implementation derived from the implementation of nsIAbLDAPReplicationQuery. + nsresult rv = NS_OK; + mChangeLogQuery = do_QueryInterface(query, &rv); + if(NS_FAILED(rv)) + return rv; + + // Call the parent's Init now. + return nsAbLDAPProcessReplicationData::Init(query, progressListener); +} + +nsresult nsAbLDAPProcessChangeLogData::OnLDAPBind(nsILDAPMessage *aMessage) +{ + NS_ENSURE_ARG_POINTER(aMessage); + if(!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + int32_t errCode; + + nsresult rv = aMessage->GetErrorCode(&errCode); + if(NS_FAILED(rv)) { + Done(false); + return rv; + } + + if(errCode != nsILDAPErrors::SUCCESS) { + Done(false); + return NS_ERROR_FAILURE; + } + + switch(mState) { + case kAnonymousBinding : + rv = GetAuthData(); + if(NS_SUCCEEDED(rv)) + rv = mChangeLogQuery->QueryAuthDN(mAuthUserID); + if(NS_SUCCEEDED(rv)) + mState = kSearchingAuthDN; + break; + case kAuthenticatedBinding : + rv = mChangeLogQuery->QueryRootDSE(); + if(NS_SUCCEEDED(rv)) + mState = kSearchingRootDSE; + break; + } //end of switch + + if(NS_FAILED(rv)) + Abort(); + + return rv; +} + +nsresult nsAbLDAPProcessChangeLogData::OnLDAPSearchEntry(nsILDAPMessage *aMessage) +{ + NS_ENSURE_ARG_POINTER(aMessage); + if(!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = NS_OK; + + switch(mState) + { + case kSearchingAuthDN : + { + nsAutoCString authDN; + rv = aMessage->GetDn(authDN); + if(NS_SUCCEEDED(rv) && !authDN.IsEmpty()) + mAuthDN = authDN.get(); + } + break; + case kSearchingRootDSE: + rv = ParseRootDSEEntry(aMessage); + break; + case kFindingChanges: + rv = ParseChangeLogEntries(aMessage); + break; + // Fall through since we only add (for updates we delete and add) + case kReplicatingChanges: + case kReplicatingAll : + return nsAbLDAPProcessReplicationData::OnLDAPSearchEntry(aMessage); + } + + if(NS_FAILED(rv)) + Abort(); + + return rv; +} + +nsresult nsAbLDAPProcessChangeLogData::OnLDAPSearchResult(nsILDAPMessage *aMessage) +{ + NS_ENSURE_ARG_POINTER(aMessage); + if (!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + int32_t errorCode; + + nsresult rv = aMessage->GetErrorCode(&errorCode); + + if(NS_SUCCEEDED(rv)) + { + if(errorCode == nsILDAPErrors::SUCCESS || errorCode == nsILDAPErrors::SIZELIMIT_EXCEEDED) { + switch(mState) { + case kSearchingAuthDN : + rv = OnSearchAuthDNDone(); + break; + case kSearchingRootDSE: + { + // Before starting the changeLog check the DB file, if its not there or bogus + // we need to create a new one and set to all. + nsCOMPtr<nsIAddrBookSession> abSession = do_GetService(NS_ADDRBOOKSESSION_CONTRACTID, &rv); + if (NS_FAILED(rv)) + break; + nsCOMPtr<nsIFile> dbPath; + rv = abSession->GetUserProfileDirectory(getter_AddRefs(dbPath)); + if (NS_FAILED(rv)) + break; + + nsAutoCString fileName; + rv = mDirectory->GetReplicationFileName(fileName); + if (NS_FAILED(rv)) + break; + + rv = dbPath->AppendNative(fileName); + if (NS_FAILED(rv)) + break; + + bool fileExists; + rv = dbPath->Exists(&fileExists); + if (NS_FAILED(rv)) + break; + + int64_t fileSize; + rv = dbPath->GetFileSize(&fileSize); + if(NS_FAILED(rv)) + break; + + if (!fileExists || !fileSize) + mUseChangeLog = false; + + // Open / create the AB here since it calls Done, + // just return from here. + if (mUseChangeLog) + rv = OpenABForReplicatedDir(false); + else + rv = OpenABForReplicatedDir(true); + if (NS_FAILED(rv)) + return rv; + + // Now start the appropriate query + rv = OnSearchRootDSEDone(); + break; + } + case kFindingChanges: + rv = OnFindingChangesDone(); + // If success we return from here since + // this changes state to kReplicatingChanges + // and it falls thru into the if clause below. + if (NS_SUCCEEDED(rv)) + return rv; + break; + case kReplicatingAll : + return nsAbLDAPProcessReplicationData::OnLDAPSearchResult(aMessage); + } // end of switch + } + else + rv = NS_ERROR_FAILURE; + // If one of the changed entry in changelog is not found, + // continue with replicating the next one. + if(mState == kReplicatingChanges) + rv = OnReplicatingChangeDone(); + } // end of outer if + + if(NS_FAILED(rv)) + Abort(); + + return rv; +} + +nsresult nsAbLDAPProcessChangeLogData::GetAuthData() +{ + if(!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + nsCOMPtr<nsIWindowWatcher> wwatch(do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + if (!wwatch) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIAuthPrompt> dialog; + nsresult rv = wwatch->GetNewAuthPrompter(0, getter_AddRefs(dialog)); + if (NS_FAILED(rv)) + return rv; + if (!dialog) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsILDAPURL> url; + rv = mQuery->GetReplicationURL(getter_AddRefs(url)); + if (NS_FAILED(rv)) + return rv; + + nsAutoCString serverUri; + rv = url->GetSpec(serverUri); + if (NS_FAILED(rv)) + return rv; + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", getter_AddRefs(bundle)); + if (NS_FAILED (rv)) + return rv ; + + nsString title; + rv = bundle->GetStringFromName(u"AuthDlgTitle", getter_Copies(title)); + if (NS_FAILED (rv)) + return rv ; + + nsString desc; + rv = bundle->GetStringFromName(u"AuthDlgDesc", getter_Copies(desc)); + if (NS_FAILED (rv)) + return rv ; + + nsString username; + nsString password; + bool btnResult = false; + rv = dialog->PromptUsernameAndPassword(title, desc, + NS_ConvertUTF8toUTF16(serverUri).get(), + nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY, + getter_Copies(username), getter_Copies(password), + &btnResult); + if(NS_SUCCEEDED(rv) && btnResult) { + CopyUTF16toUTF8(username, mAuthUserID); + CopyUTF16toUTF8(password, mAuthPswd); + } + else + rv = NS_ERROR_FAILURE; + + return rv; +} + +nsresult nsAbLDAPProcessChangeLogData::OnSearchAuthDNDone() +{ + if (!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + nsCOMPtr<nsILDAPURL> url; + nsresult rv = mQuery->GetReplicationURL(getter_AddRefs(url)); + if(NS_SUCCEEDED(rv)) + rv = mQuery->ConnectToLDAPServer(url, mAuthDN); + if(NS_SUCCEEDED(rv)) { + mState = kAuthenticatedBinding; + rv = mDirectory->SetAuthDn(mAuthDN); + } + + return rv; +} + +nsresult nsAbLDAPProcessChangeLogData::ParseRootDSEEntry(nsILDAPMessage *aMessage) +{ + NS_ENSURE_ARG_POINTER(aMessage); + if (!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + // Populate the RootDSEChangeLogEntry + CharPtrArrayGuard attrs; + nsresult rv = aMessage->GetAttributes(attrs.GetSizeAddr(), attrs.GetArrayAddr()); + // No attributes + if(NS_FAILED(rv)) + return rv; + + for(int32_t i=attrs.GetSize()-1; i >= 0; i--) { + PRUnicharPtrArrayGuard vals; + rv = aMessage->GetValues(attrs.GetArray()[i], vals.GetSizeAddr(), vals.GetArrayAddr()); + if(NS_FAILED(rv)) + continue; + if(vals.GetSize()) { + if (!PL_strcasecmp(attrs[i], "changelog")) + CopyUTF16toUTF8(vals[0], mRootDSEEntry.changeLogDN); + if (!PL_strcasecmp(attrs[i], "firstChangeNumber")) + mRootDSEEntry.firstChangeNumber = atol(NS_LossyConvertUTF16toASCII(vals[0]).get()); + if (!PL_strcasecmp(attrs[i], "lastChangeNumber")) + mRootDSEEntry.lastChangeNumber = atol(NS_LossyConvertUTF16toASCII(vals[0]).get()); + if (!PL_strcasecmp(attrs[i], "dataVersion")) + CopyUTF16toUTF8(vals[0], mRootDSEEntry.dataVersion); + } + } + + int32_t lastChangeNumber; + mDirectory->GetLastChangeNumber(&lastChangeNumber); + + if ((mRootDSEEntry.lastChangeNumber > 0) && + (lastChangeNumber < mRootDSEEntry.lastChangeNumber) && + (lastChangeNumber > mRootDSEEntry.firstChangeNumber)) + mUseChangeLog = true; + + if (mRootDSEEntry.lastChangeNumber && + (lastChangeNumber == mRootDSEEntry.lastChangeNumber)) { + Done(true); // We are up to date no need to replicate, db not open yet so call Done + return NS_OK; + } + + return rv; +} + +nsresult nsAbLDAPProcessChangeLogData::OnSearchRootDSEDone() +{ + if (!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = NS_OK; + + if(mUseChangeLog) { + rv = mChangeLogQuery->QueryChangeLog(mRootDSEEntry.changeLogDN, mRootDSEEntry.lastChangeNumber); + if (NS_FAILED(rv)) + return rv; + mState = kFindingChanges; + if(mListener) + mListener->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_START, false); + } + else { + rv = mQuery->QueryAllEntries(); + if (NS_FAILED(rv)) + return rv; + mState = kReplicatingAll; + if(mListener) + mListener->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_START, true); + } + + rv = mDirectory->SetLastChangeNumber(mRootDSEEntry.lastChangeNumber); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mDirectory->SetDataVersion(mRootDSEEntry.dataVersion); + + return rv; +} + +nsresult nsAbLDAPProcessChangeLogData::ParseChangeLogEntries(nsILDAPMessage *aMessage) +{ + NS_ENSURE_ARG_POINTER(aMessage); + if(!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + // Populate the RootDSEChangeLogEntry + CharPtrArrayGuard attrs; + nsresult rv = aMessage->GetAttributes(attrs.GetSizeAddr(), attrs.GetArrayAddr()); + // No attributes + if(NS_FAILED(rv)) + return rv; + + nsAutoString targetDN; + UpdateOp operation = NO_OP; + for(int32_t i = attrs.GetSize()-1; i >= 0; i--) { + PRUnicharPtrArrayGuard vals; + rv = aMessage->GetValues(attrs.GetArray()[i], vals.GetSizeAddr(), vals.GetArrayAddr()); + if(NS_FAILED(rv)) + continue; + if(vals.GetSize()) { + if (!PL_strcasecmp(attrs[i], "targetdn")) + targetDN = vals[0]; + if (!PL_strcasecmp(attrs[i], "changetype")) { + if (!Compare(nsDependentString(vals[0]), NS_LITERAL_STRING("add"), nsCaseInsensitiveStringComparator())) + operation = ENTRY_ADD; + if (!Compare(nsDependentString(vals[0]), NS_LITERAL_STRING("modify"), nsCaseInsensitiveStringComparator())) + operation = ENTRY_MODIFY; + if (!Compare(nsDependentString(vals[0]), NS_LITERAL_STRING("delete"), nsCaseInsensitiveStringComparator())) + operation = ENTRY_DELETE; + } + } + } + + mChangeLogEntriesCount++; + if(!(mChangeLogEntriesCount % 10)) { // Inform the listener every 10 entries + mListener->OnProgressChange(nullptr,nullptr,mChangeLogEntriesCount, -1, mChangeLogEntriesCount, -1); + // In case if the LDAP Connection thread is starved and causes problem + // uncomment this one and try. + // PR_Sleep(PR_INTERVAL_NO_WAIT); // give others a chance + } + +#ifdef DEBUG_rdayal + printf ("ChangeLog Replication : Updated Entry : %s for OpType : %u\n", + NS_ConvertUTF16toUTF8(targetDN).get(), operation); +#endif + + switch(operation) { + case ENTRY_ADD: + // Add the DN to the add list if not already in the list + if(!(mEntriesToAdd.IndexOf(targetDN) >= 0)) + mEntriesToAdd.AppendString(targetDN); + break; + case ENTRY_DELETE: + // Do not check the return here since delete may fail if + // entry deleted in changelog does not exist in DB + // for e.g if the user specifies a filter, so go next entry + DeleteCard(targetDN); + break; + case ENTRY_MODIFY: + // For modify, delete the entry from DB and add updated entry + // we do this since we cannot access the changes attribs of changelog + rv = DeleteCard(targetDN); + if (NS_SUCCEEDED(rv)) + if(!(mEntriesToAdd.IndexOf(targetDN) >= 0)) + mEntriesToAdd.AppendString(targetDN); + break; + default: + // Should not come here, would come here only + // if the entry is not a changeLog entry + NS_WARNING("nsAbLDAPProcessChangeLogData::ParseChangeLogEntries" + "Not an changelog entry"); + } + + // Go ahead processing the next entry, a modify or delete DB operation + // can 'correctly' fail if the entry is not present in the DB, + // e.g. in case a filter is specified. + return NS_OK; +} + +nsresult nsAbLDAPProcessChangeLogData::OnFindingChangesDone() +{ + if(!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + +#ifdef DEBUG_rdayal + printf ("ChangeLog Replication : Finding Changes Done \n"); +#endif + + nsresult rv = NS_OK; + + // No entries to add/update (for updates too we delete and add) entries, + // we took care of deletes in ParseChangeLogEntries, all Done! + mEntriesAddedQueryCount = mEntriesToAdd.Count(); + if(mEntriesAddedQueryCount <= 0) { + if(mReplicationDB && mDBOpen) { + // Close the DB, no need to commit since we have not made + // any changes yet to the DB. + rv = mReplicationDB->Close(false); + NS_ASSERTION(NS_SUCCEEDED(rv), "Replication DB Close(no commit) on Success failed"); + mDBOpen = false; + // Once are done with the replication file, delete the backup file + if(mBackupReplicationFile) { + rv = mBackupReplicationFile->Remove(false); + NS_ASSERTION(NS_SUCCEEDED(rv), "Replication BackupFile Remove on Success failed"); + } + } + Done(true); + return NS_OK; + } + + // Decrement the count first to get the correct array element + mEntriesAddedQueryCount--; + rv = mChangeLogQuery->QueryChangedEntries(NS_ConvertUTF16toUTF8(*(mEntriesToAdd[mEntriesAddedQueryCount]))); + if (NS_FAILED(rv)) + return rv; + + if(mListener && NS_SUCCEEDED(rv)) + mListener->OnStateChange(nullptr, nullptr, nsIWebProgressListener::STATE_START, true); + + mState = kReplicatingChanges; + return rv; +} + +nsresult nsAbLDAPProcessChangeLogData::OnReplicatingChangeDone() +{ + if(!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = NS_OK; + + if(!mEntriesAddedQueryCount) + { + if(mReplicationDB && mDBOpen) { + rv = mReplicationDB->Close(true); // Commit and close the DB + NS_ASSERTION(NS_SUCCEEDED(rv), "Replication DB Close (commit) on Success failed"); + mDBOpen = false; + } + // Once we done with the replication file, delete the backup file. + if(mBackupReplicationFile) { + rv = mBackupReplicationFile->Remove(false); + NS_ASSERTION(NS_SUCCEEDED(rv), "Replication BackupFile Remove on Success failed"); + } + Done(true); // All data is received + return NS_OK; + } + + // Remove the entry already added from the list and query the next one. + if(mEntriesAddedQueryCount < mEntriesToAdd.Count() && mEntriesAddedQueryCount >= 0) + mEntriesToAdd.RemoveStringAt(mEntriesAddedQueryCount); + mEntriesAddedQueryCount--; + rv = mChangeLogQuery->QueryChangedEntries(NS_ConvertUTF16toUTF8(*(mEntriesToAdd[mEntriesAddedQueryCount]))); + + return rv; +} + diff --git a/mailnews/addrbook/src/nsAbLDAPChangeLogData.h b/mailnews/addrbook/src/nsAbLDAPChangeLogData.h new file mode 100644 index 000000000..9300e34ec --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPChangeLogData.h @@ -0,0 +1,57 @@ +/* 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/. */ + +#ifndef nsAbLDAPChangeLogData_h__ +#define nsAbLDAPChangeLogData_h__ + +#include "mozilla/Attributes.h" +#include "nsAbLDAPReplicationData.h" +#include "nsAbLDAPChangeLogQuery.h" + +typedef struct { + nsCString changeLogDN; + int32_t firstChangeNumber; + int32_t lastChangeNumber; + nsCString dataVersion; +} RootDSEChangeLogEntry; + +class nsAbLDAPProcessChangeLogData : public nsAbLDAPProcessReplicationData +{ +public : + + nsAbLDAPProcessChangeLogData(); + + NS_IMETHOD Init(nsIAbLDAPReplicationQuery * query, nsIWebProgressListener *progressListener); + +protected : + ~nsAbLDAPProcessChangeLogData(); + + nsCOMPtr <nsIAbLDAPChangeLogQuery> mChangeLogQuery; + + nsresult OnLDAPBind(nsILDAPMessage *aMessage); + nsresult OnLDAPSearchEntry(nsILDAPMessage *aMessage) override; + nsresult OnLDAPSearchResult(nsILDAPMessage *aMessage) override; + + nsresult ParseChangeLogEntries(nsILDAPMessage *aMessage); + nsresult ParseRootDSEEntry(nsILDAPMessage *aMessage); + + nsresult GetAuthData(); // displays username and password prompt + nsCString mAuthUserID; // user id of the user making the connection + + nsresult OnSearchAuthDNDone(); + nsresult OnSearchRootDSEDone(); + nsresult OnFindingChangesDone(); + nsresult OnReplicatingChangeDone(); + + RootDSEChangeLogEntry mRootDSEEntry; + bool mUseChangeLog; + int32_t mChangeLogEntriesCount; + + int32_t mEntriesAddedQueryCount; + nsStringArray mEntriesToAdd; +}; + + +#endif // nsAbLDAPChangeLogData_h__ + diff --git a/mailnews/addrbook/src/nsAbLDAPChangeLogQuery.cpp b/mailnews/addrbook/src/nsAbLDAPChangeLogQuery.cpp new file mode 100644 index 000000000..1bdf474d6 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPChangeLogQuery.cpp @@ -0,0 +1,180 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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/. */ + + +#include "nsCOMPtr.h" +#include "nsAbLDAPChangeLogQuery.h" +#include "nsAbLDAPReplicationService.h" +#include "nsAbLDAPChangeLogData.h" +#include "nsAbUtils.h" +#include "prprf.h" +#include "nsDirPrefs.h" +#include "nsAbBaseCID.h" + + +// The tables below were originally in nsAbLDAPProperties.cpp, which has since +// gone away. +static const char * sChangeLogRootDSEAttribs[] = +{ + "changelog", + "firstChangeNumber", + "lastChangeNumber", + "dataVersion" +}; +static const char * sChangeLogEntryAttribs[] = +{ + "targetdn", + "changetype" +}; + + +NS_IMPL_ISUPPORTS_INHERITED(nsAbLDAPChangeLogQuery, nsAbLDAPReplicationQuery, nsIAbLDAPChangeLogQuery) + +nsAbLDAPChangeLogQuery::nsAbLDAPChangeLogQuery() +{ +} + +nsAbLDAPChangeLogQuery::~nsAbLDAPChangeLogQuery() +{ + +} + +// this is to be defined only till this is not hooked to SSL to get authDN and authPswd +#define USE_AUTHDLG + +NS_IMETHODIMP nsAbLDAPChangeLogQuery::Init(const nsACString & aPrefName, nsIWebProgressListener *aProgressListener) +{ + if(aPrefName.IsEmpty()) + return NS_ERROR_UNEXPECTED; + + mDirPrefName = aPrefName; + + nsresult rv = InitLDAPData(); + if(NS_FAILED(rv)) + return rv; + + // create the ChangeLog Data Processor + mDataProcessor = do_CreateInstance(NS_ABLDAP_PROCESSCHANGELOGDATA_CONTRACTID, &rv); + if(NS_FAILED(rv)) + return rv; + + // 'this' initialized + mInitialized = true; + + return mDataProcessor->Init(this, aProgressListener); +} + +NS_IMETHODIMP nsAbLDAPChangeLogQuery::DoReplicationQuery() +{ + if(!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + +#ifdef USE_AUTHDLG + return ConnectToLDAPServer(mURL, EmptyCString()); +#else + mDataProcessor->PopulateAuthData(); + return ConnectToLDAPServer(mURL, mAuthDN); +#endif +} + +NS_IMETHODIMP nsAbLDAPChangeLogQuery::QueryAuthDN(const nsACString & aValueUsedToFindDn) +{ + if (!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + nsCOMPtr<nsIAbLDAPAttributeMap> attrMap; + nsresult rv = mDirectory->GetAttributeMap(getter_AddRefs(attrMap)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString filter; + rv = attrMap->GetFirstAttribute(NS_LITERAL_CSTRING("PrimaryEmail"), filter); + NS_ENSURE_SUCCESS(rv, rv); + + filter += '='; + filter += aValueUsedToFindDn; + + nsAutoCString dn; + rv = mURL->GetDn(dn); + if(NS_FAILED(rv)) + return rv; + + rv = CreateNewLDAPOperation(); + NS_ENSURE_SUCCESS(rv, rv); + + // XXX We really should be using LDAP_NO_ATTRS here once its exposed via + // the XPCOM layer of the directory code. + return mOperation->SearchExt(dn, nsILDAPURL::SCOPE_SUBTREE, filter, + 0, nullptr, + 0, 0); +} + +NS_IMETHODIMP nsAbLDAPChangeLogQuery::QueryRootDSE() +{ + if(!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = CreateNewLDAPOperation(); + NS_ENSURE_SUCCESS(rv, rv); + return mOperation->SearchExt(EmptyCString(), nsILDAPURL::SCOPE_BASE, + NS_LITERAL_CSTRING("objectclass=*"), + sizeof(sChangeLogRootDSEAttribs), + sChangeLogRootDSEAttribs, 0, 0); +} + +NS_IMETHODIMP nsAbLDAPChangeLogQuery::QueryChangeLog(const nsACString & aChangeLogDN, int32_t aLastChangeNo) +{ + if (!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + if (aChangeLogDN.IsEmpty()) + return NS_ERROR_UNEXPECTED; + + int32_t lastChangeNumber; + nsresult rv = mDirectory->GetLastChangeNumber(&lastChangeNumber); + NS_ENSURE_SUCCESS(rv, rv); + + // make sure that the filter here just have one condition + // and should not be enclosed in enclosing brackets. + // also condition '>' doesnot work, it should be '>='/ + nsAutoCString filter (NS_LITERAL_CSTRING("changenumber>=")); + filter.AppendInt(lastChangeNumber + 1); + + rv = CreateNewLDAPOperation(); + NS_ENSURE_SUCCESS(rv, rv); + + return mOperation->SearchExt(aChangeLogDN, nsILDAPURL::SCOPE_ONELEVEL, filter, + sizeof(sChangeLogEntryAttribs), + sChangeLogEntryAttribs, 0, 0); +} + +NS_IMETHODIMP nsAbLDAPChangeLogQuery::QueryChangedEntries(const nsACString & aChangedEntryDN) +{ + if(!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + if(aChangedEntryDN.IsEmpty()) + return NS_ERROR_UNEXPECTED; + + nsAutoCString urlFilter; + nsresult rv = mURL->GetFilter(urlFilter); + if(NS_FAILED(rv)) + return rv; + + int32_t scope; + rv = mURL->GetScope(&scope); + if(NS_FAILED(rv)) + return rv; + + CharPtrArrayGuard attributes; + rv = mURL->GetAttributes(attributes.GetSizeAddr(), attributes.GetArrayAddr()); + if(NS_FAILED(rv)) + return rv; + + rv = CreateNewLDAPOperation(); + NS_ENSURE_SUCCESS(rv, rv); + return mOperation->SearchExt(aChangedEntryDN, scope, urlFilter, + attributes.GetSize(), attributes.GetArray(), + 0, 0); +} + diff --git a/mailnews/addrbook/src/nsAbLDAPChangeLogQuery.h b/mailnews/addrbook/src/nsAbLDAPChangeLogQuery.h new file mode 100644 index 000000000..9652f470e --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPChangeLogQuery.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * 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/. */ + +#ifndef nsAbLDAPChangeLogQuery_h__ +#define nsAbLDAPChangeLogQuery_h__ + +#include "mozilla/Attributes.h" +#include "nsAbLDAPReplicationQuery.h" +#include "nsStringGlue.h" + +class nsAbLDAPChangeLogQuery : public nsIAbLDAPChangeLogQuery, + public nsAbLDAPReplicationQuery +{ +public : + NS_DECL_ISUPPORTS + NS_DECL_NSIABLDAPCHANGELOGQUERY + + nsAbLDAPChangeLogQuery(); + virtual ~nsAbLDAPChangeLogQuery(); + + NS_IMETHOD DoReplicationQuery() override; + NS_IMETHOD Init(const nsACString & aPrefName, nsIWebProgressListener *aProgressListener); +}; + +#endif // nsAbLDAPChangeLogQuery_h__ diff --git a/mailnews/addrbook/src/nsAbLDAPDirFactory.cpp b/mailnews/addrbook/src/nsAbLDAPDirFactory.cpp new file mode 100644 index 000000000..299f2a953 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPDirFactory.cpp @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbLDAPDirFactory.h" +#include "nsAbUtils.h" + +#include "nsServiceManagerUtils.h" +#include "nsIAbManager.h" +#include "nsIAbDirectory.h" +#include "nsAbLDAPDirectory.h" + +#include "nsEnumeratorUtils.h" +#include "nsAbBaseCID.h" + +NS_IMPL_ISUPPORTS(nsAbLDAPDirFactory, nsIAbDirFactory) + +nsAbLDAPDirFactory::nsAbLDAPDirFactory() +{ +} + +nsAbLDAPDirFactory::~nsAbLDAPDirFactory() +{ +} + +NS_IMETHODIMP +nsAbLDAPDirFactory::GetDirectories(const nsAString &aDirName, + const nsACString &aURI, + const nsACString &aPrefName, + nsISimpleEnumerator **aDirectories) +{ + NS_ENSURE_ARG_POINTER(aDirectories); + + nsresult rv; + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> directory; + if (Substring(aURI, 0, 5).EqualsLiteral("ldap:") || + Substring(aURI, 0, 6).EqualsLiteral("ldaps:")) { + /* + * If the URI starts with ldap: or ldaps: + * then this directory is an LDAP directory. + * + * We don't want to use the ldap:// or ldaps:// URI + * as the URI because the ldap:// or ldaps:// URI + * will contain the hostname, basedn, port, etc. + * so if those attributes changed, we'll run into the + * the same problem that we hit with changing username / hostname + * for mail servers. To solve this problem, we add an extra + * level of indirection. The URI that we generate + * (the bridge URI) will be moz-abldapdirectory://<prefName> + * and when we need the hostname, basedn, port, etc, + * we'll use the <prefName> to get the necessary prefs. + * note, <prefName> does not change. + */ + nsAutoCString bridgeURI; + bridgeURI = NS_LITERAL_CSTRING(kLDAPDirectoryRoot); + bridgeURI += aPrefName; + rv = abManager->GetDirectory(bridgeURI, getter_AddRefs(directory)); + } + else { + rv = abManager->GetDirectory(aURI, getter_AddRefs(directory)); + } + NS_ENSURE_SUCCESS(rv, rv); + + return NS_NewSingletonEnumerator(aDirectories, directory); +} + +/* void deleteDirectory (in nsIAbDirectory directory); */ +NS_IMETHODIMP +nsAbLDAPDirFactory::DeleteDirectory(nsIAbDirectory *directory) +{ + // No actual deletion - as the LDAP Address Book is not physically + // created in the corresponding CreateDirectory() unlike the Personal + // Address Books. But we still need to return NS_OK from here. + return NS_OK; +} diff --git a/mailnews/addrbook/src/nsAbLDAPDirFactory.h b/mailnews/addrbook/src/nsAbLDAPDirFactory.h new file mode 100644 index 000000000..9285fbc05 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPDirFactory.h @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsAbLDAPDirFactory_h__ +#define nsAbLDAPDirFactory_h__ + +#include "nsIAbDirFactory.h" + +class nsAbLDAPDirFactory : public nsIAbDirFactory +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIABDIRFACTORY + + nsAbLDAPDirFactory(); + +private: + virtual ~nsAbLDAPDirFactory(); +}; + +#endif diff --git a/mailnews/addrbook/src/nsAbLDAPDirectory.cpp b/mailnews/addrbook/src/nsAbLDAPDirectory.cpp new file mode 100644 index 000000000..d1bb484c0 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPDirectory.cpp @@ -0,0 +1,948 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbLDAPDirectory.h" + +#include "nsAbQueryStringToExpression.h" + +#include "nsAbBaseCID.h" +#include "nsIAbManager.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsNetCID.h" +#include "nsIIOService.h" +#include "nsCOMArray.h" +#include "nsArrayEnumerator.h" +#include "nsEnumeratorUtils.h" +#include "nsIAbLDAPAttributeMap.h" +#include "nsIAbMDBDirectory.h" +#include "nsILDAPURL.h" +#include "nsILDAPConnection.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsIFile.h" +#include "nsILDAPModification.h" +#include "nsILDAPService.h" +#include "nsIAbLDAPCard.h" +#include "nsAbUtils.h" +#include "nsArrayUtils.h" +#include "nsIPrefService.h" +#include "nsIMsgAccountManager.h" +#include "nsMsgBaseCID.h" +#include "nsMsgUtils.h" +#include "mozilla/Services.h" + +#define kDefaultMaxHits 100 + +using namespace mozilla; + +nsAbLDAPDirectory::nsAbLDAPDirectory() : + nsAbDirProperty(), + mPerformingQuery(false), + mContext(0), + mLock("nsAbLDAPDirectory.mLock") +{ +} + +nsAbLDAPDirectory::~nsAbLDAPDirectory() +{ +} + +NS_IMPL_ISUPPORTS_INHERITED(nsAbLDAPDirectory, nsAbDirProperty, + nsISupportsWeakReference, nsIAbDirSearchListener, + nsIAbLDAPDirectory) + +NS_IMETHODIMP nsAbLDAPDirectory::GetPropertiesChromeURI(nsACString &aResult) +{ + aResult.AssignLiteral("chrome://messenger/content/addressbook/pref-directory-add.xul"); + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::Init(const char* aURI) +{ + // We need to ensure that the m_DirPrefId is initialized properly + nsAutoCString uri(aURI); + + // Find the first ? (of the search params) if there is one. + // We know we can start at the end of the moz-abldapdirectory:// because + // that's the URI we should have been passed. + int32_t searchCharLocation = uri.FindChar('?', kLDAPDirectoryRootLen); + + if (searchCharLocation == -1) + m_DirPrefId = Substring(uri, kLDAPDirectoryRootLen); + else + m_DirPrefId = Substring(uri, kLDAPDirectoryRootLen, searchCharLocation - kLDAPDirectoryRootLen); + + return nsAbDirProperty::Init(aURI); +} + +nsresult nsAbLDAPDirectory::Initiate() +{ + return NS_OK; +} + +/* + * + * nsIAbDirectory methods + * + */ + +NS_IMETHODIMP nsAbLDAPDirectory::GetURI(nsACString &aURI) +{ + if (mURI.IsEmpty()) + return NS_ERROR_NOT_INITIALIZED; + + aURI = mURI; + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetChildNodes(nsISimpleEnumerator* *aResult) +{ + return NS_NewEmptyEnumerator(aResult); +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetChildCards(nsISimpleEnumerator** result) +{ + nsresult rv; + + // when offline, we need to get the child cards for the local, replicated mdb directory + bool offline; + nsCOMPtr <nsIIOService> ioService = + mozilla::services::GetIOService(); + NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED); + rv = ioService->GetOffline(&offline); + NS_ENSURE_SUCCESS(rv,rv); + + if (offline) { + nsCString fileName; + rv = GetReplicationFileName(fileName); + NS_ENSURE_SUCCESS(rv,rv); + + // if there is no fileName, bail out now. + if (fileName.IsEmpty()) + return NS_OK; + + // perform the same query, but on the local directory + nsAutoCString localDirectoryURI(NS_LITERAL_CSTRING(kMDBDirectoryRoot)); + localDirectoryURI.Append(fileName); + if (mIsQueryURI) + { + localDirectoryURI.AppendLiteral("?"); + localDirectoryURI.Append(mQueryString); + } + + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIAbDirectory> directory; + rv = abManager->GetDirectory(localDirectoryURI, + getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = directory->GetChildCards(result); + } + else { + // Start the search + rv = StartSearch(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_NewEmptyEnumerator(result); + } + + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetIsQuery(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mIsQueryURI; + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::HasCard(nsIAbCard* card, bool* hasCard) +{ + nsresult rv = Initiate (); + NS_ENSURE_SUCCESS(rv, rv); + + // Enter lock + MutexAutoLock lock (mLock); + + *hasCard = mCache.Get(card, nullptr); + if (!*hasCard && mPerformingQuery) + return NS_ERROR_NOT_AVAILABLE; + + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetLDAPURL(nsILDAPURL** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + // Rather than using GetURI here we call GetStringValue directly so + // we can handle the case where the URI isn't specified (see comments + // below) + nsAutoCString URI; + nsresult rv = GetStringValue("uri", EmptyCString(), URI); + if (NS_FAILED(rv) || URI.IsEmpty()) + { + /* + * A recent change in Mozilla now means that the LDAP Address Book + * URI is based on the unique preference name value i.e. + * [moz-abldapdirectory://prefName] + * Prior to this valid change it was based on the actual uri i.e. + * [moz-abldapdirectory://host:port/basedn] + * Basing the resource on the prefName allows these attributes to + * change. + * + * But the uri value was also the means by which third-party + * products could integrate with Mozilla's LDAP Address Books + * without necessarily having an entry in the preferences file + * or more importantly needing to be able to change the + * preferences entries. Thus to set the URI Spec now, it is + * only necessary to read the uri pref entry, while in the + * case where it is not a preference, we need to replace the + * "moz-abldapdirectory". + */ + URI = mURINoQuery; + if (StringBeginsWith(URI, NS_LITERAL_CSTRING(kLDAPDirectoryRoot))) + URI.Replace(0, kLDAPDirectoryRootLen, NS_LITERAL_CSTRING("ldap://")); + } + + nsCOMPtr<nsIIOService> ioService = + mozilla::services::GetIOService(); + NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIURI> result; + rv = ioService->NewURI(URI, nullptr, nullptr, getter_AddRefs(result)); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(result, aResult); +} + +NS_IMETHODIMP nsAbLDAPDirectory::SetLDAPURL(nsILDAPURL *aUrl) +{ + NS_ENSURE_ARG_POINTER(aUrl); + + nsAutoCString oldUrl; + // Note, it doesn't matter if GetStringValue fails - we'll just send an + // update if its blank (i.e. old value not set). + GetStringValue("uri", EmptyCString(), oldUrl); + + // Actually set the new value. + nsCString tempLDAPURL; + nsresult rv = aUrl->GetSpec(tempLDAPURL); + NS_ENSURE_SUCCESS(rv, rv); + + rv = SetStringValue("uri", tempLDAPURL); + NS_ENSURE_SUCCESS(rv, rv); + + // Now we need to send an update which will ensure our indicators and + // listeners get updated correctly. + + // See if they both start with ldaps: or ldap: + bool newIsNotSecure = StringHead(tempLDAPURL, 5).Equals("ldap:"); + + if (oldUrl.IsEmpty() || + StringHead(oldUrl, 5).Equals("ldap:") != newIsNotSecure) + { + // They don't so its time to send round an update. + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // We inherit from nsIAbDirectory, so this static cast should be safe. + abManager->NotifyItemPropertyChanged(static_cast<nsIAbDirectory*>(this), + "IsSecure", + (newIsNotSecure ? u"true" : u"false"), + (newIsNotSecure ? u"false" : u"true")); + } + + return NS_OK; +} + +/* + * + * nsIAbDirectorySearch methods + * + */ + +NS_IMETHODIMP nsAbLDAPDirectory::StartSearch () +{ + if (!mIsQueryURI || mQueryString.IsEmpty()) + return NS_OK; + + nsresult rv = Initiate(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = StopSearch(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectoryQueryArguments> arguments = do_CreateInstance(NS_ABDIRECTORYQUERYARGUMENTS_CONTRACTID,&rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbBooleanExpression> expression; + rv = nsAbQueryStringToExpression::Convert(mQueryString, + getter_AddRefs(expression)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = arguments->SetExpression(expression); + NS_ENSURE_SUCCESS(rv, rv); + + rv = arguments->SetQuerySubDirectories(true); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the max hits to return + int32_t maxHits; + rv = GetMaxHits(&maxHits); + if (NS_FAILED(rv)) + maxHits = kDefaultMaxHits; + + // get the appropriate ldap attribute map, and pass it in via the + // TypeSpecificArgument + nsCOMPtr<nsIAbLDAPAttributeMap> attrMap; + rv = GetAttributeMap(getter_AddRefs(attrMap)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupports> typeSpecificArg = do_QueryInterface(attrMap, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = arguments->SetTypeSpecificArg(attrMap); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mDirectoryQuery) + { + mDirectoryQuery = do_CreateInstance(NS_ABLDAPDIRECTORYQUERY_CONTRACTID, + &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Perform the query + rv = mDirectoryQuery->DoQuery(this, arguments, this, maxHits, 0, &mContext); + NS_ENSURE_SUCCESS(rv, rv); + + // Enter lock + MutexAutoLock lock(mLock); + mPerformingQuery = true; + mCache.Clear(); + + return rv; +} + +NS_IMETHODIMP nsAbLDAPDirectory::StopSearch () +{ + nsresult rv = Initiate(); + NS_ENSURE_SUCCESS(rv, rv); + + // Enter lock + { + MutexAutoLock lockGuard(mLock); + if (!mPerformingQuery) + return NS_OK; + mPerformingQuery = false; + } + // Exit lock + + if (!mDirectoryQuery) + return NS_ERROR_NULL_POINTER; + + return mDirectoryQuery->StopQuery(mContext); +} + +/* + * + * nsAbDirSearchListenerContext methods + * + */ +NS_IMETHODIMP nsAbLDAPDirectory::OnSearchFinished(int32_t aResult, const nsAString &aErrorMessage) +{ + nsresult rv = Initiate(); + NS_ENSURE_SUCCESS(rv, rv); + + MutexAutoLock lock(mLock); + mPerformingQuery = false; + + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::OnSearchFoundCard(nsIAbCard* card) +{ + nsresult rv = Initiate(); + NS_ENSURE_SUCCESS(rv, rv); + + // Enter lock + { + MutexAutoLock lock(mLock); + mCache.Put(card, card); + } + // Exit lock + + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + if(NS_SUCCEEDED(rv)) + abManager->NotifyDirectoryItemAdded(this, card); + + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetSupportsMailingLists(bool *aSupportsMailingsLists) +{ + NS_ENSURE_ARG_POINTER(aSupportsMailingsLists); + *aSupportsMailingsLists = false; + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetReadOnly(bool *aReadOnly) +{ + NS_ENSURE_ARG_POINTER(aReadOnly); + + *aReadOnly = true; + +#ifdef MOZ_EXPERIMENTAL_WRITEABLE_LDAP + bool readOnly; + nsresult rv = GetBoolValue("readonly", false, &readOnly); + NS_ENSURE_SUCCESS(rv, rv); + + if (readOnly) + return NS_OK; + + // when online, we'll allow writing as well + bool offline; + nsCOMPtr <nsIIOService> ioService = + mozilla::services::GetIOService(); + NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED); + + rv = ioService->GetOffline(&offline); + NS_ENSURE_SUCCESS(rv,rv); + + if (!offline) + *aReadOnly = false; +#endif + + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetIsRemote(bool *aIsRemote) +{ + NS_ENSURE_ARG_POINTER(aIsRemote); + *aIsRemote = true; + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetIsSecure(bool *aIsSecure) +{ + NS_ENSURE_ARG_POINTER(aIsSecure); + + nsAutoCString URI; + nsresult rv = GetStringValue("uri", EmptyCString(), URI); + NS_ENSURE_SUCCESS(rv, rv); + + // to determine if this is a secure directory, check if the uri is ldaps:// or not + *aIsSecure = (strncmp(URI.get(), "ldaps:", 6) == 0); + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::UseForAutocomplete(const nsACString &aIdentityKey, + bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + // Set this to false by default to make the code easier below. + *aResult = false; + + nsresult rv; + bool offline = false; + nsCOMPtr <nsIIOService> ioService = + mozilla::services::GetIOService(); + NS_ENSURE_TRUE(ioService, NS_ERROR_UNEXPECTED); + + rv = ioService->GetOffline(&offline); + NS_ENSURE_SUCCESS(rv, rv); + + // If we're online, then don't allow search during local autocomplete - must + // use the separate LDAP autocomplete session due to the current interfaces + if (!offline) + return NS_OK; + + // Is the use directory pref set for autocompletion? + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool useDirectory = false; + rv = prefs->GetBoolPref("ldap_2.autoComplete.useDirectory", &useDirectory); + NS_ENSURE_SUCCESS(rv, rv); + + // No need to search if not set up globally for LDAP autocompletion and we've + // not been given an identity. + if (!useDirectory && aIdentityKey.IsEmpty()) + return NS_OK; + + nsCString prefName; + if (!aIdentityKey.IsEmpty()) + { + // If we have an identity string, try and find out the required directory + // server. + nsCOMPtr<nsIMsgAccountManager> accountManager = + do_GetService(NS_MSGACCOUNTMANAGER_CONTRACTID, &rv); + + // If we failed, just return, we can't do much about this. + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIMsgIdentity> identity; + rv = accountManager->GetIdentity(aIdentityKey, getter_AddRefs(identity)); + if (NS_SUCCEEDED(rv)) + { + bool overrideGlobalPref = false; + identity->GetOverrideGlobalPref(&overrideGlobalPref); + if (overrideGlobalPref) + identity->GetDirectoryServer(prefName); + } + } + + // If the preference name is still empty but useDirectory is false, then + // the global one is not available, nor is the overriden one. + if (prefName.IsEmpty() && !useDirectory) + return NS_OK; + } + + // If we failed to get the identity preference, or the pref name is empty + // try the global preference. + if (prefName.IsEmpty()) + { + nsresult rv = prefs->GetCharPref("ldap_2.autoComplete.directoryServer", + getter_Copies(prefName)); + NS_ENSURE_SUCCESS(rv,rv); + } + + // Now see if the pref name matches our pref id. + if (prefName.Equals(m_DirPrefId)) + { + // Yes it does, one last check - does the replication file exist? + nsresult rv; + nsCOMPtr<nsIFile> databaseFile; + // If we can't get the file, then there is no database to use + if (NS_FAILED(GetReplicationFile(getter_AddRefs(databaseFile)))) + return NS_OK; + + bool exists; + rv = databaseFile->Exists(&exists); + NS_ENSURE_SUCCESS(rv, rv); + + *aResult = exists; + } + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetSearchClientControls(nsIMutableArray **aControls) +{ + NS_IF_ADDREF(*aControls = mSearchClientControls); + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::SetSearchClientControls(nsIMutableArray *aControls) +{ + mSearchClientControls = aControls; + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetSearchServerControls(nsIMutableArray **aControls) +{ + NS_IF_ADDREF(*aControls = mSearchServerControls); + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::SetSearchServerControls(nsIMutableArray *aControls) +{ + mSearchServerControls = aControls; + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetProtocolVersion(uint32_t *aProtocolVersion) +{ + nsAutoCString versionString; + + nsresult rv = GetStringValue("protocolVersion", NS_LITERAL_CSTRING("3"), versionString); + NS_ENSURE_SUCCESS(rv, rv); + + *aProtocolVersion = versionString.EqualsLiteral("3") ? + (uint32_t)nsILDAPConnection::VERSION3 : + (uint32_t)nsILDAPConnection::VERSION2; + + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::SetProtocolVersion(uint32_t aProtocolVersion) +{ + // XXX We should cancel any existing LDAP connections here and + // be ready to re-initialise them with the new auth details. + return SetStringValue("protocolVersion", + aProtocolVersion == nsILDAPConnection::VERSION3 ? + NS_LITERAL_CSTRING("3") : NS_LITERAL_CSTRING("2")); +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetMaxHits(int32_t *aMaxHits) +{ + return GetIntValue("maxHits", kDefaultMaxHits, aMaxHits); +} + +NS_IMETHODIMP nsAbLDAPDirectory::SetMaxHits(int32_t aMaxHits) +{ + return SetIntValue("maxHits", aMaxHits); +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetReplicationFileName(nsACString &aReplicationFileName) +{ + return GetStringValue("filename", EmptyCString(), aReplicationFileName); +} + +NS_IMETHODIMP nsAbLDAPDirectory::SetReplicationFileName(const nsACString &aReplicationFileName) +{ + return SetStringValue("filename", aReplicationFileName); +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetAuthDn(nsACString &aAuthDn) +{ + return GetStringValue("auth.dn", EmptyCString(), aAuthDn); +} + +NS_IMETHODIMP nsAbLDAPDirectory::SetAuthDn(const nsACString &aAuthDn) +{ + // XXX We should cancel any existing LDAP connections here and + // be ready to re-initialise them with the new auth details. + return SetStringValue("auth.dn", aAuthDn); +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetSaslMechanism(nsACString &aSaslMechanism) +{ + return GetStringValue("auth.saslmech", EmptyCString(), aSaslMechanism); +} + +NS_IMETHODIMP nsAbLDAPDirectory::SetSaslMechanism(const nsACString &aSaslMechanism) +{ + return SetStringValue("auth.saslmech", aSaslMechanism); +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetLastChangeNumber(int32_t *aLastChangeNumber) +{ + return GetIntValue("lastChangeNumber", -1, aLastChangeNumber); +} + +NS_IMETHODIMP nsAbLDAPDirectory::SetLastChangeNumber(int32_t aLastChangeNumber) +{ + return SetIntValue("lastChangeNumber", aLastChangeNumber); +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetDataVersion(nsACString &aDataVersion) +{ + return GetStringValue("dataVersion", EmptyCString(), aDataVersion); +} + +NS_IMETHODIMP nsAbLDAPDirectory::SetDataVersion(const nsACString &aDataVersion) +{ + return SetStringValue("dataVersion", aDataVersion); +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetAttributeMap(nsIAbLDAPAttributeMap **aAttributeMap) +{ + NS_ENSURE_ARG_POINTER(aAttributeMap); + + nsresult rv; + nsCOMPtr<nsIAbLDAPAttributeMapService> mapSvc = + do_GetService("@mozilla.org/addressbook/ldap-attribute-map-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return mapSvc->GetMapForPrefBranch(m_DirPrefId, aAttributeMap); +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetReplicationFile(nsIFile **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + nsCString fileName; + nsresult rv = GetStringValue("filename", EmptyCString(), fileName); + NS_ENSURE_SUCCESS(rv, rv); + + if (fileName.IsEmpty()) + return NS_ERROR_NOT_INITIALIZED; + + nsCOMPtr<nsIFile> profileDir; + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(profileDir)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = profileDir->AppendNative(fileName); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ADDREF(*aResult = profileDir); + + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetReplicationDatabase(nsIAddrDatabase **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + nsresult rv; + nsCOMPtr<nsIFile> databaseFile; + rv = GetReplicationFile(getter_AddRefs(databaseFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAddrDatabase> addrDBFactory = + do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return addrDBFactory->Open(databaseFile, false /* no create */, true, + aResult); +} + +NS_IMETHODIMP nsAbLDAPDirectory::AddCard(nsIAbCard *aUpdatedCard, + nsIAbCard **aAddedCard) +{ + NS_ENSURE_ARG_POINTER(aUpdatedCard); + NS_ENSURE_ARG_POINTER(aAddedCard); + + nsCOMPtr<nsIAbLDAPAttributeMap> attrMap; + nsresult rv = GetAttributeMap(getter_AddRefs(attrMap)); + NS_ENSURE_SUCCESS(rv, rv); + + // Create a new LDAP card + nsCOMPtr<nsIAbLDAPCard> card = + do_CreateInstance(NS_ABLDAPCARD_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = Initiate(); + NS_ENSURE_SUCCESS(rv, rv); + + // Copy over the card data + nsCOMPtr<nsIAbCard> copyToCard = do_QueryInterface(card, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = copyToCard->Copy(aUpdatedCard); + NS_ENSURE_SUCCESS(rv, rv); + + // Retrieve preferences + nsAutoCString prefString; + rv = GetRdnAttributes(prefString); + NS_ENSURE_SUCCESS(rv, rv); + + CharPtrArrayGuard rdnAttrs; + rv = SplitStringList(prefString, rdnAttrs.GetSizeAddr(), + rdnAttrs.GetArrayAddr()); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetObjectClasses(prefString); + NS_ENSURE_SUCCESS(rv, rv); + + CharPtrArrayGuard objClass; + rv = SplitStringList(prefString, objClass.GetSizeAddr(), + objClass.GetArrayAddr()); + NS_ENSURE_SUCCESS(rv, rv); + + // Process updates + nsCOMPtr<nsIArray> modArray; + rv = card->GetLDAPMessageInfo(attrMap, objClass.GetSize(), objClass.GetArray(), + nsILDAPModification::MOD_ADD, getter_AddRefs(modArray)); + NS_ENSURE_SUCCESS(rv, rv); + + // For new cards, the base DN is the search base DN + nsCOMPtr<nsILDAPURL> currentUrl; + rv = GetLDAPURL(getter_AddRefs(currentUrl)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString baseDN; + rv = currentUrl->GetDn(baseDN); + NS_ENSURE_SUCCESS(rv, rv); + + // Calculate DN + nsAutoCString cardDN; + rv = card->BuildRdn(attrMap, rdnAttrs.GetSize(), rdnAttrs.GetArray(), + cardDN); + NS_ENSURE_SUCCESS(rv, rv); + cardDN.AppendLiteral(","); + cardDN.Append(baseDN); + + rv = card->SetDn(cardDN); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString ourUuid; + GetUuid(ourUuid); + copyToCard->SetDirectoryId(ourUuid); + + // Launch query + rv = DoModify(this, nsILDAPModification::MOD_ADD, cardDN, modArray, + EmptyCString(), EmptyCString()); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ADDREF(*aAddedCard = copyToCard); + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::DeleteCards(nsIArray *aCards) +{ + uint32_t cardCount; + uint32_t i; + nsAutoCString cardDN; + + nsresult rv = aCards->GetLength(&cardCount); + NS_ENSURE_SUCCESS(rv, rv); + + for (i = 0; i < cardCount; ++i) + { + nsCOMPtr<nsIAbLDAPCard> card(do_QueryElementAt(aCards, i, &rv)); + if (NS_FAILED(rv)) + { + NS_WARNING("Wrong type of card passed to nsAbLDAPDirectory::DeleteCards"); + break; + } + + // Set up the search ldap url - this is mURL + rv = Initiate(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = card->GetDn(cardDN); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbCard> realCard(do_QueryInterface(card)); + realCard->SetDirectoryId(EmptyCString()); + + // Launch query + rv = DoModify(this, nsILDAPModification::MOD_DELETE, cardDN, nullptr, + EmptyCString(), EmptyCString()); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectory::ModifyCard(nsIAbCard *aUpdatedCard) +{ + NS_ENSURE_ARG_POINTER(aUpdatedCard); + + nsCOMPtr<nsIAbLDAPAttributeMap> attrMap; + nsresult rv = GetAttributeMap(getter_AddRefs(attrMap)); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the LDAP card + nsCOMPtr<nsIAbLDAPCard> card = do_QueryInterface(aUpdatedCard, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = Initiate(); + NS_ENSURE_SUCCESS(rv, rv); + + // Retrieve preferences + nsAutoCString prefString; + rv = GetObjectClasses(prefString); + NS_ENSURE_SUCCESS(rv, rv); + + CharPtrArrayGuard objClass; + rv = SplitStringList(prefString, objClass.GetSizeAddr(), + objClass.GetArrayAddr()); + NS_ENSURE_SUCCESS(rv, rv); + + // Process updates + nsCOMPtr<nsIArray> modArray; + rv = card->GetLDAPMessageInfo(attrMap, objClass.GetSize(), objClass.GetArray(), + nsILDAPModification::MOD_REPLACE, getter_AddRefs(modArray)); + NS_ENSURE_SUCCESS(rv, rv); + + // Get current DN + nsAutoCString oldDN; + rv = card->GetDn(oldDN); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILDAPService> ldapSvc = do_GetService( + "@mozilla.org/network/ldap-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Retrieve base DN and RDN attributes + nsAutoCString baseDN; + nsAutoCString oldRDN; + CharPtrArrayGuard rdnAttrs; + rv = ldapSvc->ParseDn(oldDN.get(), oldRDN, baseDN, + rdnAttrs.GetSizeAddr(), rdnAttrs.GetArrayAddr()); + NS_ENSURE_SUCCESS(rv, rv); + + // Calculate new RDN and check whether it has changed + nsAutoCString newRDN; + rv = card->BuildRdn(attrMap, rdnAttrs.GetSize(), rdnAttrs.GetArray(), + newRDN); + NS_ENSURE_SUCCESS(rv, rv); + + if (newRDN.Equals(oldRDN)) + { + // Launch query + rv = DoModify(this, nsILDAPModification::MOD_REPLACE, oldDN, modArray, + EmptyCString(), EmptyCString()); + } + else + { + // Build and store the new DN + nsAutoCString newDN(newRDN); + newDN.AppendLiteral(","); + newDN.Append(baseDN); + + rv = card->SetDn(newDN); + NS_ENSURE_SUCCESS(rv, rv); + + // Launch query + rv = DoModify(this, nsILDAPModification::MOD_REPLACE, oldDN, modArray, + newRDN, baseDN); + } + return rv; +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetRdnAttributes(nsACString &aRdnAttributes) +{ + return GetStringValue("rdnAttributes", NS_LITERAL_CSTRING("cn"), + aRdnAttributes); +} + +NS_IMETHODIMP nsAbLDAPDirectory::SetRdnAttributes(const nsACString &aRdnAttributes) +{ + return SetStringValue("rdnAttributes", aRdnAttributes); +} + +NS_IMETHODIMP nsAbLDAPDirectory::GetObjectClasses(nsACString &aObjectClasses) +{ + return GetStringValue("objectClasses", NS_LITERAL_CSTRING( + "top,person,organizationalPerson,inetOrgPerson,mozillaAbPersonAlpha"), + aObjectClasses); +} + +NS_IMETHODIMP nsAbLDAPDirectory::SetObjectClasses(const nsACString &aObjectClasses) +{ + return SetStringValue("objectClasses", aObjectClasses); +} + +nsresult nsAbLDAPDirectory::SplitStringList( + const nsACString& aString, + uint32_t *aCount, + char ***aValues) +{ + NS_ENSURE_ARG_POINTER(aCount); + NS_ENSURE_ARG_POINTER(aValues); + + nsTArray<nsCString> strarr; + ParseString(aString, ',', strarr); + + char **cArray = nullptr; + if (!(cArray = static_cast<char **>(moz_xmalloc( + strarr.Length() * sizeof(char *))))) + return NS_ERROR_OUT_OF_MEMORY; + + for (uint32_t i = 0; i < strarr.Length(); ++i) + { + if (!(cArray[i] = ToNewCString(strarr[i]))) + { + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(strarr.Length(), cArray); + return NS_ERROR_OUT_OF_MEMORY; + } + } + + *aCount = strarr.Length(); + *aValues = cArray; + return NS_OK; +} + diff --git a/mailnews/addrbook/src/nsAbLDAPDirectory.h b/mailnews/addrbook/src/nsAbLDAPDirectory.h new file mode 100644 index 000000000..6e5279a97 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPDirectory.h @@ -0,0 +1,75 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsAbLDAPDirectory_h__ +#define nsAbLDAPDirectory_h__ + +#include "mozilla/Attributes.h" +#include "nsAbDirProperty.h" +#include "nsAbLDAPDirectoryModify.h" +#include "nsIAbDirectoryQuery.h" +#include "nsIAbDirectorySearch.h" +#include "nsIAbDirSearchListener.h" +#include "nsIAbLDAPDirectory.h" +#include "nsIMutableArray.h" +#include "nsInterfaceHashtable.h" +#include "mozilla/Mutex.h" + +class nsAbLDAPDirectory : + public nsAbDirProperty, // nsIAbDirectory + public nsAbLDAPDirectoryModify, + public nsIAbDirectorySearch, + public nsIAbLDAPDirectory, + public nsIAbDirSearchListener +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + nsAbLDAPDirectory(); + + NS_IMETHOD Init(const char *aUri) override; + + // nsIAbDirectory methods + NS_IMETHOD GetPropertiesChromeURI(nsACString &aResult) override; + NS_IMETHOD GetURI(nsACString &aURI) override; + NS_IMETHOD GetChildNodes(nsISimpleEnumerator* *result) override; + NS_IMETHOD GetChildCards(nsISimpleEnumerator* *result) override; + NS_IMETHOD GetIsQuery(bool *aResult) override; + NS_IMETHOD HasCard(nsIAbCard *cards, bool *hasCard) override; + NS_IMETHOD GetSupportsMailingLists(bool *aSupportsMailingsLists) override; + NS_IMETHOD GetReadOnly(bool *aReadOnly) override; + NS_IMETHOD GetIsRemote(bool *aIsRemote) override; + NS_IMETHOD GetIsSecure(bool *aIsRemote) override; + NS_IMETHOD UseForAutocomplete(const nsACString &aIdentityKey, bool *aResult) override; + NS_IMETHOD AddCard(nsIAbCard *aChildCard, nsIAbCard **aAddedCard) override; + NS_IMETHOD ModifyCard(nsIAbCard *aModifiedCard) override; + NS_IMETHOD DeleteCards(nsIArray *aCards) override; + + // nsIAbDirectorySearch methods + NS_DECL_NSIABDIRECTORYSEARCH + NS_DECL_NSIABLDAPDIRECTORY + NS_DECL_NSIABDIRSEARCHLISTENER + +protected: + virtual ~nsAbLDAPDirectory(); + nsresult Initiate(); + + nsresult SplitStringList(const nsACString& aString, + uint32_t *aCount, + char ***aValues); + + bool mPerformingQuery; + int32_t mContext; + int32_t mMaxHits; + + nsInterfaceHashtable<nsISupportsHashKey, nsIAbCard> mCache; + + mozilla::Mutex mLock; + nsCOMPtr<nsIAbDirectoryQuery> mDirectoryQuery; + nsCOMPtr<nsIMutableArray> mSearchServerControls; + nsCOMPtr<nsIMutableArray> mSearchClientControls; +}; + +#endif diff --git a/mailnews/addrbook/src/nsAbLDAPDirectoryModify.cpp b/mailnews/addrbook/src/nsAbLDAPDirectoryModify.cpp new file mode 100644 index 000000000..95af79c04 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPDirectoryModify.cpp @@ -0,0 +1,372 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbLDAPDirectoryModify.h" +#include "nsILDAPMessage.h" +#include "nsILDAPConnection.h" +#include "nsILDAPErrors.h" +#include "nsILDAPModification.h" +#include "nsIServiceManager.h" +#include "nsIAbLDAPDirectory.h" +#include "nsIMutableArray.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" + +#include <stdio.h> + +using namespace mozilla; + +class nsAbModifyLDAPMessageListener : public nsAbLDAPListenerBase +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + nsAbModifyLDAPMessageListener(const int32_t type, + const nsACString &cardDN, + nsIArray* modArray, + const nsACString &newRDN, + const nsACString &newBaseDN, + nsILDAPURL* directoryUrl, + nsILDAPConnection* connection, + nsIMutableArray* serverSearchControls, + nsIMutableArray* clientSearchControls, + const nsACString &login, + const int32_t timeOut = 0); + // nsILDAPMessageListener + NS_IMETHOD OnLDAPMessage(nsILDAPMessage *aMessage) override; + +protected: + virtual ~nsAbModifyLDAPMessageListener(); + + nsresult Cancel(); + virtual void InitFailed(bool aCancelled = false) override; + virtual nsresult DoTask() override; + nsresult DoMainTask(); + nsresult OnLDAPMessageModifyResult(nsILDAPMessage *aMessage); + nsresult OnLDAPMessageRenameResult(nsILDAPMessage *aMessage); + + int32_t mType; + nsCString mCardDN; + nsCOMPtr<nsIArray> mModification; + nsCString mNewRDN; + nsCString mNewBaseDN; + + bool mFinished; + bool mCanceled; + bool mFlagRename; + + nsCOMPtr<nsILDAPOperation> mModifyOperation; + nsCOMPtr<nsIMutableArray> mServerSearchControls; + nsCOMPtr<nsIMutableArray> mClientSearchControls; +}; + + +NS_IMPL_ISUPPORTS(nsAbModifyLDAPMessageListener, nsILDAPMessageListener) + +nsAbModifyLDAPMessageListener::nsAbModifyLDAPMessageListener( + const int32_t type, + const nsACString &cardDN, + nsIArray* modArray, + const nsACString &newRDN, + const nsACString &newBaseDN, + nsILDAPURL* directoryUrl, + nsILDAPConnection* connection, + nsIMutableArray* serverSearchControls, + nsIMutableArray* clientSearchControls, + const nsACString &login, + const int32_t timeOut) : + nsAbLDAPListenerBase(directoryUrl, connection, login, timeOut), + mType(type), + mCardDN(cardDN), + mModification(modArray), + mNewRDN(newRDN), + mNewBaseDN(newBaseDN), + mFinished(false), + mCanceled(false), + mFlagRename(false), + mServerSearchControls(serverSearchControls), + mClientSearchControls(clientSearchControls) +{ + if (mType == nsILDAPModification::MOD_REPLACE && + !mNewRDN.IsEmpty() && !mNewBaseDN.IsEmpty()) + mFlagRename = true; +} + +nsAbModifyLDAPMessageListener::~nsAbModifyLDAPMessageListener () +{ +} + +nsresult nsAbModifyLDAPMessageListener::Cancel () +{ + nsresult rv = Initiate(); + NS_ENSURE_SUCCESS(rv, rv); + + MutexAutoLock lock(mLock); + + if (mFinished || mCanceled) + return NS_OK; + + mCanceled = true; + + return NS_OK; +} + +NS_IMETHODIMP nsAbModifyLDAPMessageListener::OnLDAPMessage(nsILDAPMessage *aMessage) +{ + nsresult rv = Initiate(); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t messageType; + rv = aMessage->GetType(&messageType); + NS_ENSURE_SUCCESS(rv, rv); + + bool cancelOperation = false; + + // Enter lock + { + MutexAutoLock lock (mLock); + + if (mFinished) + return NS_OK; + + // for these messages, no matter the outcome, we're done + if ((messageType == nsILDAPMessage::RES_ADD) || + (messageType == nsILDAPMessage::RES_DELETE) || + (messageType == nsILDAPMessage::RES_MODIFY)) + mFinished = true; + else if (mCanceled) + { + mFinished = true; + cancelOperation = true; + } + } + // Leave lock + + // nsCOMPtr<nsIAbDirectoryQueryResult> queryResult; + if (!cancelOperation) + { + switch (messageType) + { + case nsILDAPMessage::RES_BIND: + rv = OnLDAPMessageBind(aMessage); + if (NS_FAILED(rv)) + // We know the bind failed and hence the message has an error, so we + // can just call ModifyResult with the message and that'll sort it out + // for us. + rv = OnLDAPMessageModifyResult(aMessage); + break; + case nsILDAPMessage::RES_ADD: + case nsILDAPMessage::RES_MODIFY: + case nsILDAPMessage::RES_DELETE: + rv = OnLDAPMessageModifyResult(aMessage); + break; + case nsILDAPMessage::RES_MODDN: + mFlagRename = false; + rv = OnLDAPMessageRenameResult(aMessage); + if (NS_FAILED(rv)) + // Rename failed, so we stop here + mFinished = true; + break; + default: + break; + } + } + else + { + if (mModifyOperation) + rv = mModifyOperation->AbandonExt(); + + // reset because we might re-use this listener...except don't do this + // until the search is done, so we'll ignore results from a previous + // search. + mCanceled = mFinished = false; + } + + return rv; +} + +void nsAbModifyLDAPMessageListener::InitFailed(bool aCancelled) +{ + // XXX Just cancel the operation for now + // we'll need to review this when we've got the proper listeners in place. + Cancel(); +} + +nsresult nsAbModifyLDAPMessageListener::DoTask() +{ + nsresult rv; + mCanceled = mFinished = false; + + mModifyOperation = do_CreateInstance(NS_LDAPOPERATION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mModifyOperation->Init (mConnection, this, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + // XXX do we need the search controls? + rv = mModifyOperation->SetServerControls(mServerSearchControls); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mModifyOperation->SetClientControls(mClientSearchControls); + NS_ENSURE_SUCCESS(rv, rv); + + if (mFlagRename) + return mModifyOperation->Rename(mCardDN, mNewRDN, mNewBaseDN, true); + + switch (mType) + { + case nsILDAPModification::MOD_ADD: + return mModifyOperation->AddExt(mCardDN, mModification); + case nsILDAPModification::MOD_DELETE: + return mModifyOperation->DeleteExt(mCardDN); + case nsILDAPModification::MOD_REPLACE: + return mModifyOperation->ModifyExt(mCardDN, mModification); + default: + NS_ERROR("Bad LDAP modification requested"); + return NS_ERROR_UNEXPECTED; + } +} + +nsresult nsAbModifyLDAPMessageListener::OnLDAPMessageModifyResult(nsILDAPMessage *aMessage) +{ + nsresult rv; + NS_ENSURE_ARG_POINTER(aMessage); + + int32_t errCode; + rv = aMessage->GetErrorCode(&errCode); + NS_ENSURE_SUCCESS(rv, rv); + + if (errCode != nsILDAPErrors::SUCCESS) + { + nsAutoCString errMessage; + rv = aMessage->GetErrorMessage(errMessage); + NS_ENSURE_SUCCESS(rv, rv); + + printf("LDAP modification failed (code: %i, message: %s)\n", + errCode, errMessage.get()); + return NS_ERROR_FAILURE; + } + + printf("LDAP modification succeeded\n"); + return NS_OK; +} + +nsresult nsAbModifyLDAPMessageListener::OnLDAPMessageRenameResult(nsILDAPMessage *aMessage) +{ + nsresult rv; + NS_ENSURE_ARG_POINTER(aMessage); + + int32_t errCode; + rv = aMessage->GetErrorCode(&errCode); + NS_ENSURE_SUCCESS(rv, rv); + + if (errCode != nsILDAPErrors::SUCCESS) + { + nsAutoCString errMessage; + rv = aMessage->GetErrorMessage(errMessage); + NS_ENSURE_SUCCESS(rv, rv); + + printf("LDAP rename failed (code: %i, message: %s)\n", + errCode, errMessage.get()); + return NS_ERROR_FAILURE; + } + + // Rename succeeded, now update the card DN and + // process the main task + mCardDN.Assign(mNewRDN); + mCardDN.AppendLiteral(","); + mCardDN.Append(mNewBaseDN); + + printf("LDAP rename succeeded\n"); + return DoTask(); +} + +nsAbLDAPDirectoryModify::nsAbLDAPDirectoryModify() +{ +} + +nsAbLDAPDirectoryModify::~nsAbLDAPDirectoryModify() +{ +} + +nsresult nsAbLDAPDirectoryModify::DoModify(nsIAbLDAPDirectory *directory, + const int32_t &updateType, + const nsACString &cardDN, + nsIArray* modArray, + const nsACString &newRDN, + const nsACString &newBaseDN) +{ + NS_ENSURE_ARG_POINTER(directory); + // modArray may be null in the delete operation case. + if (!modArray && + (updateType == nsILDAPModification::MOD_ADD || + updateType == nsILDAPModification::MOD_REPLACE)) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + // it's an error if we don't have a dn + if (cardDN.IsEmpty()) + return NS_ERROR_INVALID_ARG; + + nsCOMPtr<nsILDAPURL> currentUrl; + rv = directory->GetLDAPURL(getter_AddRefs(currentUrl)); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the ldap connection + nsCOMPtr<nsILDAPConnection> ldapConnection = + do_CreateInstance(NS_LDAPCONNECTION_CONTRACTID, &rv); + + nsCOMPtr<nsIMutableArray> serverSearchControls; + rv = directory->GetSearchServerControls(getter_AddRefs(serverSearchControls)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> clientSearchControls; + rv = directory->GetSearchClientControls(getter_AddRefs(clientSearchControls)); + NS_ENSURE_SUCCESS(rv, rv); + + /* + // XXX we need to fix how this all works - specifically, see the first patch + // on bug 124553 for how the query equivalent did this + // too soon? Do we need a new listener? + if (alreadyInitialized) + { + nsAbQueryLDAPMessageListener *msgListener = + NS_STATIC_CAST(nsAbQueryLDAPMessageListener *, + NS_STATIC_CAST(nsILDAPMessageListener *, mListener.get())); + if (msgListener) + { + msgListener->mUrl = url; + return msgListener->DoSearch(); + } + }*/ + + nsCString login; + rv = directory->GetAuthDn(login); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t protocolVersion; + rv = directory->GetProtocolVersion(&protocolVersion); + NS_ENSURE_SUCCESS(rv, rv); + + // Initiate LDAP message listener + nsAbModifyLDAPMessageListener* _messageListener = + new nsAbModifyLDAPMessageListener(updateType, cardDN, modArray, + newRDN, newBaseDN, + currentUrl, + ldapConnection, + serverSearchControls, + clientSearchControls, + login, + 0); + if (_messageListener == NULL) + return NS_ERROR_OUT_OF_MEMORY; + + // Now lets initialize the LDAP connection properly. We'll kick + // off the bind operation in the callback function, |OnLDAPInit()|. + return ldapConnection->Init(currentUrl, login, + _messageListener, nullptr, protocolVersion); +} + diff --git a/mailnews/addrbook/src/nsAbLDAPDirectoryModify.h b/mailnews/addrbook/src/nsAbLDAPDirectoryModify.h new file mode 100644 index 000000000..8e14b8368 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPDirectoryModify.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsAbLDAPDirectoryModify_h__ +#define nsAbLDAPDirectoryModify_h__ + +#include "nsAbLDAPListenerBase.h" +#include "nsIAbLDAPDirectory.h" +#include "nsILDAPOperation.h" +#include "nsIArray.h" + +class nsILDAPURL; + +class nsAbLDAPDirectoryModify +{ +public: + nsAbLDAPDirectoryModify(); + virtual ~nsAbLDAPDirectoryModify(); + +protected: + nsresult DoModify(nsIAbLDAPDirectory *directory, + const int32_t &aUpdateType, + const nsACString &aCardDN, + nsIArray* modArray, + const nsACString &aNewRDN, + const nsACString &aNewBaseDN); +}; + +#endif // nsAbLDAPDirectoryModify_h__ diff --git a/mailnews/addrbook/src/nsAbLDAPDirectoryQuery.cpp b/mailnews/addrbook/src/nsAbLDAPDirectoryQuery.cpp new file mode 100644 index 000000000..9b22c796c --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPDirectoryQuery.cpp @@ -0,0 +1,610 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbLDAPDirectoryQuery.h" +#include "nsAbBoolExprToLDAPFilter.h" +#include "nsILDAPMessage.h" +#include "nsILDAPErrors.h" +#include "nsILDAPOperation.h" +#include "nsIAbLDAPAttributeMap.h" +#include "nsIAbLDAPCard.h" +#include "nsAbUtils.h" +#include "nsAbBaseCID.h" +#include "nsStringGlue.h" +#include "prprf.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsCategoryManagerUtils.h" +#include "nsAbLDAPDirectory.h" +#include "nsAbLDAPListenerBase.h" +#include "nsXPCOMCIDInternal.h" + +using namespace mozilla; + +// nsAbLDAPListenerBase inherits nsILDAPMessageListener +class nsAbQueryLDAPMessageListener : public nsAbLDAPListenerBase +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + + // Note that the directoryUrl is the details of the ldap directory + // without any search params or return attributes specified. The searchUrl + // therefore has the search params and return attributes specified. + // nsAbQueryLDAPMessageListener(nsIAbDirectoryQuery* directoryQuery, + nsAbQueryLDAPMessageListener(nsIAbDirectoryQueryResultListener* resultListener, + nsILDAPURL* directoryUrl, + nsILDAPURL* searchUrl, + nsILDAPConnection* connection, + nsIAbDirectoryQueryArguments* queryArguments, + nsIMutableArray* serverSearchControls, + nsIMutableArray* clientSearchControls, + const nsACString &login, + const nsACString &mechanism, + const int32_t resultLimit = -1, + const int32_t timeOut = 0); + + // nsILDAPMessageListener + NS_IMETHOD OnLDAPMessage(nsILDAPMessage *aMessage) override; + +protected: + virtual ~nsAbQueryLDAPMessageListener (); + nsresult OnLDAPMessageSearchEntry(nsILDAPMessage *aMessage); + nsresult OnLDAPMessageSearchResult(nsILDAPMessage *aMessage); + + friend class nsAbLDAPDirectoryQuery; + + nsresult Cancel(); + virtual nsresult DoTask() override; + virtual void InitFailed(bool aCancelled = false) override; + + nsCOMPtr<nsILDAPURL> mSearchUrl; + nsIAbDirectoryQueryResultListener *mResultListener; + int32_t mContextID; + nsCOMPtr<nsIAbDirectoryQueryArguments> mQueryArguments; + int32_t mResultLimit; + + bool mFinished; + bool mCanceled; + bool mWaitingForPrevQueryToFinish; + + nsCOMPtr<nsIMutableArray> mServerSearchControls; + nsCOMPtr<nsIMutableArray> mClientSearchControls; +}; + + +NS_IMPL_ISUPPORTS(nsAbQueryLDAPMessageListener, nsILDAPMessageListener) + +nsAbQueryLDAPMessageListener::nsAbQueryLDAPMessageListener( + nsIAbDirectoryQueryResultListener *resultListener, + nsILDAPURL* directoryUrl, + nsILDAPURL* searchUrl, + nsILDAPConnection* connection, + nsIAbDirectoryQueryArguments* queryArguments, + nsIMutableArray* serverSearchControls, + nsIMutableArray* clientSearchControls, + const nsACString &login, + const nsACString &mechanism, + const int32_t resultLimit, + const int32_t timeOut) : + nsAbLDAPListenerBase(directoryUrl, connection, login, timeOut), + mSearchUrl(searchUrl), + mResultListener(resultListener), + mQueryArguments(queryArguments), + mResultLimit(resultLimit), + mFinished(false), + mCanceled(false), + mWaitingForPrevQueryToFinish(false), + mServerSearchControls(serverSearchControls), + mClientSearchControls(clientSearchControls) +{ + mSaslMechanism.Assign(mechanism); +} + +nsAbQueryLDAPMessageListener::~nsAbQueryLDAPMessageListener () +{ +} + +nsresult nsAbQueryLDAPMessageListener::Cancel () +{ + nsresult rv = Initiate(); + NS_ENSURE_SUCCESS(rv, rv); + + MutexAutoLock lock(mLock); + + if (mFinished || mCanceled) + return NS_OK; + + mCanceled = true; + if (!mFinished) + mWaitingForPrevQueryToFinish = true; + + return NS_OK; +} + +NS_IMETHODIMP nsAbQueryLDAPMessageListener::OnLDAPMessage(nsILDAPMessage *aMessage) +{ + nsresult rv = Initiate(); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t messageType; + rv = aMessage->GetType(&messageType); + NS_ENSURE_SUCCESS(rv, rv); + + bool cancelOperation = false; + + // Enter lock + { + MutexAutoLock lock (mLock); + + if (mFinished) + return NS_OK; + + if (messageType == nsILDAPMessage::RES_SEARCH_RESULT) + mFinished = true; + else if (mCanceled) + { + mFinished = true; + cancelOperation = true; + } + } + // Leave lock + + if (!mResultListener) + return NS_ERROR_NULL_POINTER; + + if (!cancelOperation) + { + switch (messageType) + { + case nsILDAPMessage::RES_BIND: + rv = OnLDAPMessageBind(aMessage); + if (NS_FAILED(rv)) + // We know the bind failed and hence the message has an error, so we + // can just call SearchResult with the message and that'll sort it out + // for us. + rv = OnLDAPMessageSearchResult(aMessage); + break; + case nsILDAPMessage::RES_SEARCH_ENTRY: + if (!mFinished && !mWaitingForPrevQueryToFinish) + rv = OnLDAPMessageSearchEntry(aMessage); + break; + case nsILDAPMessage::RES_SEARCH_RESULT: + mWaitingForPrevQueryToFinish = false; + rv = OnLDAPMessageSearchResult(aMessage); + NS_ENSURE_SUCCESS(rv, rv); + break; + default: + break; + } + } + else + { + if (mOperation) + rv = mOperation->AbandonExt(); + + rv = mResultListener->OnQueryResult( + nsIAbDirectoryQueryResultListener::queryResultStopped, 0); + + // reset because we might re-use this listener...except don't do this + // until the search is done, so we'll ignore results from a previous + // search. + if (messageType == nsILDAPMessage::RES_SEARCH_RESULT) + mCanceled = mFinished = false; + } + + return rv; +} + +nsresult nsAbQueryLDAPMessageListener::DoTask() +{ + nsresult rv; + mCanceled = mFinished = false; + + mOperation = do_CreateInstance(NS_LDAPOPERATION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mOperation->Init(mConnection, this, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString dn; + rv = mSearchUrl->GetDn(dn); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t scope; + rv = mSearchUrl->GetScope(&scope); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString filter; + rv = mSearchUrl->GetFilter(filter); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString attributes; + rv = mSearchUrl->GetAttributes(attributes); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mOperation->SetServerControls(mServerSearchControls); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mOperation->SetClientControls(mClientSearchControls); + NS_ENSURE_SUCCESS(rv, rv); + + return mOperation->SearchExt(dn, scope, filter, attributes, mTimeOut, + mResultLimit); +} + +void nsAbQueryLDAPMessageListener::InitFailed(bool aCancelled) +{ + if (!mResultListener) + return; + + // In the !aCancelled case we know there was an error, but we won't be + // able to translate it, so just return an error code of zero. + mResultListener->OnQueryResult( + aCancelled ? nsIAbDirectoryQueryResultListener::queryResultStopped : + nsIAbDirectoryQueryResultListener::queryResultError, 0); +} + +nsresult nsAbQueryLDAPMessageListener::OnLDAPMessageSearchEntry(nsILDAPMessage *aMessage) +{ + nsresult rv; + + if (!mResultListener) + return NS_ERROR_NULL_POINTER; + + // the map for translating between LDAP attrs <-> addrbook fields + nsCOMPtr<nsISupports> iSupportsMap; + rv = mQueryArguments->GetTypeSpecificArg(getter_AddRefs(iSupportsMap)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbLDAPAttributeMap> map = do_QueryInterface(iSupportsMap, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbCard> card = do_CreateInstance(NS_ABLDAPCARD_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = map->SetCardPropertiesFromLDAPMessage(aMessage, card); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbLDAPCard> ldapCard = do_QueryInterface(card, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = ldapCard->SetMetaProperties(aMessage); + NS_ENSURE_SUCCESS(rv, rv); + + return mResultListener->OnQueryFoundCard(card); +} + +nsresult nsAbQueryLDAPMessageListener::OnLDAPMessageSearchResult(nsILDAPMessage *aMessage) +{ + int32_t errorCode; + nsresult rv = aMessage->GetErrorCode(&errorCode); + NS_ENSURE_SUCCESS(rv, rv); + + if (errorCode == nsILDAPErrors::SUCCESS || errorCode == nsILDAPErrors::SIZELIMIT_EXCEEDED) + return mResultListener->OnQueryResult( + nsIAbDirectoryQueryResultListener::queryResultComplete, 0); + + return mResultListener->OnQueryResult( + nsIAbDirectoryQueryResultListener::queryResultError, errorCode); +} + +// nsAbLDAPDirectoryQuery + +NS_IMPL_ISUPPORTS(nsAbLDAPDirectoryQuery, nsIAbDirectoryQuery, + nsIAbDirectoryQueryResultListener) + +nsAbLDAPDirectoryQuery::nsAbLDAPDirectoryQuery() : + mInitialized(false) +{ +} + +nsAbLDAPDirectoryQuery::~nsAbLDAPDirectoryQuery() +{ +} + +NS_IMETHODIMP nsAbLDAPDirectoryQuery::DoQuery(nsIAbDirectory *aDirectory, + nsIAbDirectoryQueryArguments* aArguments, + nsIAbDirSearchListener* aListener, + int32_t aResultLimit, + int32_t aTimeOut, + int32_t* _retval) +{ + NS_ENSURE_ARG_POINTER(aListener); + NS_ENSURE_ARG_POINTER(aArguments); + + mListeners.AppendObject(aListener); + + // Ensure existing query is stopped. Context id doesn't matter here + nsresult rv = StopQuery(0); + NS_ENSURE_SUCCESS(rv, rv); + + mInitialized = true; + + // Get the current directory as LDAP specific + nsCOMPtr<nsIAbLDAPDirectory> directory(do_QueryInterface(aDirectory, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // We also need the current URL to check as well... + nsCOMPtr<nsILDAPURL> currentUrl; + rv = directory->GetLDAPURL(getter_AddRefs(currentUrl)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString login; + rv = directory->GetAuthDn(login); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString saslMechanism; + rv = directory->GetSaslMechanism(saslMechanism); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t protocolVersion; + rv = directory->GetProtocolVersion(&protocolVersion); + NS_ENSURE_SUCCESS(rv, rv); + + // To do: + // Ensure query is stopped + // If connection params have changed re-create connection + // else reuse existing connection + + bool redoConnection = false; + + if (!mConnection || !mDirectoryUrl) + { + mDirectoryUrl = currentUrl; + aDirectory->GetUuid(mDirectoryId); + mCurrentLogin = login; + mCurrentMechanism = saslMechanism; + mCurrentProtocolVersion = protocolVersion; + redoConnection = true; + } + else + { + bool equal; + rv = mDirectoryUrl->Equals(currentUrl, &equal); + NS_ENSURE_SUCCESS(rv, rv); + + if (!equal) + { + mDirectoryUrl = currentUrl; + aDirectory->GetUuid(mDirectoryId); + mCurrentLogin = login; + mCurrentMechanism = saslMechanism; + mCurrentProtocolVersion = protocolVersion; + redoConnection = true; + } + else + { + // Has login or version changed? + if (login != mCurrentLogin || + saslMechanism != mCurrentMechanism || + protocolVersion != mCurrentProtocolVersion) + { + redoConnection = true; + mCurrentLogin = login; + mCurrentMechanism = saslMechanism; + mCurrentProtocolVersion = protocolVersion; + } + } + } + + nsCOMPtr<nsIURI> uri; + rv = mDirectoryUrl->Clone(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILDAPURL> url(do_QueryInterface(uri, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + // Get/Set the return attributes + nsCOMPtr<nsISupports> iSupportsMap; + rv = aArguments->GetTypeSpecificArg(getter_AddRefs(iSupportsMap)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbLDAPAttributeMap> map = do_QueryInterface(iSupportsMap, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Require all attributes that are mapped to card properties + nsAutoCString returnAttributes; + rv = map->GetAllCardAttributes(returnAttributes); + NS_ENSURE_SUCCESS(rv, rv); + + rv = url->SetAttributes(returnAttributes); + // Now do the error check + NS_ENSURE_SUCCESS(rv, rv); + + // Also require the objectClass attribute, it is used by + // nsAbLDAPCard::SetMetaProperties + rv = url->AddAttribute(NS_LITERAL_CSTRING("objectClass")); + + nsAutoCString filter; + + // Get filter from arguments if set: + rv = aArguments->GetFilter(filter); + NS_ENSURE_SUCCESS(rv, rv); + + if (filter.IsEmpty()) { + // Get the filter + nsCOMPtr<nsISupports> supportsExpression; + rv = aArguments->GetExpression(getter_AddRefs(supportsExpression)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbBooleanExpression> expression(do_QueryInterface(supportsExpression, &rv)); + + // figure out how we map attribute names to addressbook fields for this + // query + rv = nsAbBoolExprToLDAPFilter::Convert(map, expression, filter); + NS_ENSURE_SUCCESS(rv, rv); + } + + /* + * Mozilla itself cannot arrive here with a blank filter + * as the nsAbLDAPDirectory::StartSearch() disallows it. + * But 3rd party LDAP query integration with Mozilla begins + * in this method. + * + * Default the filter string if blank, otherwise it gets + * set to (objectclass=*) which returns everything. Set + * the default to (objectclass=inetorgperson) as this + * is the most appropriate default objectclass which is + * central to the makeup of the mozilla ldap address book + * entries. + */ + if (filter.IsEmpty()) + { + filter.AssignLiteral("(objectclass=inetorgperson)"); + } + + // get the directoryFilter from the directory url and merge it with the user's + // search filter + nsAutoCString urlFilter; + rv = mDirectoryUrl->GetFilter(urlFilter); + + // if urlFilter is unset (or set to the default "objectclass=*"), there's + // no need to AND in an empty search term, so leave prefix and suffix empty + + nsAutoCString searchFilter; + if (urlFilter.Length() && !urlFilter.EqualsLiteral("(objectclass=*)")) + { + // if urlFilter isn't parenthesized, we need to add in parens so that + // the filter works as a term to & + // + if (urlFilter[0] != '(') + { + searchFilter = NS_LITERAL_CSTRING("(&("); + searchFilter.Append(urlFilter); + searchFilter.AppendLiteral(")"); + } + else + { + searchFilter = NS_LITERAL_CSTRING("(&"); + searchFilter.Append(urlFilter); + } + + searchFilter += filter; + searchFilter += ')'; + } + else + searchFilter = filter; + + rv = url->SetFilter(searchFilter); + NS_ENSURE_SUCCESS(rv, rv); + + // Now formulate the search string + + // Get the scope + int32_t scope; + bool doSubDirectories; + rv = aArguments->GetQuerySubDirectories (&doSubDirectories); + NS_ENSURE_SUCCESS(rv, rv); + scope = doSubDirectories ? nsILDAPURL::SCOPE_SUBTREE : + nsILDAPURL::SCOPE_ONELEVEL; + + rv = url->SetScope(scope); + NS_ENSURE_SUCCESS(rv, rv); + + // too soon? Do we need a new listener? + // If we already have a connection, and don't need to re-do it, give it the + // new search details and go for it... + if (!redoConnection) + { + nsAbQueryLDAPMessageListener *msgListener = + static_cast<nsAbQueryLDAPMessageListener *>(static_cast<nsILDAPMessageListener *>(mListener.get())); + if (msgListener) + { + // Ensure the urls are correct + msgListener->mDirectoryUrl = mDirectoryUrl; + msgListener->mSearchUrl = url; + // Also ensure we set the correct result limit + msgListener->mResultLimit = aResultLimit; + return msgListener->DoTask(); + } + } + + nsCOMPtr<nsIAbLDAPDirectory> abLDAPDir = do_QueryInterface(aDirectory, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> serverSearchControls; + rv = abLDAPDir->GetSearchServerControls(getter_AddRefs(serverSearchControls)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> clientSearchControls; + rv = abLDAPDir->GetSearchClientControls(getter_AddRefs(clientSearchControls)); + NS_ENSURE_SUCCESS(rv, rv); + + // Create the new connection (which cause the old one to be dropped if necessary) + mConnection = do_CreateInstance(NS_LDAPCONNECTION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectoryQueryResultListener> resultListener = + do_QueryInterface((nsIAbDirectoryQuery*)this, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Initiate LDAP message listener + nsAbQueryLDAPMessageListener* _messageListener = + new nsAbQueryLDAPMessageListener(resultListener, mDirectoryUrl, url, + mConnection, aArguments, + serverSearchControls, clientSearchControls, + mCurrentLogin, mCurrentMechanism, + aResultLimit, aTimeOut); + if (_messageListener == NULL) + return NS_ERROR_OUT_OF_MEMORY; + + mListener = _messageListener; + *_retval = 1; + + // Now lets initialize the LDAP connection properly. We'll kick + // off the bind operation in the callback function, |OnLDAPInit()|. + rv = mConnection->Init(mDirectoryUrl, mCurrentLogin, + mListener, nullptr, mCurrentProtocolVersion); + NS_ENSURE_SUCCESS(rv, rv); + + return rv; +} + +/* void stopQuery (in long contextID); */ +NS_IMETHODIMP nsAbLDAPDirectoryQuery::StopQuery(int32_t contextID) +{ + mInitialized = true; + + if (!mListener) + return NS_OK; + + nsAbQueryLDAPMessageListener *listener = + static_cast<nsAbQueryLDAPMessageListener *>(static_cast<nsILDAPMessageListener *>(mListener.get())); + if (listener) + return listener->Cancel(); + + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectoryQuery::OnQueryFoundCard(nsIAbCard *aCard) +{ + aCard->SetDirectoryId(mDirectoryId); + + for (int32_t i = 0; i < mListeners.Count(); ++i) + mListeners[i]->OnSearchFoundCard(aCard); + + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPDirectoryQuery::OnQueryResult(int32_t aResult, + int32_t aErrorCode) +{ + uint32_t count = mListeners.Count(); + + // XXX: Temporary fix for crasher needs reviewing as part of bug 135231. + // Temporarily add a reference to ourselves, in case the only thing + // keeping us alive is the link with the listener. + NS_ADDREF_THIS(); + + for (int32_t i = count - 1; i >= 0; --i) + { + mListeners[i]->OnSearchFinished(aResult, EmptyString()); + mListeners.RemoveObjectAt(i); + } + + NS_RELEASE_THIS(); + + return NS_OK; +} diff --git a/mailnews/addrbook/src/nsAbLDAPDirectoryQuery.h b/mailnews/addrbook/src/nsAbLDAPDirectoryQuery.h new file mode 100644 index 000000000..2c11c2ab9 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPDirectoryQuery.h @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsAbLDAPDirectoryQuery_h__ +#define nsAbLDAPDirectoryQuery_h__ + +#include "nsIAbDirectoryQuery.h" +#include "nsILDAPConnection.h" +#include "nsILDAPMessageListener.h" +#include "nsILDAPURL.h" +#include "nsWeakReference.h" + +#include "nsStringGlue.h" +#include "nsCOMArray.h" + +class nsAbLDAPDirectoryQuery : public nsIAbDirectoryQuery, + public nsIAbDirectoryQueryResultListener +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIABDIRECTORYQUERY + NS_DECL_NSIABDIRECTORYQUERYRESULTLISTENER + + nsAbLDAPDirectoryQuery(); + +protected: + nsCOMPtr<nsILDAPMessageListener> mListener; + +private: + virtual ~nsAbLDAPDirectoryQuery(); + nsCOMPtr<nsILDAPConnection> mConnection; + nsCOMPtr<nsILDAPURL> mDirectoryUrl; + nsCString mDirectoryId; + nsCOMArray<nsIAbDirSearchListener> mListeners; + nsCString mCurrentLogin; + nsCString mCurrentMechanism; + uint32_t mCurrentProtocolVersion; + + bool mInitialized; +}; + +#endif // nsAbLDAPDirectoryQuery_h__ diff --git a/mailnews/addrbook/src/nsAbLDAPListenerBase.cpp b/mailnews/addrbook/src/nsAbLDAPListenerBase.cpp new file mode 100644 index 000000000..e5465a7f5 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPListenerBase.cpp @@ -0,0 +1,358 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbLDAPListenerBase.h" +#include "nsIWindowWatcher.h" +#include "nsIWindowMediator.h" +#include "mozIDOMWindow.h" +#include "nsIAuthPrompt.h" +#include "nsIStringBundle.h" +#include "nsILDAPMessage.h" +#include "nsILDAPErrors.h" +#include "nsILoginManager.h" +#include "nsILoginInfo.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsMemory.h" +#include "mozilla/Services.h" + +using namespace mozilla; + +nsAbLDAPListenerBase::nsAbLDAPListenerBase(nsILDAPURL* url, + nsILDAPConnection* connection, + const nsACString &login, + const int32_t timeOut) : + mDirectoryUrl(url), mConnection(connection), mLogin(login), + mTimeOut(timeOut), mBound(false), mInitialized(false), + mLock("nsAbLDAPListenerBase.mLock") +{ +} + +nsAbLDAPListenerBase::~nsAbLDAPListenerBase() +{ +} + +nsresult nsAbLDAPListenerBase::Initiate() +{ + if (!mConnection || !mDirectoryUrl) + return NS_ERROR_NULL_POINTER; + + if (mInitialized) + return NS_OK; + + mInitialized = true; + + return NS_OK; +} + +// If something fails in this function, we must call InitFailed() so that the +// derived class (and listener) knows to cancel what its doing as there is +// a problem. +NS_IMETHODIMP nsAbLDAPListenerBase::OnLDAPInit(nsILDAPConnection *aConn, nsresult aStatus) +{ + if (!mConnection || !mDirectoryUrl) + { + InitFailed(); + return NS_ERROR_NULL_POINTER; + } + + nsresult rv; + nsString passwd; + + // Make sure that the Init() worked properly + if (NS_FAILED(aStatus)) + { + InitFailed(); + return NS_OK; + } + + // If mLogin is set, we're expected to use it to get a password. + // + if (!mLogin.IsEmpty() && !mSaslMechanism.EqualsLiteral("GSSAPI")) + { + // get the string bundle service + // + nsCOMPtr<nsIStringBundleService> stringBundleSvc = + mozilla::services::GetStringBundleService(); + if (!stringBundleSvc) + { + NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit():" + " error getting string bundle service"); + InitFailed(); + return NS_ERROR_UNEXPECTED; + } + + // get the LDAP string bundle + // + nsCOMPtr<nsIStringBundle> ldapBundle; + rv = stringBundleSvc->CreateBundle("chrome://mozldap/locale/ldap.properties", + getter_AddRefs(ldapBundle)); + if (NS_FAILED(rv)) + { + NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit(): error creating string" + "bundle chrome://mozldap/locale/ldap.properties"); + InitFailed(); + return rv; + } + + // get the title for the authentication prompt + // + nsString authPromptTitle; + rv = ldapBundle->GetStringFromName(u"authPromptTitle", + getter_Copies(authPromptTitle)); + if (NS_FAILED(rv)) + { + NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit(): error getting" + "'authPromptTitle' string from bundle " + "chrome://mozldap/locale/ldap.properties"); + InitFailed(); + return rv; + } + + // get the host name for the auth prompt + // + nsAutoCString host; + rv = mDirectoryUrl->GetAsciiHost(host); + if (NS_FAILED(rv)) + { + NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit(): error getting ascii host" + "name from directory url"); + InitFailed(); + return rv; + } + + // hostTemp is only necessary to work around a code-generation + // bug in egcs 1.1.2 (the version of gcc that comes with Red Hat 6.2), + // which is the default compiler for Mozilla on linux at the moment. + // + NS_ConvertASCIItoUTF16 hostTemp(host); + const char16_t *hostArray[1] = { hostTemp.get() }; + + // format the hostname into the authprompt text string + // + nsString authPromptText; + rv = ldapBundle->FormatStringFromName(u"authPromptText", + hostArray, + sizeof(hostArray) / sizeof(const char16_t *), + getter_Copies(authPromptText)); + if (NS_FAILED(rv)) + { + NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit():" + "error getting 'authPromptText' string from bundle " + "chrome://mozldap/locale/ldap.properties"); + InitFailed(); + return rv; + } + + // get the window mediator service, so we can get an auth prompter + // + nsCOMPtr<nsIWindowMediator> windowMediator = + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID, &rv); + if (NS_FAILED(rv)) + { + NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit():" + " couldn't get window mediator service."); + InitFailed(); + return rv; + } + + // get the addressbook window, as it will be used to parent the auth + // prompter dialog + // + nsCOMPtr<mozIDOMWindowProxy> window; + rv = windowMediator->GetMostRecentWindow(nullptr, + getter_AddRefs(window)); + if (NS_FAILED(rv)) + { + NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit():" + " error getting most recent window"); + InitFailed(); + return rv; + } + + // get the window watcher service, so we can get an auth prompter + // + nsCOMPtr<nsIWindowWatcher> windowWatcherSvc = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + if (NS_FAILED(rv)) + { + NS_ERROR("nsAbLDAPListenerBase::OnLDAPInit():" + " couldn't get window watcher service."); + InitFailed(); + return rv; + } + + // get the auth prompter itself + // + nsCOMPtr<nsIAuthPrompt> authPrompter; + rv = windowWatcherSvc->GetNewAuthPrompter(window, + getter_AddRefs(authPrompter)); + if (NS_FAILED(rv)) + { + NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit():" + " error getting auth prompter"); + InitFailed(); + return rv; + } + + // get authentication password, prompting the user if necessary + // + // we're going to use the URL spec of the server as the "realm" for + // wallet to remember the password by / for. + + // Get the specification + nsCString spec; + rv = mDirectoryUrl->GetSpec(spec); + if (NS_FAILED(rv)) + { + NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit():" + " error getting directory url spec"); + InitFailed(); + return rv; + } + + bool status; + rv = authPrompter->PromptPassword(authPromptTitle.get(), + authPromptText.get(), + NS_ConvertUTF8toUTF16(spec).get(), + nsIAuthPrompt::SAVE_PASSWORD_PERMANENTLY, + getter_Copies(passwd), + &status); + if (NS_FAILED(rv)) + { + NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit(): failed to prompt for" + " password"); + InitFailed(); + return rv; + } + else if (!status) + { + InitFailed(true); + return NS_OK; + } + } + + // Initiate the LDAP operation + mOperation = do_CreateInstance(NS_LDAPOPERATION_CONTRACTID, &rv); + if (NS_FAILED(rv)) + { + NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit(): failed to create ldap operation"); + InitFailed(); + return rv; + } + + rv = mOperation->Init(mConnection, this, nullptr); + if (NS_FAILED(rv)) + { + NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit(): failed to Initialise operation"); + InitFailed(); + return rv; + } + + // Try non-password mechanisms first + if (mSaslMechanism.EqualsLiteral("GSSAPI")) + { + nsAutoCString service; + rv = mDirectoryUrl->GetAsciiHost(service); + NS_ENSURE_SUCCESS(rv, rv); + + service.Insert(NS_LITERAL_CSTRING("ldap@"), 0); + + nsCOMPtr<nsIAuthModule> authModule = + do_CreateInstance(NS_AUTH_MODULE_CONTRACTID_PREFIX "sasl-gssapi", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mOperation->SaslBind(service, mSaslMechanism, authModule); + if (NS_FAILED(rv)) + { + NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit(): " + "failed to perform GSSAPI bind"); + mOperation = nullptr; // Break Listener -> Operation -> Listener ref cycle + InitFailed(); + } + return rv; + } + + // Bind + rv = mOperation->SimpleBind(NS_ConvertUTF16toUTF8(passwd)); + if (NS_FAILED(rv)) + { + NS_ERROR("nsAbLDAPMessageBase::OnLDAPInit(): failed to perform bind operation"); + mOperation = nullptr; // Break Listener->Operation->Listener reference cycle + InitFailed(); + } + return rv; +} + +nsresult nsAbLDAPListenerBase::OnLDAPMessageBind(nsILDAPMessage *aMessage) +{ + if (mBound) + return NS_OK; + + // see whether the bind actually succeeded + // + int32_t errCode; + nsresult rv = aMessage->GetErrorCode(&errCode); + NS_ENSURE_SUCCESS(rv, rv); + + if (errCode != nsILDAPErrors::SUCCESS) + { + // if the login failed, tell the wallet to forget this password + // + if (errCode == nsILDAPErrors::INAPPROPRIATE_AUTH || + errCode == nsILDAPErrors::INVALID_CREDENTIALS) + { + // Login failed, so try again - but first remove the existing login(s) + // so that the user gets prompted. This may not be the best way of doing + // things, we need to review that later. + + nsCOMPtr<nsILoginManager> loginMgr = + do_GetService(NS_LOGINMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString spec; + rv = mDirectoryUrl->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString prePath; + rv = mDirectoryUrl->GetPrePath(prePath); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t count; + nsILoginInfo** logins; + + rv = loginMgr->FindLogins(&count, NS_ConvertUTF8toUTF16(prePath), + EmptyString(), + NS_ConvertUTF8toUTF16(spec), &logins); + NS_ENSURE_SUCCESS(rv, rv); + + // Typically there should only be one-login stored for this url, however, + // just in case there isn't. + for (uint32_t i = 0; i < count; ++i) + { + rv = loginMgr->RemoveLogin(logins[i]); + if (NS_FAILED(rv)) + { + NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(count, logins); + return rv; + } + } + NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(count, logins); + + // XXX We should probably pop up an error dialog telling + // the user that the login failed here, rather than just bringing + // up the password dialog again, which is what calling OnLDAPInit() + // does. + return OnLDAPInit(nullptr, NS_OK); + } + + // Don't know how to handle this, so use the message error code in + // the failure return value so we hopefully get it back to the UI. + return NS_ERROR_GENERATE_FAILURE(NS_ERROR_MODULE_LDAP, errCode); + } + + mBound = true; + return DoTask(); +} diff --git a/mailnews/addrbook/src/nsAbLDAPListenerBase.h b/mailnews/addrbook/src/nsAbLDAPListenerBase.h new file mode 100644 index 000000000..aa292df71 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPListenerBase.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsAbLDAPListenerBase_h__ +#define nsAbLDAPListenerBase_h__ + +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" +#include "nsILDAPMessageListener.h" +#include "nsILDAPURL.h" +#include "nsILDAPConnection.h" +#include "nsILDAPOperation.h" +#include "nsStringGlue.h" +#include "mozilla/Mutex.h" + +class nsAbLDAPListenerBase : public nsILDAPMessageListener +{ +public: + // Note that the directoryUrl is the details of the ldap directory + // without any search params or attributes specified. + nsAbLDAPListenerBase(nsILDAPURL* directoryUrl = nullptr, + nsILDAPConnection* connection = nullptr, + const nsACString &login = EmptyCString(), + const int32_t timeOut = 0); + virtual ~nsAbLDAPListenerBase(); + + NS_IMETHOD OnLDAPInit(nsILDAPConnection *aConn, nsresult aStatus) override; + +protected: + nsresult OnLDAPMessageBind(nsILDAPMessage *aMessage); + + nsresult Initiate(); + + // Called if an LDAP initialization fails. + virtual void InitFailed(bool aCancelled = false) = 0; + + // Called to start off the required task after a bind. + virtual nsresult DoTask() = 0; + + nsCOMPtr<nsILDAPURL> mDirectoryUrl; + nsCOMPtr<nsILDAPOperation> mOperation; // current ldap op + nsILDAPConnection* mConnection; + nsCString mLogin; + nsCString mSaslMechanism; + int32_t mTimeOut; + bool mBound; + bool mInitialized; + + mozilla::Mutex mLock; +}; + +#endif diff --git a/mailnews/addrbook/src/nsAbLDAPReplicationData.cpp b/mailnews/addrbook/src/nsAbLDAPReplicationData.cpp new file mode 100644 index 000000000..faa8cdd23 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPReplicationData.cpp @@ -0,0 +1,489 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsILDAPMessage.h" +#include "nsAbLDAPReplicationData.h" +#include "nsIAbCard.h" +#include "nsAbBaseCID.h" +#include "nsAbUtils.h" +#include "nsAbLDAPReplicationQuery.h" +#include "nsILDAPErrors.h" +#include "nsComponentManagerUtils.h" +#include "nsMsgUtils.h" + +// once bug # 101252 gets fixed, this should be reverted back to be non threadsafe +// implementation is not really thread safe since each object should exist +// independently along with its related independent nsAbLDAPReplicationQuery object. +NS_IMPL_ISUPPORTS(nsAbLDAPProcessReplicationData, nsIAbLDAPProcessReplicationData, nsILDAPMessageListener) + +nsAbLDAPProcessReplicationData::nsAbLDAPProcessReplicationData() : + nsAbLDAPListenerBase(), + mState(kIdle), + mProtocol(-1), + mCount(0), + mDBOpen(false), + mInitialized(false) +{ +} + +nsAbLDAPProcessReplicationData::~nsAbLDAPProcessReplicationData() +{ + /* destructor code */ + if(mDBOpen && mReplicationDB) + mReplicationDB->Close(false); +} + +NS_IMETHODIMP nsAbLDAPProcessReplicationData::Init( + nsIAbLDAPDirectory *aDirectory, + nsILDAPConnection *aConnection, + nsILDAPURL* aURL, + nsIAbLDAPReplicationQuery *aQuery, + nsIWebProgressListener *aProgressListener) +{ + NS_ENSURE_ARG_POINTER(aDirectory); + NS_ENSURE_ARG_POINTER(aConnection); + NS_ENSURE_ARG_POINTER(aURL); + NS_ENSURE_ARG_POINTER(aQuery); + + mDirectory = aDirectory; + mConnection = aConnection; + mDirectoryUrl = aURL; + mQuery = aQuery; + + mListener = aProgressListener; + + nsresult rv = mDirectory->GetAttributeMap(getter_AddRefs(mAttrMap)); + if (NS_FAILED(rv)) { + mQuery = nullptr; + return rv; + } + + rv = mDirectory->GetAuthDn(mLogin); + if (NS_FAILED(rv)) { + mQuery = nullptr; + return rv; + } + + rv = mDirectory->GetSaslMechanism(mSaslMechanism); + if (NS_FAILED(rv)) { + mQuery = nullptr; + return rv; + } + + mInitialized = true; + + return rv; +} + +NS_IMETHODIMP nsAbLDAPProcessReplicationData::GetReplicationState(int32_t *aReplicationState) +{ + NS_ENSURE_ARG_POINTER(aReplicationState); + *aReplicationState = mState; + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPProcessReplicationData::GetProtocolUsed(int32_t *aProtocolUsed) +{ + NS_ENSURE_ARG_POINTER(aProtocolUsed); + *aProtocolUsed = mProtocol; + return NS_OK; +} + +NS_IMETHODIMP nsAbLDAPProcessReplicationData::OnLDAPMessage(nsILDAPMessage *aMessage) +{ + NS_ENSURE_ARG_POINTER(aMessage); + + if (!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + int32_t messageType; + nsresult rv = aMessage->GetType(&messageType); + if (NS_FAILED(rv)) { + Done(false); + return rv; + } + + switch (messageType) + { + case nsILDAPMessage::RES_BIND: + rv = OnLDAPMessageBind(aMessage); + if (NS_FAILED(rv)) + rv = Abort(); + break; + case nsILDAPMessage::RES_SEARCH_ENTRY: + rv = OnLDAPSearchEntry(aMessage); + break; + case nsILDAPMessage::RES_SEARCH_RESULT: + rv = OnLDAPSearchResult(aMessage); + break; + default: + // for messageTypes we do not handle return NS_OK to LDAP and move ahead. + rv = NS_OK; + break; + } + + return rv; +} + +NS_IMETHODIMP nsAbLDAPProcessReplicationData::Abort() +{ + if (!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = NS_OK; + + if (mState != kIdle && mOperation) { + rv = mOperation->AbandonExt(); + if (NS_SUCCEEDED(rv)) + mState = kIdle; + } + + if (mReplicationDB && mDBOpen) { + // force close since we need to delete the file. + mReplicationDB->ForceClosed(); + mDBOpen = false; + + // delete the unsaved replication file + if (mReplicationFile) { + rv = mReplicationFile->Remove(false); + if (NS_SUCCEEDED(rv) && mDirectory) { + nsAutoCString fileName; + rv = mDirectory->GetReplicationFileName(fileName); + // now put back the backed up replicated file if aborted + if (NS_SUCCEEDED(rv) && mBackupReplicationFile) + rv = mBackupReplicationFile->MoveToNative(nullptr, fileName); + } + } + } + + Done(false); + + return rv; +} + +nsresult nsAbLDAPProcessReplicationData::DoTask() +{ + if (!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = OpenABForReplicatedDir(true); + if (NS_FAILED(rv)) + // do not call done here since it is called by OpenABForReplicationDir + return rv; + + mOperation = do_CreateInstance(NS_LDAPOPERATION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mOperation->Init(mConnection, this, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + // get the relevant attributes associated with the directory server url + nsAutoCString urlFilter; + rv = mDirectoryUrl->GetFilter(urlFilter); + if (NS_FAILED(rv)) + return rv; + + nsAutoCString dn; + rv = mDirectoryUrl->GetDn(dn); + if (NS_FAILED(rv)) + return rv; + + if (dn.IsEmpty()) + return NS_ERROR_UNEXPECTED; + + int32_t scope; + rv = mDirectoryUrl->GetScope(&scope); + if (NS_FAILED(rv)) + return rv; + + nsAutoCString attributes; + rv = mDirectoryUrl->GetAttributes(attributes); + if (NS_FAILED(rv)) + return rv; + + mState = kReplicatingAll; + + if (mListener && NS_SUCCEEDED(rv)) + // XXX Cast from bool to nsresult + mListener->OnStateChange(nullptr, nullptr, + nsIWebProgressListener::STATE_START, + static_cast<nsresult>(true)); + + return mOperation->SearchExt(dn, scope, urlFilter, attributes, 0, 0); +} + +void nsAbLDAPProcessReplicationData::InitFailed(bool aCancelled) +{ + // Just call Done() which will ensure everything is tidied up nicely. + Done(false); +} + +nsresult nsAbLDAPProcessReplicationData::OnLDAPSearchEntry(nsILDAPMessage *aMessage) +{ + NS_ENSURE_ARG_POINTER(aMessage); + if (!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + // since this runs on the main thread and is single threaded, this will + // take care of entries returned by LDAP Connection thread after Abort. + if (!mReplicationDB || !mDBOpen) + return NS_ERROR_FAILURE; + + nsresult rv = NS_OK; + + // Although we would may naturally create an nsIAbLDAPCard here, we don't + // need to as we are writing this straight to the database, so just create + // the database version instead. + nsCOMPtr<nsIAbCard> newCard(do_CreateInstance(NS_ABMDBCARD_CONTRACTID, + &rv)); + if (NS_FAILED(rv)) { + Abort(); + return rv; + } + + rv = mAttrMap->SetCardPropertiesFromLDAPMessage(aMessage, newCard); + if (NS_FAILED(rv)) + { + NS_WARNING("nsAbLDAPProcessReplicationData::OnLDAPSearchEntry" + "No card properties could be set"); + // if some entries are bogus for us, continue with next one + return NS_OK; + } + + rv = mReplicationDB->CreateNewCardAndAddToDB(newCard, false, nullptr); + if(NS_FAILED(rv)) { + Abort(); + return rv; + } + + // now set the attribute for the DN of the entry in the card in the DB + nsAutoCString authDN; + rv = aMessage->GetDn(authDN); + if(NS_SUCCEEDED(rv) && !authDN.IsEmpty()) + { + newCard->SetPropertyAsAUTF8String("_DN", authDN); + } + + rv = mReplicationDB->EditCard(newCard, false, nullptr); + if(NS_FAILED(rv)) { + Abort(); + return rv; + } + + + mCount ++; + + if (mListener && !(mCount % 10)) // inform the listener every 10 entries + { + mListener->OnProgressChange(nullptr,nullptr,mCount, -1, mCount, -1); + // in case if the LDAP Connection thread is starved and causes problem + // uncomment this one and try. + // PR_Sleep(PR_INTERVAL_NO_WAIT); // give others a chance + } + + return rv; +} + + +nsresult nsAbLDAPProcessReplicationData::OnLDAPSearchResult(nsILDAPMessage *aMessage) +{ +#ifdef DEBUG_rdayal + printf("LDAP Replication : Got Results for Completion"); +#endif + + NS_ENSURE_ARG_POINTER(aMessage); + if(!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + int32_t errorCode; + nsresult rv = aMessage->GetErrorCode(&errorCode); + + if(NS_SUCCEEDED(rv)) { + // We are done with the LDAP search for all entries. + if(errorCode == nsILDAPErrors::SUCCESS || errorCode == nsILDAPErrors::SIZELIMIT_EXCEEDED) { + Done(true); + if(mReplicationDB && mDBOpen) { + rv = mReplicationDB->Close(true); + NS_ASSERTION(NS_SUCCEEDED(rv), "Replication DB Close on Success failed"); + mDBOpen = false; + // once we have saved the new replication file, delete the backup file + if(mBackupReplicationFile) + { + rv = mBackupReplicationFile->Remove(false); + NS_ASSERTION(NS_SUCCEEDED(rv), "Replication BackupFile Remove on Success failed"); + } + } + return NS_OK; + } + } + + // in case if GetErrorCode returned error or errorCode is not SUCCESS / SIZELIMIT_EXCEEDED + if(mReplicationDB && mDBOpen) { + // if error result is returned close the DB without saving ??? + // should we commit anyway ??? whatever is returned is not lost then !! + rv = mReplicationDB->ForceClosed(); // force close since we need to delete the file. + NS_ASSERTION(NS_SUCCEEDED(rv), "Replication DB ForceClosed on Failure failed"); + mDBOpen = false; + // if error result is returned remove the replicated file + if(mReplicationFile) { + rv = mReplicationFile->Remove(false); + NS_ASSERTION(NS_SUCCEEDED(rv), "Replication File Remove on Failure failed"); + if(NS_SUCCEEDED(rv)) { + // now put back the backed up replicated file + if(mBackupReplicationFile && mDirectory) + { + nsAutoCString fileName; + rv = mDirectory->GetReplicationFileName(fileName); + if (NS_SUCCEEDED(rv) && !fileName.IsEmpty()) + { + rv = mBackupReplicationFile->MoveToNative(nullptr, fileName); + NS_ASSERTION(NS_SUCCEEDED(rv), "Replication Backup File Move back on Failure failed"); + } + } + } + } + Done(false); + } + + return NS_OK; +} + +nsresult nsAbLDAPProcessReplicationData::OpenABForReplicatedDir(bool aCreate) +{ + if (!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = mDirectory->GetReplicationFile(getter_AddRefs(mReplicationFile)); + if (NS_FAILED(rv)) + { + Done(false); + return NS_ERROR_FAILURE; + } + + nsCString fileName; + rv = mReplicationFile->GetNativeLeafName(fileName); + if (NS_FAILED(rv)) { + Done(false); + return rv; + } + + // if the AB DB already exists backup existing one, + // in case if the user cancels or Abort put back the backed up file + bool fileExists; + rv = mReplicationFile->Exists(&fileExists); + if(NS_SUCCEEDED(rv) && fileExists) { + // create the backup file object same as the Replication file object. + // we create a backup file here since we need to cleanup the existing file + // for create and then commit so instead of deleting existing cards we just + // clone the existing one for a much better performance - for Download All. + // And also important in case if replication fails we donot lose user's existing + // replicated data for both Download all and Changelog. + nsCOMPtr<nsIFile> clone; + rv = mReplicationFile->Clone(getter_AddRefs(clone)); + if(NS_FAILED(rv)) { + Done(false); + return rv; + } + mBackupReplicationFile = do_QueryInterface(clone, &rv); + if(NS_FAILED(rv)) { + Done(false); + return rv; + } + rv = mBackupReplicationFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0777); + if(NS_FAILED(rv)) { + Done(false); + return rv; + } + nsAutoString backupFileLeafName; + rv = mBackupReplicationFile->GetLeafName(backupFileLeafName); + if(NS_FAILED(rv)) { + Done(false); + return rv; + } + // remove the newly created unique backup file so that move and copy succeeds. + rv = mBackupReplicationFile->Remove(false); + if(NS_FAILED(rv)) { + Done(false); + return rv; + } + + if(aCreate) { + // set backup file to existing replication file for move + mBackupReplicationFile->SetNativeLeafName(fileName); + + rv = mBackupReplicationFile->MoveTo(nullptr, backupFileLeafName); + // set the backup file leaf name now + if (NS_SUCCEEDED(rv)) + mBackupReplicationFile->SetLeafName(backupFileLeafName); + } + else { + // set backup file to existing replication file for copy + mBackupReplicationFile->SetNativeLeafName(fileName); + + // specify the parent here specifically, + // passing nullptr to copy to the same dir actually renames existing file + // instead of making another copy of the existing file. + nsCOMPtr<nsIFile> parent; + rv = mBackupReplicationFile->GetParent(getter_AddRefs(parent)); + if (NS_SUCCEEDED(rv)) + rv = mBackupReplicationFile->CopyTo(parent, backupFileLeafName); + // set the backup file leaf name now + if (NS_SUCCEEDED(rv)) + mBackupReplicationFile->SetLeafName(backupFileLeafName); + } + if(NS_FAILED(rv)) { + Done(false); + return rv; + } + } + + nsCOMPtr<nsIAddrDatabase> addrDBFactory = + do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv); + if(NS_FAILED(rv)) { + if (mBackupReplicationFile) + mBackupReplicationFile->Remove(false); + Done(false); + return rv; + } + + rv = addrDBFactory->Open(mReplicationFile, aCreate, true, getter_AddRefs(mReplicationDB)); + if(NS_FAILED(rv)) { + Done(false); + if (mBackupReplicationFile) + mBackupReplicationFile->Remove(false); + return rv; + } + + mDBOpen = true; // replication DB is now Open + return rv; +} + +void nsAbLDAPProcessReplicationData::Done(bool aSuccess) +{ + if (!mInitialized) + return; + + mState = kReplicationDone; + + if (mQuery) + mQuery->Done(aSuccess); + + if (mListener) + // XXX Cast from bool to nsresult + mListener->OnStateChange(nullptr, nullptr, + nsIWebProgressListener::STATE_STOP, + static_cast<nsresult>(aSuccess)); + + // since this is called when all is done here, either on success, + // failure or abort release the query now. + mQuery = nullptr; +} + +nsresult nsAbLDAPProcessReplicationData::DeleteCard(nsString & aDn) +{ + nsCOMPtr<nsIAbCard> cardToDelete; + mReplicationDB->GetCardFromAttribute(nullptr, "_DN", NS_ConvertUTF16toUTF8(aDn), + false, getter_AddRefs(cardToDelete)); + return mReplicationDB->DeleteCard(cardToDelete, false, nullptr); +} diff --git a/mailnews/addrbook/src/nsAbLDAPReplicationData.h b/mailnews/addrbook/src/nsAbLDAPReplicationData.h new file mode 100644 index 000000000..546ce659e --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPReplicationData.h @@ -0,0 +1,66 @@ +/* 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/. */ + + +#ifndef nsAbLDAPReplicationData_h__ +#define nsAbLDAPReplicationData_h__ + +#include "mozilla/Attributes.h" +#include "nsIAbLDAPReplicationData.h" +#include "nsIWebProgressListener.h" +#include "nsIAbLDAPReplicationQuery.h" +#include "nsAbLDAPListenerBase.h" +#include "nsIAddrDatabase.h" +#include "nsIFile.h" +#include "nsDirPrefs.h" +#include "nsIAbLDAPAttributeMap.h" +#include "nsIAbLDAPDirectory.h" +#include "nsStringGlue.h" + +class nsAbLDAPProcessReplicationData : public nsIAbLDAPProcessReplicationData, + public nsAbLDAPListenerBase +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIABLDAPPROCESSREPLICATIONDATA + + nsAbLDAPProcessReplicationData(); + + // nsILDAPMessageListener + NS_IMETHOD OnLDAPMessage(nsILDAPMessage *aMessage) override; + +protected: + virtual ~nsAbLDAPProcessReplicationData(); + virtual nsresult DoTask() override; + virtual void InitFailed(bool aCancelled = false) override; + + // pointer to the interfaces used by this object + nsCOMPtr<nsIWebProgressListener> mListener; + // pointer to the query to call back to once we've finished + nsCOMPtr<nsIAbLDAPReplicationQuery> mQuery; + + nsCOMPtr<nsIAddrDatabase> mReplicationDB; + nsCOMPtr <nsIFile> mReplicationFile; + nsCOMPtr <nsIFile> mBackupReplicationFile; + + // state of processing, protocol used and count of results + int32_t mState; + int32_t mProtocol; + int32_t mCount; + bool mDBOpen; + bool mInitialized; + + nsCOMPtr<nsIAbLDAPDirectory> mDirectory; + nsCOMPtr<nsIAbLDAPAttributeMap> mAttrMap; // maps ab properties to ldap attrs + + virtual nsresult OnLDAPSearchEntry(nsILDAPMessage *aMessage); + virtual nsresult OnLDAPSearchResult(nsILDAPMessage *aMessage); + + nsresult OpenABForReplicatedDir(bool bCreate); + nsresult DeleteCard(nsString & aDn); + void Done(bool aSuccess); +}; + + +#endif // nsAbLDAPReplicationData_h__ diff --git a/mailnews/addrbook/src/nsAbLDAPReplicationQuery.cpp b/mailnews/addrbook/src/nsAbLDAPReplicationQuery.cpp new file mode 100644 index 000000000..d82a8336c --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPReplicationQuery.cpp @@ -0,0 +1,153 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + + +#include "nsCOMPtr.h" +#include "nsAbLDAPReplicationQuery.h" +#include "nsAbLDAPReplicationService.h" +#include "nsAbLDAPReplicationData.h" +#include "nsILDAPURL.h" +#include "nsAbBaseCID.h" +#include "nsAbUtils.h" +#include "nsDirPrefs.h" +#include "prmem.h" +#include "nsComponentManagerUtils.h" +#include "nsMsgUtils.h" + +NS_IMPL_ISUPPORTS(nsAbLDAPReplicationQuery, + nsIAbLDAPReplicationQuery) + +nsAbLDAPReplicationQuery::nsAbLDAPReplicationQuery() + : mInitialized(false) +{ +} + +nsresult nsAbLDAPReplicationQuery::InitLDAPData() +{ + nsAutoCString fileName; + nsresult rv = mDirectory->GetReplicationFileName(fileName); + NS_ENSURE_SUCCESS(rv, rv); + + // this is done here to take care of the problem related to bug # 99124. + // earlier versions of Mozilla could have the fileName associated with the directory + // to be abook.mab which is the profile's personal addressbook. If the pref points to + // it, calls nsDirPrefs to generate a new server filename. + if (fileName.IsEmpty() || fileName.EqualsLiteral(kPersonalAddressbook)) + { + // Ensure fileName is empty for DIR_GenerateAbFileName to work + // correctly. + fileName.Truncate(); + + nsCOMPtr<nsIAbDirectory> standardDir(do_QueryInterface(mDirectory, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString dirPrefId; + rv = standardDir->GetDirPrefId(dirPrefId); + NS_ENSURE_SUCCESS(rv, rv); + + // XXX This should be replaced by a local function at some stage. + // For now we'll continue using the nsDirPrefs version. + DIR_Server* server = DIR_GetServerFromList(dirPrefId.get()); + if (server) + { + DIR_SetServerFileName(server); + // Now ensure the prefs are saved + DIR_SavePrefsForOneServer(server); + } + } + + rv = mDirectory->SetReplicationFileName(fileName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mDirectory->GetLDAPURL(getter_AddRefs(mURL)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mDirectory->GetAuthDn(mLogin); + NS_ENSURE_SUCCESS(rv, rv); + + mConnection = do_CreateInstance(NS_LDAPCONNECTION_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + mOperation = do_CreateInstance(NS_LDAPOPERATION_CONTRACTID, &rv); + + return rv; +} + +nsresult nsAbLDAPReplicationQuery::ConnectToLDAPServer() +{ + if (!mInitialized || !mURL) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv; + nsCOMPtr<nsILDAPMessageListener> mDp = do_QueryInterface(mDataProcessor, + &rv); + if (NS_FAILED(rv)) + return NS_ERROR_UNEXPECTED; + + // this could be a rebind call + int32_t replicationState = nsIAbLDAPProcessReplicationData::kIdle; + rv = mDataProcessor->GetReplicationState(&replicationState); + if (NS_FAILED(rv) || + replicationState != nsIAbLDAPProcessReplicationData::kIdle) + return rv; + + uint32_t protocolVersion; + rv = mDirectory->GetProtocolVersion(&protocolVersion); + NS_ENSURE_SUCCESS(rv, rv); + + // initialize the LDAP connection + return mConnection->Init(mURL, mLogin, mDp, nullptr, protocolVersion); +} + +NS_IMETHODIMP nsAbLDAPReplicationQuery::Init(nsIAbLDAPDirectory *aDirectory, + nsIWebProgressListener *aProgressListener) +{ + NS_ENSURE_ARG_POINTER(aDirectory); + + mDirectory = aDirectory; + + nsresult rv = InitLDAPData(); + if (NS_FAILED(rv)) + return rv; + + mDataProcessor = + do_CreateInstance(NS_ABLDAP_PROCESSREPLICATIONDATA_CONTRACTID, &rv); + if (NS_FAILED(rv)) + return rv; + + // 'this' initialized + mInitialized = true; + + return mDataProcessor->Init(mDirectory, mConnection, mURL, this, + aProgressListener); +} + +NS_IMETHODIMP nsAbLDAPReplicationQuery::DoReplicationQuery() +{ + return ConnectToLDAPServer(); +} + +NS_IMETHODIMP nsAbLDAPReplicationQuery::CancelQuery() +{ + if (!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + return mDataProcessor->Abort(); +} + +NS_IMETHODIMP nsAbLDAPReplicationQuery::Done(bool aSuccess) +{ + if (!mInitialized) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = NS_OK; + nsCOMPtr<nsIAbLDAPReplicationService> replicationService = + do_GetService(NS_ABLDAP_REPLICATIONSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + replicationService->Done(aSuccess); + + return rv; +} diff --git a/mailnews/addrbook/src/nsAbLDAPReplicationQuery.h b/mailnews/addrbook/src/nsAbLDAPReplicationQuery.h new file mode 100644 index 000000000..f5d7cdda7 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPReplicationQuery.h @@ -0,0 +1,44 @@ +/* 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/. */ + + +#ifndef nsAbLDAPReplicationQuery_h__ +#define nsAbLDAPReplicationQuery_h__ + +#include "nsIWebProgressListener.h" +#include "nsIAbLDAPReplicationQuery.h" +#include "nsIAbLDAPReplicationData.h" +#include "nsIAbLDAPDirectory.h" +#include "nsILDAPConnection.h" +#include "nsILDAPOperation.h" +#include "nsILDAPURL.h" +#include "nsDirPrefs.h" +#include "nsStringGlue.h" + +class nsAbLDAPReplicationQuery final : public nsIAbLDAPReplicationQuery +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIABLDAPREPLICATIONQUERY + + nsAbLDAPReplicationQuery(); + + nsresult InitLDAPData(); + nsresult ConnectToLDAPServer(); + +protected : + ~nsAbLDAPReplicationQuery() {} + // pointer to interfaces used by this object + nsCOMPtr<nsILDAPConnection> mConnection; + nsCOMPtr<nsILDAPOperation> mOperation; + nsCOMPtr<nsILDAPURL> mURL; + nsCOMPtr<nsIAbLDAPDirectory> mDirectory; + + nsCOMPtr<nsIAbLDAPProcessReplicationData> mDataProcessor; + + bool mInitialized; + nsCString mLogin; +}; + +#endif // nsAbLDAPReplicationQuery_h__ diff --git a/mailnews/addrbook/src/nsAbLDAPReplicationService.cpp b/mailnews/addrbook/src/nsAbLDAPReplicationService.cpp new file mode 100644 index 000000000..0ee53d719 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPReplicationService.cpp @@ -0,0 +1,136 @@ +/* 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/. */ + + +#include "nsCOMPtr.h" +#include "nsAbLDAPReplicationService.h" +#include "nsAbLDAPReplicationQuery.h" +#include "nsAbBaseCID.h" +#include "nsIWebProgressListener.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" + +// XXX Change log replication doesn't work. Bug 311632 should fix it. +//#include "nsAbLDAPChangeLogQuery.h" +#include "nsIAbLDAPReplicationData.h" + +/*** implementation of the service ******/ + +NS_IMPL_ISUPPORTS(nsAbLDAPReplicationService, nsIAbLDAPReplicationService) + +nsAbLDAPReplicationService::nsAbLDAPReplicationService() + : mReplicating(false) +{ +} + +nsAbLDAPReplicationService::~nsAbLDAPReplicationService() +{ +} + +/* void startReplication(in string aURI, in nsIWebProgressListener progressListener); */ +NS_IMETHODIMP nsAbLDAPReplicationService::StartReplication(nsIAbLDAPDirectory *aDirectory, + nsIWebProgressListener *progressListener) +{ + NS_ENSURE_ARG_POINTER(aDirectory); + +#ifdef DEBUG_rdayal + printf("Start Replication called"); +#endif + + // Makes sure to allow only one replication at a time. + if(mReplicating) + return NS_ERROR_FAILURE; + + mDirectory = aDirectory; + + nsresult rv = NS_ERROR_NOT_IMPLEMENTED; + + switch (DecideProtocol()) + { + case nsIAbLDAPProcessReplicationData::kDefaultDownloadAll: + mQuery = do_CreateInstance(NS_ABLDAP_REPLICATIONQUERY_CONTRACTID, &rv); + break; +// XXX Change log replication doesn't work. Bug 311632 should fix it. +//case nsIAbLDAPProcessReplicationData::kChangeLogProtocol: +// mQuery = do_CreateInstance (NS_ABLDAP_CHANGELOGQUERY_CONTRACTID, &rv); +// break; + default: + break; + } + + if (NS_SUCCEEDED(rv) && mQuery) + { + rv = mQuery->Init(mDirectory, progressListener); + if (NS_SUCCEEDED(rv)) + { + rv = mQuery->DoReplicationQuery(); + if (NS_SUCCEEDED(rv)) + { + mReplicating = true; + return rv; + } + } + } + + if (progressListener && NS_FAILED(rv)) + progressListener->OnStateChange(nullptr, nullptr, + nsIWebProgressListener::STATE_STOP, + NS_OK); + + if (NS_FAILED(rv)) + { + mDirectory = nullptr; + mQuery = nullptr; + } + + return rv; +} + +/* void cancelReplication(in string aURI); */ +NS_IMETHODIMP nsAbLDAPReplicationService::CancelReplication(nsIAbLDAPDirectory *aDirectory) +{ + NS_ENSURE_ARG_POINTER(aDirectory); + + nsresult rv = NS_ERROR_FAILURE; + + if (aDirectory == mDirectory) + { + if (mQuery && mReplicating) + rv = mQuery->CancelQuery(); + } + + // If query has been cancelled successfully + if (NS_SUCCEEDED(rv)) + Done(false); + + return rv; +} + +NS_IMETHODIMP nsAbLDAPReplicationService::Done(bool aSuccess) +{ + mReplicating = false; + if (mQuery) + { + mQuery = nullptr; // Release query obj + mDirectory = nullptr; // Release directory + } + + return NS_OK; +} + + +// XXX: This method should query the RootDSE for the changeLog attribute, +// if it exists ChangeLog protocol is supported. +int32_t nsAbLDAPReplicationService::DecideProtocol() +{ + // Do the changeLog, it will decide if there is a need to replicate all + // entries or only update existing DB and will do the appropriate thing. + // + // XXX: Bug 231965 changed this from kChangeLogProtocol to + // kDefaultDownloadAll because of a problem with ldap replication not + // working correctly. We need to change this back at some stage (bug 311632). + return nsIAbLDAPProcessReplicationData::kDefaultDownloadAll; +} + + diff --git a/mailnews/addrbook/src/nsAbLDAPReplicationService.h b/mailnews/addrbook/src/nsAbLDAPReplicationService.h new file mode 100644 index 000000000..07fb768f7 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDAPReplicationService.h @@ -0,0 +1,33 @@ +/* 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/. */ + + + +#ifndef nsAbLDAPReplicationService_h___ +#define nsAbLDAPReplicationService_h___ + +#include "nsIAbLDAPReplicationService.h" +#include "nsIAbLDAPReplicationQuery.h" +#include "nsStringGlue.h" + +class nsAbLDAPReplicationService : public nsIAbLDAPReplicationService +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIABLDAPREPLICATIONSERVICE + + nsAbLDAPReplicationService(); + + int32_t DecideProtocol(); + +protected: + virtual ~nsAbLDAPReplicationService(); + nsCOMPtr<nsIAbLDAPReplicationQuery> mQuery; + bool mReplicating; + nsCOMPtr<nsIAbLDAPDirectory> mDirectory; + +}; + + +#endif /* nsAbLDAPReplicationService_h___ */ diff --git a/mailnews/addrbook/src/nsAbLDIFService.cpp b/mailnews/addrbook/src/nsAbLDIFService.cpp new file mode 100644 index 000000000..95eb4b96a --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDIFService.cpp @@ -0,0 +1,868 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ +#include "nsIAddrDatabase.h" +#include "nsStringGlue.h" +#include "nsAbLDIFService.h" +#include "nsIFile.h" +#include "nsILineInputStream.h" +#include "nsIInputStream.h" +#include "nsNetUtil.h" +#include "nsISeekableStream.h" +#include "mdb.h" +#include "plstr.h" +#include "prmem.h" +#include "prprf.h" +#include "nsCRTGlue.h" +#include "nsTArray.h" + +#include <ctype.h> + +NS_IMPL_ISUPPORTS(nsAbLDIFService, nsIAbLDIFService) + +// If we get a line longer than 32K it's just toooooo bad! +#define kTextAddressBufferSz (64 * 1024) + +nsAbLDIFService::nsAbLDIFService() +{ + mStoreLocAsHome = false; + mLFCount = 0; + mCRCount = 0; +} + +nsAbLDIFService::~nsAbLDIFService() +{ +} + +#define RIGHT2 0x03 +#define RIGHT4 0x0f +#define CONTINUED_LINE_MARKER '\001' + +// XXX TODO fix me +// use the NSPR base64 library. see plbase64.h +// see bug #145367 +static unsigned char b642nib[0x80] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0x3e, 0xff, 0xff, 0xff, 0x3f, + 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, + 0x3c, 0x3d, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, + 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, + 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30, + 0x31, 0x32, 0x33, 0xff, 0xff, 0xff, 0xff, 0xff +}; + +NS_IMETHODIMP nsAbLDIFService::ImportLDIFFile(nsIAddrDatabase *aDb, nsIFile *aSrc, bool aStoreLocAsHome, uint32_t *aProgress) +{ + NS_ENSURE_ARG_POINTER(aSrc); + NS_ENSURE_ARG_POINTER(aDb); + + mStoreLocAsHome = aStoreLocAsHome; + + char buf[1024]; + char* pBuf = &buf[0]; + int32_t startPos = 0; + uint32_t len = 0; + nsTArray<int32_t> listPosArray; // where each list/group starts in ldif file + nsTArray<int32_t> listSizeArray; // size of the list/group info + int32_t savedStartPos = 0; + int32_t filePos = 0; + uint64_t bytesLeft = 0; + + nsCOMPtr<nsIInputStream> inputStream; + nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aSrc); + NS_ENSURE_SUCCESS(rv, rv); + + // Initialize the parser for a run... + mLdifLine.Truncate(); + + while (NS_SUCCEEDED(inputStream->Available(&bytesLeft)) && bytesLeft > 0) + { + if (NS_SUCCEEDED(inputStream->Read(pBuf, sizeof(buf), &len)) && len > 0) + { + startPos = 0; + + while (NS_SUCCEEDED(GetLdifStringRecord(buf, len, startPos))) + { + if (mLdifLine.Find("groupOfNames") == -1) + AddLdifRowToDatabase(aDb, false); + else + { + //keep file position for mailing list + listPosArray.AppendElement(savedStartPos); + listSizeArray.AppendElement(filePos + startPos-savedStartPos); + ClearLdifRecordBuffer(); + } + savedStartPos = filePos + startPos; + } + filePos += len; + if (aProgress) + *aProgress = (uint32_t)filePos; + } + } + //last row + if (!mLdifLine.IsEmpty() && mLdifLine.Find("groupOfNames") == -1) + AddLdifRowToDatabase(aDb, false); + + // mail Lists + int32_t i, pos; + uint32_t size; + int32_t listTotal = listPosArray.Length(); + char *listBuf; + ClearLdifRecordBuffer(); // make sure the buffer is clean + + nsCOMPtr<nsISeekableStream> seekableStream = do_QueryInterface(inputStream, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + for (i = 0; i < listTotal; i++) + { + pos = listPosArray[i]; + size = listSizeArray[i]; + if (NS_SUCCEEDED(seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, pos))) + { + // Allocate enough space for the lists/groups as the size varies. + listBuf = (char *) PR_Malloc(size); + if (!listBuf) + continue; + if (NS_SUCCEEDED(inputStream->Read(listBuf, size, &len)) && len > 0) + { + startPos = 0; + + while (NS_SUCCEEDED(GetLdifStringRecord(listBuf, len, startPos))) + { + if (mLdifLine.Find("groupOfNames") != -1) + { + AddLdifRowToDatabase(aDb, true); + if (NS_SUCCEEDED(seekableStream->Seek(nsISeekableStream::NS_SEEK_SET, 0))) + break; + } + } + } + PR_FREEIF(listBuf); + } + } + + rv = inputStream->Close(); + NS_ENSURE_SUCCESS(rv, rv); + + // Finally commit everything to the database and return. + return aDb->Commit(nsAddrDBCommitType::kLargeCommit); +} + +/* + * str_parse_line - takes a line of the form "type:[:] value" and splits it + * into components "type" and "value". if a double colon separates type from + * value, then value is encoded in base 64, and parse_line un-decodes it + * (in place) before returning. + * in LDIF, non-ASCII data is treated as base64 encoded UTF-8 + */ + +nsresult nsAbLDIFService::str_parse_line(char *line, char **type, char **value, int *vlen) const +{ + char *p, *s, *d, *byte, *stop; + char nib; + int i, b64; + + /* skip any leading space */ + while ( isspace( *line ) ) { + line++; + } + *type = line; + + for ( s = line; *s && *s != ':'; s++ ) + ; /* NULL */ + if ( *s == '\0' ) { + return NS_ERROR_FAILURE; + } + + /* trim any space between type and : */ + for ( p = s - 1; p > line && isspace( *p ); p-- ) { + *p = '\0'; + } + *s++ = '\0'; + + /* check for double : - indicates base 64 encoded value */ + if ( *s == ':' ) { + s++; + b64 = 1; + /* single : - normally encoded value */ + } else { + b64 = 0; + } + + /* skip space between : and value */ + while ( isspace( *s ) ) { + s++; + } + + /* if no value is present, error out */ + if ( *s == '\0' ) { + return NS_ERROR_FAILURE; + } + + /* check for continued line markers that should be deleted */ + for ( p = s, d = s; *p; p++ ) { + if ( *p != CONTINUED_LINE_MARKER ) + *d++ = *p; + } + *d = '\0'; + + *value = s; + if ( b64 ) { + stop = PL_strchr( s, '\0' ); + byte = s; + for ( p = s, *vlen = 0; p < stop; p += 4, *vlen += 3 ) { + for ( i = 0; i < 3; i++ ) { + if ( p[i] != '=' && (p[i] & 0x80 || + b642nib[ p[i] & 0x7f ] > 0x3f) ) { + return NS_ERROR_FAILURE; + } + } + + /* first digit */ + nib = b642nib[ p[0] & 0x7f ]; + byte[0] = nib << 2; + /* second digit */ + nib = b642nib[ p[1] & 0x7f ]; + byte[0] |= nib >> 4; + byte[1] = (nib & RIGHT4) << 4; + /* third digit */ + if ( p[2] == '=' ) { + *vlen += 1; + break; + } + nib = b642nib[ p[2] & 0x7f ]; + byte[1] |= nib >> 2; + byte[2] = (nib & RIGHT2) << 6; + /* fourth digit */ + if ( p[3] == '=' ) { + *vlen += 2; + break; + } + nib = b642nib[ p[3] & 0x7f ]; + byte[2] |= nib; + + byte += 3; + } + s[ *vlen ] = '\0'; + } else { + *vlen = (int) (d - s); + } + return NS_OK; +} + +/* + * str_getline - return the next "line" (minus newline) of input from a + * string buffer of lines separated by newlines, terminated by \n\n + * or \0. this routine handles continued lines, bundling them into + * a single big line before returning. if a line begins with a white + * space character, it is a continuation of the previous line. the white + * space character (nb: only one char), and preceeding newline are changed + * into CONTINUED_LINE_MARKER chars, to be deleted later by the + * str_parse_line() routine above. + * + * it takes a pointer to a pointer to the buffer on the first call, + * which it updates and must be supplied on subsequent calls. + */ + +char* nsAbLDIFService::str_getline(char **next) const +{ + char *lineStr; + char c; + + if ( *next == nullptr || **next == '\n' || **next == '\0' ) { + return( nullptr); + } + + lineStr = *next; + while ( (*next = PL_strchr( *next, '\n' )) != NULL ) { + c = *(*next + 1); + if ( isspace( c ) && c != '\n' ) { + **next = CONTINUED_LINE_MARKER; + *(*next+1) = CONTINUED_LINE_MARKER; + } else { + *(*next)++ = '\0'; + break; + } + } + + return( lineStr ); +} + +nsresult nsAbLDIFService::GetLdifStringRecord(char* buf, int32_t len, int32_t& stopPos) +{ + for (; stopPos < len; stopPos++) + { + char c = buf[stopPos]; + + if (c == 0xA) + { + mLFCount++; + } + else if (c == 0xD) + { + mCRCount++; + } + else + { + if (mLFCount == 0 && mCRCount == 0) + mLdifLine.Append(c); + else if (( mLFCount > 1) || ( mCRCount > 2 && mLFCount ) || + ( !mLFCount && mCRCount > 1 )) + { + return NS_OK; + } + else if ((mLFCount == 1 || mCRCount == 1)) + { + mLdifLine.Append('\n'); + mLdifLine.Append(c); + mLFCount = 0; + mCRCount = 0; + } + } + } + + if (((stopPos == len) && (mLFCount > 1)) || (mCRCount > 2 && mLFCount) || + (!mLFCount && mCRCount > 1)) + return NS_OK; + + return NS_ERROR_FAILURE; +} + +void nsAbLDIFService::AddLdifRowToDatabase(nsIAddrDatabase *aDatabase, + bool bIsList) +{ + // If no data to process then reset CR/LF counters and return. + if (mLdifLine.IsEmpty()) + { + mLFCount = 0; + mCRCount = 0; + return; + } + + nsCOMPtr <nsIMdbRow> newRow; + if (aDatabase) + { + if (bIsList) + aDatabase->GetNewListRow(getter_AddRefs(newRow)); + else + aDatabase->GetNewRow(getter_AddRefs(newRow)); + + if (!newRow) + return; + } + else + return; + + char* cursor = ToNewCString(mLdifLine); + char* saveCursor = cursor; /* keep for deleting */ + char* line = 0; + char* typeSlot = 0; + char* valueSlot = 0; + int length = 0; // the length of an ldif attribute + while ( (line = str_getline(&cursor)) != nullptr) + { + if (NS_SUCCEEDED(str_parse_line(line, &typeSlot, &valueSlot, &length))) { + AddLdifColToDatabase(aDatabase, newRow, typeSlot, valueSlot, bIsList); + } + else + continue; // parse error: continue with next loop iteration + } + free(saveCursor); + aDatabase->AddCardRowToDB(newRow); + + if (bIsList) + aDatabase->AddListDirNode(newRow); + + // Clear buffer for next record + ClearLdifRecordBuffer(); +} + +void nsAbLDIFService::AddLdifColToDatabase(nsIAddrDatabase *aDatabase, + nsIMdbRow* newRow, char* typeSlot, + char* valueSlot, bool bIsList) +{ + nsAutoCString colType(typeSlot); + nsAutoCString column(valueSlot); + + // 4.x exports attributes like "givenname", + // mozilla does "givenName" to be compliant with RFC 2798 + ToLowerCase(colType); + + mdb_u1 firstByte = (mdb_u1)(colType.get())[0]; + switch ( firstByte ) + { + case 'b': + if (colType.EqualsLiteral("birthyear")) + aDatabase->AddBirthYear(newRow, column.get()); + else if (colType.EqualsLiteral("birthmonth")) + aDatabase->AddBirthMonth(newRow, column.get()); + else if (colType.EqualsLiteral("birthday")) + aDatabase->AddBirthDay(newRow, column.get()); + break; // 'b' + + case 'c': + if (colType.EqualsLiteral("cn") || colType.EqualsLiteral("commonname")) + { + if (bIsList) + aDatabase->AddListName(newRow, column.get()); + else + aDatabase->AddDisplayName(newRow, column.get()); + } + else if (colType.EqualsLiteral("c") || colType.EqualsLiteral("countryname")) + { + if (mStoreLocAsHome ) + aDatabase->AddHomeCountry(newRow, column.get()); + else + aDatabase->AddWorkCountry(newRow, column.get()); + } + + else if (colType.EqualsLiteral("cellphone") ) + aDatabase->AddCellularNumber(newRow, column.get()); + + else if (colType.EqualsLiteral("carphone")) + aDatabase->AddCellularNumber(newRow, column.get()); + + else if (colType.EqualsLiteral("custom1")) + aDatabase->AddCustom1(newRow, column.get()); + + else if (colType.EqualsLiteral("custom2")) + aDatabase->AddCustom2(newRow, column.get()); + + else if (colType.EqualsLiteral("custom3")) + aDatabase->AddCustom3(newRow, column.get()); + + else if (colType.EqualsLiteral("custom4")) + aDatabase->AddCustom4(newRow, column.get()); + + else if (colType.EqualsLiteral("company")) + aDatabase->AddCompany(newRow, column.get()); + break; // 'c' + + case 'd': + if (colType.EqualsLiteral("description")) + { + if (bIsList) + aDatabase->AddListDescription(newRow, column.get()); + else + aDatabase->AddNotes(newRow, column.get()); + } + + else if (colType.EqualsLiteral("department")) + aDatabase->AddDepartment(newRow, column.get()); + + else if (colType.EqualsLiteral("displayname")) + { + if (bIsList) + aDatabase->AddListName(newRow, column.get()); + else + aDatabase->AddDisplayName(newRow, column.get()); + } + break; // 'd' + + case 'f': + + if (colType.EqualsLiteral("fax") || + colType.EqualsLiteral("facsimiletelephonenumber")) + aDatabase->AddFaxNumber(newRow, column.get()); + break; // 'f' + + case 'g': + if (colType.EqualsLiteral("givenname")) + aDatabase->AddFirstName(newRow, column.get()); + + break; // 'g' + + case 'h': + if (colType.EqualsLiteral("homephone")) + aDatabase->AddHomePhone(newRow, column.get()); + + else if (colType.EqualsLiteral("homestreet")) + aDatabase->AddHomeAddress(newRow, column.get()); + + else if (colType.EqualsLiteral("homeurl")) + aDatabase->AddWebPage2(newRow, column.get()); + break; // 'h' + + case 'l': + if (colType.EqualsLiteral("l") || colType.EqualsLiteral("locality")) + { + if (mStoreLocAsHome) + aDatabase->AddHomeCity(newRow, column.get()); + else + aDatabase->AddWorkCity(newRow, column.get()); + } + // labeledURI contains a URI and, optionally, a label + // This will remove the label and place the URI as the work URL + else if (colType.EqualsLiteral("labeleduri")) + { + int32_t index = column.FindChar(' '); + if (index != -1) + column.SetLength(index); + + aDatabase->AddWebPage1(newRow, column.get()); + } + + break; // 'l' + + case 'm': + if (colType.EqualsLiteral("mail")) + aDatabase->AddPrimaryEmail(newRow, column.get()); + + else if (colType.EqualsLiteral("member") && bIsList) + aDatabase->AddLdifListMember(newRow, column.get()); + + else if (colType.EqualsLiteral("mobile")) + aDatabase->AddCellularNumber(newRow, column.get()); + + else if (colType.EqualsLiteral("mozilla_aimscreenname")) + aDatabase->AddAimScreenName(newRow, column.get()); + + else if (colType.EqualsLiteral("mozillacustom1")) + aDatabase->AddCustom1(newRow, column.get()); + + else if (colType.EqualsLiteral("mozillacustom2")) + aDatabase->AddCustom2(newRow, column.get()); + + else if (colType.EqualsLiteral("mozillacustom3")) + aDatabase->AddCustom3(newRow, column.get()); + + else if (colType.EqualsLiteral("mozillacustom4")) + aDatabase->AddCustom4(newRow, column.get()); + + else if (colType.EqualsLiteral("mozillahomecountryname")) + aDatabase->AddHomeCountry(newRow, column.get()); + + else if (colType.EqualsLiteral("mozillahomelocalityname")) + aDatabase->AddHomeCity(newRow, column.get()); + + else if (colType.EqualsLiteral("mozillahomestate")) + aDatabase->AddHomeState(newRow, column.get()); + + else if (colType.EqualsLiteral("mozillahomestreet")) + aDatabase->AddHomeAddress(newRow, column.get()); + + else if (colType.EqualsLiteral("mozillahomestreet2")) + aDatabase->AddHomeAddress2(newRow, column.get()); + + else if (colType.EqualsLiteral("mozillahomepostalcode")) + aDatabase->AddHomeZipCode(newRow, column.get()); + + else if (colType.EqualsLiteral("mozillahomeurl")) + aDatabase->AddWebPage2(newRow, column.get()); + + else if (colType.EqualsLiteral("mozillanickname")) + { + if (bIsList) + aDatabase->AddListNickName(newRow, column.get()); + else + aDatabase->AddNickName(newRow, column.get()); + } + + else if (colType.EqualsLiteral("mozillasecondemail")) + aDatabase->Add2ndEmail(newRow, column.get()); + + else if (colType.EqualsLiteral("mozillausehtmlmail")) + { + ToLowerCase(column); + if (-1 != column.Find("true")) + aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::html); + else if (-1 != column.Find("false")) + aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::plaintext); + else + aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::unknown); + } + + else if (colType.EqualsLiteral("mozillaworkstreet2")) + aDatabase->AddWorkAddress2(newRow, column.get()); + + else if (colType.EqualsLiteral("mozillaworkurl")) + aDatabase->AddWebPage1(newRow, column.get()); + + break; // 'm' + + case 'n': + if (colType.EqualsLiteral("notes")) + aDatabase->AddNotes(newRow, column.get()); + + else if (colType.EqualsLiteral("nscpaimscreenname") || + colType.EqualsLiteral("nsaimid")) + aDatabase->AddAimScreenName(newRow, column.get()); + + break; // 'n' + + case 'o': + if (colType.EqualsLiteral("objectclass")) + break; + + else if (colType.EqualsLiteral("ou") || colType.EqualsLiteral("orgunit")) + aDatabase->AddDepartment(newRow, column.get()); + + else if (colType.EqualsLiteral("o")) // organization + aDatabase->AddCompany(newRow, column.get()); + + break; // 'o' + + case 'p': + if (colType.EqualsLiteral("postalcode")) + { + if (mStoreLocAsHome) + aDatabase->AddHomeZipCode(newRow, column.get()); + else + aDatabase->AddWorkZipCode(newRow, column.get()); + } + + else if (colType.EqualsLiteral("postofficebox")) + { + nsAutoCString workAddr1, workAddr2; + SplitCRLFAddressField(column, workAddr1, workAddr2); + aDatabase->AddWorkAddress(newRow, workAddr1.get()); + aDatabase->AddWorkAddress2(newRow, workAddr2.get()); + } + else if (colType.EqualsLiteral("pager") || colType.EqualsLiteral("pagerphone")) + aDatabase->AddPagerNumber(newRow, column.get()); + + break; // 'p' + + case 'r': + if (colType.EqualsLiteral("region")) + { + aDatabase->AddWorkState(newRow, column.get()); + } + + break; // 'r' + + case 's': + if (colType.EqualsLiteral("sn") || colType.EqualsLiteral("surname")) + aDatabase->AddLastName(newRow, column.get()); + + else if (colType.EqualsLiteral("street")) + aDatabase->AddWorkAddress(newRow, column.get()); + + else if (colType.EqualsLiteral("streetaddress")) + { + nsAutoCString addr1, addr2; + SplitCRLFAddressField(column, addr1, addr2); + if (mStoreLocAsHome) + { + aDatabase->AddHomeAddress(newRow, addr1.get()); + aDatabase->AddHomeAddress2(newRow, addr2.get()); + } + else + { + aDatabase->AddWorkAddress(newRow, addr1.get()); + aDatabase->AddWorkAddress2(newRow, addr2.get()); + } + } + else if (colType.EqualsLiteral("st")) + { + if (mStoreLocAsHome) + aDatabase->AddHomeState(newRow, column.get()); + else + aDatabase->AddWorkState(newRow, column.get()); + } + + break; // 's' + + case 't': + if (colType.EqualsLiteral("title")) + aDatabase->AddJobTitle(newRow, column.get()); + + else if (colType.EqualsLiteral("telephonenumber") ) + { + aDatabase->AddWorkPhone(newRow, column.get()); + } + + break; // 't' + + case 'u': + + if (colType.EqualsLiteral("uniquemember") && bIsList) + aDatabase->AddLdifListMember(newRow, column.get()); + + break; // 'u' + + case 'w': + if (colType.EqualsLiteral("workurl")) + aDatabase->AddWebPage1(newRow, column.get()); + + break; // 'w' + + case 'x': + if (colType.EqualsLiteral("xmozillanickname")) + { + if (bIsList) + aDatabase->AddListNickName(newRow, column.get()); + else + aDatabase->AddNickName(newRow, column.get()); + } + + else if (colType.EqualsLiteral("xmozillausehtmlmail")) + { + ToLowerCase(column); + if (-1 != column.Find("true")) + aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::html); + else if (-1 != column.Find("false")) + aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::plaintext); + else + aDatabase->AddPreferMailFormat(newRow, nsIAbPreferMailFormat::unknown); + } + + break; // 'x' + + case 'z': + if (colType.EqualsLiteral("zip")) // alias for postalcode + { + if (mStoreLocAsHome) + aDatabase->AddHomeZipCode(newRow, column.get()); + else + aDatabase->AddWorkZipCode(newRow, column.get()); + } + + break; // 'z' + + default: + break; // default + } +} + +void nsAbLDIFService::ClearLdifRecordBuffer() +{ + if (!mLdifLine.IsEmpty()) + { + mLdifLine.Truncate(); + mLFCount = 0; + mCRCount = 0; + } +} + +// Some common ldif fields, it an ldif file has NONE of these entries +// then it is most likely NOT an ldif file! +static const char *const sLDIFFields[] = { + "objectclass", + "sn", + "dn", + "cn", + "givenName", + "mail", + nullptr +}; +#define kMaxLDIFLen 14 + +// Count total number of legal ldif fields and records in the first 100 lines of the +// file and if the average legal ldif field is 3 or higher than it's a valid ldif file. +NS_IMETHODIMP nsAbLDIFService::IsLDIFFile(nsIFile *pSrc, bool *_retval) +{ + NS_ENSURE_ARG_POINTER(pSrc); + NS_ENSURE_ARG_POINTER(_retval); + + *_retval = false; + + nsresult rv = NS_OK; + + nsCOMPtr<nsIInputStream> fileStream; + rv = NS_NewLocalFileInputStream(getter_AddRefs(fileStream), pSrc); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILineInputStream> lineInputStream(do_QueryInterface(fileStream, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t lineLen = 0; + int32_t lineCount = 0; + int32_t ldifFields = 0; // total number of legal ldif fields. + char field[kMaxLDIFLen]; + int32_t fLen = 0; + const char *pChar; + int32_t recCount = 0; // total number of records. + int32_t i; + bool gotLDIF = false; + bool more = true; + nsCString line; + + while (more && NS_SUCCEEDED(rv) && (lineCount < 100)) + { + rv = lineInputStream->ReadLine(line, &more); + + if (NS_SUCCEEDED(rv) && more) + { + pChar = line.get(); + lineLen = line.Length(); + if (!lineLen && gotLDIF) + { + recCount++; + gotLDIF = false; + } + + if (lineLen && (*pChar != ' ') && (*pChar != '\t')) + { + fLen = 0; + + while (lineLen && (fLen < (kMaxLDIFLen - 1)) && (*pChar != ':')) + { + field[fLen] = *pChar; + pChar++; + fLen++; + lineLen--; + } + + field[fLen] = 0; + + if (lineLen && (*pChar == ':') && (fLen < (kMaxLDIFLen - 1))) + { + // see if this is an ldif field (case insensitive)? + i = 0; + while (sLDIFFields[i]) + { + if (!PL_strcasecmp( sLDIFFields[i], field)) + { + ldifFields++; + gotLDIF = true; + break; + } + i++; + } + } + } + } + lineCount++; + } + + // If we just saw ldif address, increment recCount. + if (gotLDIF) + recCount++; + + rv = fileStream->Close(); + + if (recCount > 1) + ldifFields /= recCount; + + // If the average field number >= 3 then it's a good ldif file. + if (ldifFields >= 3) + { + *_retval = true; + } + + return rv; +} + +void nsAbLDIFService::SplitCRLFAddressField(nsCString &inputAddress, nsCString &outputLine1, nsCString &outputLine2) const +{ + int32_t crlfPos = inputAddress.Find("\r\n"); + if (crlfPos != -1) + { + outputLine1 = Substring(inputAddress, 0, crlfPos); + outputLine2 = Substring(inputAddress, crlfPos + 2); + } + else + outputLine1.Assign(inputAddress); +} + diff --git a/mailnews/addrbook/src/nsAbLDIFService.h b/mailnews/addrbook/src/nsAbLDIFService.h new file mode 100644 index 000000000..8f50559c9 --- /dev/null +++ b/mailnews/addrbook/src/nsAbLDIFService.h @@ -0,0 +1,37 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ +#ifndef __nsAbLDIFService_h +#define __nsAbLDIFService_h + +#include "nsIAbLDIFService.h" +#include "nsCOMPtr.h" + +class nsIMdbRow; + +class nsAbLDIFService : public nsIAbLDIFService +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIABLDIFSERVICE + + nsAbLDIFService(); +private: + virtual ~nsAbLDIFService(); + nsresult str_parse_line(char *line, char **type, char **value, int *vlen) const; + char * str_getline(char **next) const; + nsresult GetLdifStringRecord(char* buf, int32_t len, int32_t& stopPos); + void AddLdifRowToDatabase(nsIAddrDatabase *aDatabase, bool aIsList); + void AddLdifColToDatabase(nsIAddrDatabase *aDatabase, nsIMdbRow* newRow, + char* typeSlot, char* valueSlot, bool bIsList); + void ClearLdifRecordBuffer(); + void SplitCRLFAddressField(nsCString &inputAddress, nsCString &outputLine1, nsCString &outputLine2) const; + + bool mStoreLocAsHome; + nsCString mLdifLine; + int32_t mLFCount; + int32_t mCRCount; +}; + +#endif diff --git a/mailnews/addrbook/src/nsAbMDBCard.cpp b/mailnews/addrbook/src/nsAbMDBCard.cpp new file mode 100644 index 000000000..14af6a875 --- /dev/null +++ b/mailnews/addrbook/src/nsAbMDBCard.cpp @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbMDBCard.h" + +nsAbMDBCard::nsAbMDBCard(void) +{ +} + +nsAbMDBCard::~nsAbMDBCard(void) +{ +} + +NS_IMPL_ISUPPORTS_INHERITED0(nsAbMDBCard, nsAbCardProperty) + +NS_IMETHODIMP nsAbMDBCard::Equals(nsIAbCard *card, bool *result) +{ + NS_ENSURE_ARG_POINTER(card); + NS_ENSURE_ARG_POINTER(result); + + if (this == card) { + *result = true; + return NS_OK; + } + + // If we have the same directory, we will equal the other card merely given + // the row IDs. If not, we are never equal. But we are dumb in that we don't + // know who our directory is, which may change in the future. For now, + // however, the only known users of this method are for locating us in a list + // of cards, most commonly mailing lists; a warning on the IDL has also + // notified consumers that this method is not generally safe to use. In this + // respect, it is safe to assume that the directory portion is satisfied when + // making this call. + // However, if we make the wrong assumption, one of two things will happen. + // If the other directory is a local address book, we could return a spurious + // true result. If not, then DbRowID should be unset and we can definitively + // return false. + + uint32_t row; + nsresult rv = card->GetPropertyAsUint32("DbRowID", &row); + if (NS_FAILED(rv)) + { + *result = false; + return NS_OK; + } + + uint32_t ourRow; + rv = GetPropertyAsUint32("DbRowID", &ourRow); + NS_ENSURE_SUCCESS(rv, rv); + + *result = (row == ourRow); + return NS_OK; +} diff --git a/mailnews/addrbook/src/nsAbMDBCard.h b/mailnews/addrbook/src/nsAbMDBCard.h new file mode 100644 index 000000000..0d9123bc4 --- /dev/null +++ b/mailnews/addrbook/src/nsAbMDBCard.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsAbMDBCard_h__ +#define nsAbMDBCard_h__ + +#include "mozilla/Attributes.h" +#include "nsAbCardProperty.h" +#include "nsCOMPtr.h" + +class nsAbMDBCard: public nsAbCardProperty +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + nsAbMDBCard(void); + + NS_IMETHOD Equals(nsIAbCard *card, bool *result) override; + +private: + virtual ~nsAbMDBCard(); +}; + +#endif diff --git a/mailnews/addrbook/src/nsAbMDBDirFactory.cpp b/mailnews/addrbook/src/nsAbMDBDirFactory.cpp new file mode 100644 index 000000000..73ecb32fc --- /dev/null +++ b/mailnews/addrbook/src/nsAbMDBDirFactory.cpp @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbMDBDirFactory.h" +#include "nsAbUtils.h" +#include "nsStringGlue.h" +#include "nsServiceManagerUtils.h" +#include "nsIFile.h" +#include "nsIAbManager.h" +#include "nsIAbMDBDirectory.h" +#include "nsAbMDBDirFactory.h" +#include "nsIAddrDBListener.h" +#include "nsIAddrDatabase.h" +#include "nsEnumeratorUtils.h" +#include "nsIMutableArray.h" +#include "nsArrayUtils.h" +#include "nsAbBaseCID.h" + +NS_IMPL_ISUPPORTS(nsAbMDBDirFactory, nsIAbDirFactory) + +nsAbMDBDirFactory::nsAbMDBDirFactory() +{ +} + +nsAbMDBDirFactory::~nsAbMDBDirFactory() +{ +} + +NS_IMETHODIMP nsAbMDBDirFactory::GetDirectories(const nsAString &aDirName, + const nsACString &aURI, + const nsACString &aPrefName, + nsISimpleEnumerator **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + nsresult rv; + + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectory(aURI, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = directory->SetDirPrefId(aPrefName); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> dbPath; + rv = abManager->GetUserProfileDirectory(getter_AddRefs(dbPath)); + + nsCOMPtr<nsIAddrDatabase> listDatabase; + if (NS_SUCCEEDED(rv)) + { + nsAutoCString fileName; + + if (StringBeginsWith(aURI, NS_LITERAL_CSTRING(kMDBDirectoryRoot))) + fileName = Substring(aURI, kMDBDirectoryRootLen, aURI.Length() - kMDBDirectoryRootLen); + + rv = dbPath->AppendNative(fileName); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAddrDatabase> addrDBFactory = do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = addrDBFactory->Open(dbPath, true, true, getter_AddRefs(listDatabase)); + } + NS_ENSURE_SUCCESS(rv, rv); + + rv = listDatabase->GetMailingListsFromDB(directory); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_NewSingletonEnumerator(_retval, directory); +} + +/* void deleteDirectory (in nsIAbDirectory directory); */ +NS_IMETHODIMP nsAbMDBDirFactory::DeleteDirectory(nsIAbDirectory *directory) +{ + if (!directory) + return NS_ERROR_NULL_POINTER; + + nsresult rv = NS_OK; + + nsCOMPtr<nsIMutableArray> pAddressLists; + rv = directory->GetAddressLists(getter_AddRefs(pAddressLists)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t total; + rv = pAddressLists->GetLength(&total); + NS_ENSURE_SUCCESS(rv, rv); + + for (uint32_t i = 0; i < total; i++) + { + nsCOMPtr<nsIAbDirectory> listDir(do_QueryElementAt(pAddressLists, i, &rv)); + if (NS_FAILED(rv)) + break; + + nsCOMPtr<nsIAbMDBDirectory> dblistDir(do_QueryInterface(listDir, &rv)); + if (NS_FAILED(rv)) + break; + + rv = directory->DeleteDirectory(listDir); + if (NS_FAILED(rv)) + break; + + rv = dblistDir->RemoveElementsFromAddressList(); + if (NS_FAILED(rv)) + break; + } + pAddressLists->Clear(); + + nsCOMPtr<nsIAbMDBDirectory> dbdirectory(do_QueryInterface(directory, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + return dbdirectory->ClearDatabase(); +} + diff --git a/mailnews/addrbook/src/nsAbMDBDirFactory.h b/mailnews/addrbook/src/nsAbMDBDirFactory.h new file mode 100644 index 000000000..200175f62 --- /dev/null +++ b/mailnews/addrbook/src/nsAbMDBDirFactory.h @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsAbMDBDirFactory_h__ +#define nsAbMDBDirFactory_h__ + +#include "nsIAbDirFactory.h" + +class nsAbMDBDirFactory : public nsIAbDirFactory +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIABDIRFACTORY + + nsAbMDBDirFactory(); + +private: + virtual ~nsAbMDBDirFactory(); +}; + + +#endif diff --git a/mailnews/addrbook/src/nsAbMDBDirProperty.cpp b/mailnews/addrbook/src/nsAbMDBDirProperty.cpp new file mode 100644 index 000000000..7df904d87 --- /dev/null +++ b/mailnews/addrbook/src/nsAbMDBDirProperty.cpp @@ -0,0 +1,145 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbMDBDirProperty.h" +#include "nsIServiceManager.h" +#include "nsStringGlue.h" +#include "nsCOMPtr.h" +#include "nsAbBaseCID.h" +#include "nsAddrDatabase.h" +#include "nsIAbCard.h" +#include "nsIAbListener.h" +#include "nsArrayUtils.h" +#include "mdb.h" +#include "nsComponentManagerUtils.h" + +nsAbMDBDirProperty::nsAbMDBDirProperty(void) + : nsAbDirProperty() +{ + m_dbRowID = 0; +} + +nsAbMDBDirProperty::~nsAbMDBDirProperty(void) +{ +} + + +NS_IMPL_ISUPPORTS_INHERITED(nsAbMDBDirProperty, nsAbDirProperty, + nsIAbDirectory, + nsISupportsWeakReference, nsIAbMDBDirectory) + +//////////////////////////////////////////////////////////////////////////////// + + + +// nsIAbMDBDirectory attributes + +NS_IMETHODIMP nsAbMDBDirProperty::GetDbRowID(uint32_t *aDbRowID) +{ + *aDbRowID = m_dbRowID; + return NS_OK; +} + +NS_IMETHODIMP nsAbMDBDirProperty::SetDbRowID(uint32_t aDbRowID) +{ + m_dbRowID = aDbRowID; + return NS_OK; +} + + +// nsIAbMDBDirectory methods + +/* add mailing list to the parent directory */ +NS_IMETHODIMP nsAbMDBDirProperty::AddMailListToDirectory(nsIAbDirectory *mailList) +{ + if (!m_AddressList) + { + nsresult rv; + m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + uint32_t position; + if (NS_FAILED(m_AddressList->IndexOf(0, mailList, &position))) + m_AddressList->AppendElement(mailList, false); + + return NS_OK; +} + +/* add addresses to the mailing list */ +NS_IMETHODIMP nsAbMDBDirProperty::AddAddressToList(nsIAbCard *card) +{ + if (!m_AddressList) + { + nsresult rv; + m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + uint32_t position; + if (NS_FAILED(m_AddressList->IndexOf(0, card, &position))) + m_AddressList->AppendElement(card, false); + + return NS_OK; +} + +NS_IMETHODIMP nsAbMDBDirProperty::CopyDBMailList(nsIAbMDBDirectory* srcListDB) +{ + nsresult err = NS_OK; + nsCOMPtr<nsIAbDirectory> srcList(do_QueryInterface(srcListDB)); + if (NS_FAILED(err)) + return NS_ERROR_NULL_POINTER; + + CopyMailList (srcList); + + uint32_t rowID; + srcListDB->GetDbRowID(&rowID); + SetDbRowID(rowID); + + return NS_OK; +} + + +// nsIAbMDBDirectory NOT IMPLEMENTED methods + +/* nsIAbDirectory addDirectory (in string uriName); */ +NS_IMETHODIMP nsAbMDBDirProperty::AddDirectory(const char *uriName, nsIAbDirectory **_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [noscript] void removeElementsFromAddressList (); */ +NS_IMETHODIMP nsAbMDBDirProperty::RemoveElementsFromAddressList() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* void removeEmailAddressAt (in unsigned long aIndex); */ +NS_IMETHODIMP nsAbMDBDirProperty::RemoveEmailAddressAt(uint32_t aIndex) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [noscript] void notifyDirItemAdded (in nsISupports item); */ +NS_IMETHODIMP nsAbMDBDirProperty::NotifyDirItemAdded(nsISupports *item) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +/* [noscript] void clearDatabase (); */ +NS_IMETHODIMP nsAbMDBDirProperty::ClearDatabase() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbMDBDirProperty::GetDatabaseFile(nsIFile **aResult) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbMDBDirProperty::GetDatabase(nsIAddrDatabase **aResult) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} diff --git a/mailnews/addrbook/src/nsAbMDBDirProperty.h b/mailnews/addrbook/src/nsAbMDBDirProperty.h new file mode 100644 index 000000000..3ee90d17e --- /dev/null +++ b/mailnews/addrbook/src/nsAbMDBDirProperty.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/******************************************************************************************************** + + Interface for representing Address Book Directory + +*********************************************************************************************************/ + +#ifndef nsAbMDBDirProperty_h__ +#define nsAbMDBDirProperty_h__ + +#include "nsIAbMDBDirectory.h" +#include "nsAbDirProperty.h" +#include "nsIAbCard.h" +#include "nsCOMPtr.h" +#include "nsDirPrefs.h" +#include "nsIAddrDatabase.h" + + /* + * Address Book Directory + */ + +class nsAbMDBDirProperty: public nsIAbMDBDirectory, public nsAbDirProperty +{ +public: + nsAbMDBDirProperty(void); + + NS_DECL_ISUPPORTS + NS_DECL_NSIABMDBDIRECTORY + +protected: + virtual ~nsAbMDBDirProperty(); + + uint32_t m_dbRowID; +}; + +#endif diff --git a/mailnews/addrbook/src/nsAbMDBDirectory.cpp b/mailnews/addrbook/src/nsAbMDBDirectory.cpp new file mode 100644 index 000000000..be4799cf1 --- /dev/null +++ b/mailnews/addrbook/src/nsAbMDBDirectory.cpp @@ -0,0 +1,1125 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbMDBDirectory.h" +#include "nsStringGlue.h" +#include "nsCOMPtr.h" +#include "nsAbBaseCID.h" +#include "nsAddrDatabase.h" +#include "nsIAbListener.h" +#include "nsIAbManager.h" +#include "nsIURL.h" +#include "nsNetCID.h" +#include "nsAbDirectoryQuery.h" +#include "nsIAbDirectoryQueryProxy.h" +#include "nsAbQueryStringToExpression.h" +#include "nsIMutableArray.h" +#include "nsArrayEnumerator.h" +#include "nsEnumeratorUtils.h" +#include "mdb.h" +#include "prprf.h" +#include "nsIPrefService.h" +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsIFile.h" +#include "nsComponentManagerUtils.h" +#include "nsMemory.h" +#include "nsArrayUtils.h" +#include "nsUnicharUtils.h" +#include "mozilla/DebugOnly.h" + +nsAbMDBDirectory::nsAbMDBDirectory(void): + nsAbMDBDirProperty(), + mPerformingQuery(false) +{ +} + +nsAbMDBDirectory::~nsAbMDBDirectory(void) +{ + if (mDatabase) { + mDatabase->RemoveListener(this); + } +} + +NS_IMPL_ISUPPORTS_INHERITED(nsAbMDBDirectory, nsAbMDBDirProperty, + nsIAbDirSearchListener, + nsIAbDirectorySearch, + nsIAddrDBListener) + +NS_IMETHODIMP nsAbMDBDirectory::Init(const char *aUri) +{ + // We need to ensure that the m_DirPrefId is initialized properly + nsDependentCString uri(aUri); + + // Find the first ? (of the search params) if there is one. + // We know we can start at the end of the moz-abmdbdirectory:// because + // that's the URI we should have been passed. + int32_t searchCharLocation = uri.FindChar('?', kMDBDirectoryRootLen); + nsAutoCString URINoQuery; + if (searchCharLocation != kNotFound) + { + URINoQuery = Substring(uri, 0, searchCharLocation); + } else { + URINoQuery.Assign(uri); + } + + // In the non-query part of the URI, check if we are a mailinglist + if (URINoQuery.Find("MailList") != kNotFound) + m_IsMailList = true; + + // Mailing lists don't have their own prefs. + if (m_DirPrefId.IsEmpty() && !m_IsMailList) + { + nsAutoCString filename; + + // Extract the filename from the uri. + filename = Substring(URINoQuery, kMDBDirectoryRootLen); + + // Get the pref servers and the address book directory branch + nsresult rv; + nsCOMPtr<nsIPrefService> prefService(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefService->GetBranch(NS_LITERAL_CSTRING(PREF_LDAP_SERVER_TREE_NAME ".").get(), + getter_AddRefs(prefBranch)); + NS_ENSURE_SUCCESS(rv, rv); + + char** childArray; + uint32_t childCount, i; + int32_t dotOffset; + nsCString childValue; + nsDependentCString child; + + rv = prefBranch->GetChildList("", &childCount, &childArray); + NS_ENSURE_SUCCESS(rv, rv); + + for (i = 0; i < childCount; ++i) + { + child.Assign(childArray[i]); + + if (StringEndsWith(child, NS_LITERAL_CSTRING(".filename"))) + { + if (NS_SUCCEEDED(prefBranch->GetCharPref(child.get(), + getter_Copies(childValue)))) + { + if (childValue == filename) + { + dotOffset = child.RFindChar('.'); + if (dotOffset != -1) + { + nsAutoCString prefName(StringHead(child, dotOffset)); + m_DirPrefId.AssignLiteral(PREF_LDAP_SERVER_TREE_NAME "."); + m_DirPrefId.Append(prefName); + } + } + } + } + } + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(childCount, childArray); + + NS_ASSERTION(!m_DirPrefId.IsEmpty(), + "Error, Could not set m_DirPrefId in nsAbMDBDirectory::Init"); + } + + return nsAbDirProperty::Init(aUri); +} + +//////////////////////////////////////////////////////////////////////////////// + +nsresult nsAbMDBDirectory::RemoveCardFromAddressList(nsIAbCard* card) +{ + nsresult rv = NS_OK; + uint32_t listTotal; + int32_t i, j; + + // These checks ensure we don't run into null pointers + // as we did when we caused bug 280463. + if (!mDatabase) + { + rv = GetAbDatabase(); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (!m_AddressList) + { + rv = mDatabase->GetMailingListsFromDB(this); + NS_ENSURE_SUCCESS(rv, rv); + + // If the previous call didn't gives us an m_AddressList (and succeeded) + // then we haven't got any mailing lists to try and remove the card from. + // So just return without doing anything + if (!m_AddressList) + return NS_OK; + } + + rv = m_AddressList->GetLength(&listTotal); + NS_ENSURE_SUCCESS(rv,rv); + + for (i = listTotal - 1; i >= 0; i--) + { + nsCOMPtr<nsIAbDirectory> listDir(do_QueryElementAt(m_AddressList, i, &rv)); + if (listDir) + { + // First remove the instance in the database + mDatabase->DeleteCardFromMailList(listDir, card, false); + + // Now remove the instance in any lists we hold. + nsCOMPtr<nsIMutableArray> pAddressLists; + listDir->GetAddressLists(getter_AddRefs(pAddressLists)); + if (pAddressLists) + { + uint32_t total; + rv = pAddressLists->GetLength(&total); + for (j = total - 1; j >= 0; j--) + { + nsCOMPtr<nsIAbCard> cardInList(do_QueryElementAt(pAddressLists, j, &rv)); + bool equals; + rv = cardInList->Equals(card, &equals); // should we checking email? + if (NS_SUCCEEDED(rv) && equals) + pAddressLists->RemoveElementAt(j); + } + } + } + } + return NS_OK; +} + +NS_IMETHODIMP nsAbMDBDirectory::DeleteDirectory(nsIAbDirectory *directory) +{ + NS_ENSURE_ARG_POINTER(directory); + + nsCOMPtr<nsIAddrDatabase> database; + nsresult rv = GetDatabase(getter_AddRefs(database)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = database->DeleteMailList(directory, this); + + if (NS_SUCCEEDED(rv)) + database->Commit(nsAddrDBCommitType::kLargeCommit); + + uint32_t dirIndex; + if (m_AddressList && NS_SUCCEEDED(m_AddressList->IndexOf(0, directory, &dirIndex))) + m_AddressList->RemoveElementAt(dirIndex); + // XXX Cast from bool to nsresult + rv = static_cast<nsresult>(mSubDirectories.RemoveObject(directory)); + + NotifyItemDeleted(directory); + return rv; +} + +nsresult nsAbMDBDirectory::NotifyItemChanged(nsISupports *item) +{ + nsresult rv; + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = abManager->NotifyItemPropertyChanged(item, nullptr, nullptr, nullptr); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +nsresult nsAbMDBDirectory::NotifyPropertyChanged(nsIAbDirectory *list, const char *property, const char16_t* oldValue, const char16_t* newValue) +{ + nsresult rv; + nsCOMPtr<nsISupports> supports = do_QueryInterface(list, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = abManager->NotifyItemPropertyChanged(supports, property, oldValue, newValue); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +nsresult nsAbMDBDirectory::NotifyItemAdded(nsISupports *item) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + if(NS_SUCCEEDED(rv)) + abManager->NotifyDirectoryItemAdded(this, item); + return NS_OK; +} + +nsresult nsAbMDBDirectory::NotifyItemDeleted(nsISupports *item) +{ + nsresult rv = NS_OK; + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + if(NS_SUCCEEDED(rv)) + abManager->NotifyDirectoryItemDeleted(this, item); + + return NS_OK; +} + +// nsIAbMDBDirectory methods + +NS_IMETHODIMP nsAbMDBDirectory::ClearDatabase() +{ + if (mIsQueryURI) + return NS_ERROR_NOT_IMPLEMENTED; + + if (mDatabase) + { + mDatabase->RemoveListener(this); + mDatabase = nullptr; + } + return NS_OK; +} + +NS_IMETHODIMP nsAbMDBDirectory::RemoveElementsFromAddressList() +{ + if (mIsQueryURI) + return NS_ERROR_NOT_IMPLEMENTED; + + if (m_AddressList) + { + uint32_t count; + mozilla::DebugOnly<nsresult> rv = m_AddressList->GetLength(&count); + NS_ASSERTION(NS_SUCCEEDED(rv), "Count failed"); + int32_t i; + for (i = count - 1; i >= 0; i--) + m_AddressList->RemoveElementAt(i); + } + m_AddressList = nullptr; + return NS_OK; +} + +NS_IMETHODIMP nsAbMDBDirectory::RemoveEmailAddressAt(uint32_t aIndex) +{ + if (mIsQueryURI) + return NS_ERROR_NOT_IMPLEMENTED; + + if (m_AddressList) + { + return m_AddressList->RemoveElementAt(aIndex); + } + else + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsAbMDBDirectory::AddDirectory(const char *uriName, nsIAbDirectory **childDir) +{ + if (mIsQueryURI) + return NS_ERROR_NOT_IMPLEMENTED; + + if (!childDir || !uriName) + return NS_ERROR_NULL_POINTER; + + if (mURI.IsEmpty()) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv = NS_OK; + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectory(nsDependentCString(uriName), getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + if (mSubDirectories.IndexOf(directory) == -1) + mSubDirectories.AppendObject(directory); + NS_IF_ADDREF(*childDir = directory); + return rv; +} + +NS_IMETHODIMP nsAbMDBDirectory::GetDatabaseFile(nsIFile **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + nsCString fileName; + nsresult rv = GetStringValue("filename", EmptyCString(), fileName); + NS_ENSURE_SUCCESS(rv, rv); + + if (fileName.IsEmpty()) + return NS_ERROR_NOT_INITIALIZED; + + nsCOMPtr<nsIFile> dbFile; + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, + getter_AddRefs(dbFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = dbFile->AppendNative(fileName); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ADDREF(*aResult = dbFile); + + return NS_OK; +} + +NS_IMETHODIMP nsAbMDBDirectory::GetDatabase(nsIAddrDatabase **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + nsresult rv; + nsCOMPtr<nsIFile> databaseFile; + rv = GetDatabaseFile(getter_AddRefs(databaseFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAddrDatabase> addrDBFactory = + do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return addrDBFactory->Open(databaseFile, false /* no create */, true, + aResult); +} + +// nsIAbDirectory methods + +NS_IMETHODIMP nsAbMDBDirectory::GetURI(nsACString &aURI) +{ + if (mURI.IsEmpty()) + return NS_ERROR_NOT_INITIALIZED; + + aURI = mURI; + return NS_OK; +} + +NS_IMETHODIMP nsAbMDBDirectory::GetChildNodes(nsISimpleEnumerator* *aResult) +{ + if (mIsQueryURI) + return NS_NewEmptyEnumerator(aResult); + + return NS_NewArrayEnumerator(aResult, mSubDirectories); +} + +NS_IMETHODIMP nsAbMDBDirectory::GetChildCards(nsISimpleEnumerator* *result) +{ + nsresult rv; + + if (mIsQueryURI) + { + rv = StartSearch(); + NS_ENSURE_SUCCESS(rv, rv); + + // TODO + // Search is synchronous so need to return + // results after search is complete + nsCOMPtr<nsIMutableArray> array(do_CreateInstance(NS_ARRAY_CONTRACTID)); + for (auto iter = mSearchCache.Iter(); !iter.Done(); iter.Next()) { + array->AppendElement(iter.Data(), false); + } + return NS_NewArrayEnumerator(result, array); + } + + rv = GetAbDatabase(); + + if (NS_FAILED(rv) || !mDatabase) + return rv; + + return m_IsMailList ? mDatabase->EnumerateListAddresses(this, result) : + mDatabase->EnumerateCards(this, result); +} + +NS_IMETHODIMP nsAbMDBDirectory::GetIsQuery(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mIsQueryURI; + return NS_OK; +} + +NS_IMETHODIMP nsAbMDBDirectory::DeleteCards(nsIArray *aCards) +{ + NS_ENSURE_ARG_POINTER(aCards); + nsresult rv = NS_OK; + + if (mIsQueryURI) { + // if this is a query, delete the cards from the directory (without the query) + // before we do the delete, make this directory (which represents the search) + // a listener on the database, so that it will get notified when the cards are deleted + // after delete, remove this query as a listener. + nsCOMPtr<nsIAddrDatabase> database; + rv = GetDatabase(getter_AddRefs(database)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = database->AddListener(this); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbManager> abManager = + do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectory(mURINoQuery, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = directory->DeleteCards(aCards); + NS_ENSURE_SUCCESS(rv, rv); + + rv = database->RemoveListener(this); + NS_ENSURE_SUCCESS(rv, rv); + return rv; + } + + if (!mDatabase) + rv = GetAbDatabase(); + + if (NS_SUCCEEDED(rv) && mDatabase) + { + uint32_t cardCount; + uint32_t i; + rv = aCards->GetLength(&cardCount); + NS_ENSURE_SUCCESS(rv, rv); + for (i = 0; i < cardCount; i++) + { + nsCOMPtr<nsIAbCard> card(do_QueryElementAt(aCards, i, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + if (card) + { + uint32_t rowID; + rv = card->GetPropertyAsUint32("DbRowID", &rowID); + NS_ENSURE_SUCCESS(rv, rv); + + if (m_IsMailList) + { + mDatabase->DeleteCardFromMailList(this, card, true); + + uint32_t cardTotal = 0; + int32_t i; + if (m_AddressList) + rv = m_AddressList->GetLength(&cardTotal); + for (i = cardTotal - 1; i >= 0; i--) + { + nsCOMPtr<nsIAbCard> arrayCard(do_QueryElementAt(m_AddressList, i, &rv)); + if (arrayCard) + { + // No card can have a row ID of 0 + uint32_t arrayRowID = 0; + arrayCard->GetPropertyAsUint32("DbRowID", &arrayRowID); + if (rowID == arrayRowID) + m_AddressList->RemoveElementAt(i); + } + } + } + else + { + mDatabase->DeleteCard(card, true, this); + bool bIsMailList = false; + card->GetIsMailList(&bIsMailList); + if (bIsMailList) + { + //to do, get mailing list dir side uri and notify nsIAbManager to remove it + nsAutoCString listUri(mURI); + listUri.AppendLiteral("/MailList"); + listUri.AppendInt(rowID); + if (!listUri.IsEmpty()) + { + nsresult rv = NS_OK; + + nsCOMPtr<nsIAbManager> abManager = + do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> listDir; + rv = abManager->GetDirectory(listUri, getter_AddRefs(listDir)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t dirIndex; + if (m_AddressList && NS_SUCCEEDED(m_AddressList->IndexOf(0, listDir, &dirIndex))) + m_AddressList->RemoveElementAt(dirIndex); + + mSubDirectories.RemoveObject(listDir); + + if (listDir) + NotifyItemDeleted(listDir); + } + } + else + { + rv = RemoveCardFromAddressList(card); + NS_ENSURE_SUCCESS(rv,rv); + } + } + } + } + mDatabase->Commit(nsAddrDBCommitType::kLargeCommit); + } + return rv; +} + +NS_IMETHODIMP nsAbMDBDirectory::HasCard(nsIAbCard *cards, bool *hasCard) +{ + if(!hasCard) + return NS_ERROR_NULL_POINTER; + + if (mIsQueryURI) + { + *hasCard = mSearchCache.Get(cards, nullptr); + return NS_OK; + } + + nsresult rv = NS_OK; + if (!mDatabase) + rv = GetAbDatabase(); + + if(NS_SUCCEEDED(rv) && mDatabase) + { + if(NS_SUCCEEDED(rv)) + rv = mDatabase->ContainsCard(cards, hasCard); + } + return rv; +} + +NS_IMETHODIMP nsAbMDBDirectory::HasDirectory(nsIAbDirectory *dir, bool *hasDir) +{ + if (!hasDir) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + nsCOMPtr<nsIAbMDBDirectory> dbdir(do_QueryInterface(dir, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool bIsMailingList = false; + dir->GetIsMailList(&bIsMailingList); + if (bIsMailingList) + { + nsCOMPtr<nsIAddrDatabase> database; + rv = GetDatabase(getter_AddRefs(database)); + + if (NS_SUCCEEDED(rv)) + rv = database->ContainsMailList(dir, hasDir); + } + + return rv; +} + +NS_IMETHODIMP nsAbMDBDirectory::HasMailListWithName(const char16_t *aName, bool *aHasList) +{ + NS_ENSURE_ARG_POINTER(aHasList); + + nsCOMPtr<nsIAddrDatabase> database; + nsresult rv = GetDatabase(getter_AddRefs(database)); + if (NS_SUCCEEDED(rv)) + { + rv = database->FindMailListbyUnicodeName(aName, aHasList); + if (NS_SUCCEEDED(rv) && *aHasList) + return NS_OK; + } + + return rv; +} + +NS_IMETHODIMP nsAbMDBDirectory::AddMailList(nsIAbDirectory *list, nsIAbDirectory **addedList) +{ + NS_ENSURE_ARG_POINTER(addedList); + + if (mIsQueryURI) + return NS_ERROR_NOT_IMPLEMENTED; + + nsresult rv = NS_OK; + if (!mDatabase) + rv = GetAbDatabase(); + + if (NS_FAILED(rv) || !mDatabase) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIAbMDBDirectory> dblist(do_QueryInterface(list, &rv)); + if (NS_FAILED(rv)) + { + nsCOMPtr<nsIAbDirectory> newlist(new nsAbMDBDirProperty); + if (!newlist) + return NS_ERROR_OUT_OF_MEMORY; + + rv = newlist->CopyMailList(list); + NS_ENSURE_SUCCESS(rv, rv); + + dblist = do_QueryInterface(newlist, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + mDatabase->CreateMailListAndAddToDB(newlist, true, this); + } + else + mDatabase->CreateMailListAndAddToDB(list, true, this); + + mDatabase->Commit(nsAddrDBCommitType::kLargeCommit); + + uint32_t dbRowID; + dblist->GetDbRowID(&dbRowID); + + nsAutoCString listUri(mURI); + listUri.AppendLiteral("/MailList"); + listUri.AppendInt(dbRowID); + + nsCOMPtr<nsIAbDirectory> newList; + rv = AddDirectory(listUri.get(), getter_AddRefs(newList)); + if (NS_SUCCEEDED(rv) && newList) + { + nsCOMPtr<nsIAbMDBDirectory> dbnewList(do_QueryInterface(newList, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + dbnewList->CopyDBMailList(dblist); + AddMailListToDirectory(newList); + NotifyItemAdded(newList); + } + + NS_IF_ADDREF(*addedList = newList); + return rv; +} + +NS_IMETHODIMP nsAbMDBDirectory::AddCard(nsIAbCard* card, nsIAbCard **addedCard) +{ + if (mIsQueryURI) + return NS_ERROR_NOT_IMPLEMENTED; + + nsresult rv = NS_OK; + if (!mDatabase) + rv = GetAbDatabase(); + + if (NS_FAILED(rv) || !mDatabase) + return NS_ERROR_FAILURE; + + if (m_IsMailList) + rv = mDatabase->CreateNewListCardAndAddToDB(this, m_dbRowID, card, true /* notify */); + else + rv = mDatabase->CreateNewCardAndAddToDB(card, true, this); + NS_ENSURE_SUCCESS(rv, rv); + + mDatabase->Commit(nsAddrDBCommitType::kLargeCommit); + + NS_IF_ADDREF(*addedCard = card); + return NS_OK; +} + +NS_IMETHODIMP nsAbMDBDirectory::ModifyCard(nsIAbCard *aModifiedCard) +{ + NS_ENSURE_ARG_POINTER(aModifiedCard); + + nsresult rv; + if (!mDatabase) + { + rv = GetAbDatabase(); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = mDatabase->EditCard(aModifiedCard, true, this); + NS_ENSURE_SUCCESS(rv, rv); + return mDatabase->Commit(nsAddrDBCommitType::kLargeCommit); +} + +NS_IMETHODIMP nsAbMDBDirectory::DropCard(nsIAbCard* aCard, bool needToCopyCard) +{ + NS_ENSURE_ARG_POINTER(aCard); + + if (mIsQueryURI) + return NS_ERROR_NOT_IMPLEMENTED; + + nsresult rv = NS_OK; + + if (!mDatabase) + rv = GetAbDatabase(); + + if (NS_FAILED(rv) || !mDatabase) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIAbCard> newCard; + + if (needToCopyCard) { + newCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = newCard->Copy(aCard); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + newCard = aCard; + } + + if (m_IsMailList) { + if (needToCopyCard) { + nsCOMPtr <nsIMdbRow> cardRow; + // if card doesn't exist in db, add the card to the directory that + // contains the mailing list. + mDatabase->FindRowByCard(newCard, getter_AddRefs(cardRow)); + if (!cardRow) + mDatabase->CreateNewCardAndAddToDB(newCard, true /* notify */, this); + else + mDatabase->InitCardFromRow(newCard, cardRow); + } + // since we didn't copy the card, we don't have to notify that it was inserted + mDatabase->CreateNewListCardAndAddToDB(this, m_dbRowID, newCard, false /* notify */); + } + else { + mDatabase->CreateNewCardAndAddToDB(newCard, true /* notify */, this); + } + mDatabase->Commit(nsAddrDBCommitType::kLargeCommit); + return NS_OK; +} + +NS_IMETHODIMP nsAbMDBDirectory::EditMailListToDatabase(nsIAbCard *listCard) +{ + if (mIsQueryURI) + return NS_ERROR_NOT_IMPLEMENTED; + + if (!m_IsMailList) + return NS_ERROR_UNEXPECTED; + + nsresult rv = GetAbDatabase(); + NS_ENSURE_SUCCESS(rv, rv); + + mDatabase->EditMailList(this, listCard, true); + mDatabase->Commit(nsAddrDBCommitType::kLargeCommit); + + return NS_OK; +} + +static bool ContainsDirectory(nsIAbDirectory *parent, nsIAbDirectory *directory) +{ + // If parent is a maillist, 'addressLists' contains AbCards. + bool bIsMailList = false; + nsresult rv = parent->GetIsMailList(&bIsMailList); + NS_ENSURE_SUCCESS(rv, false); + + if (bIsMailList) + return false; + + nsCOMPtr<nsIMutableArray> pAddressLists; + parent->GetAddressLists(getter_AddRefs(pAddressLists)); + if (pAddressLists) + { + uint32_t total; + rv = pAddressLists->GetLength(&total); + for (uint32_t i = 0; i < total; ++i) + { + nsCOMPtr<nsIAbDirectory> pList(do_QueryElementAt(pAddressLists, i, &rv)); + + if (directory == pList) + return true; + } + } + + return false; +} + +// nsIAddrDBListener methods + +NS_IMETHODIMP nsAbMDBDirectory::OnCardAttribChange(uint32_t abCode) +{ + return NS_OK; +} + +NS_IMETHODIMP nsAbMDBDirectory::OnCardEntryChange +(uint32_t aAbCode, nsIAbCard *aCard, nsIAbDirectory *aParent) +{ + // Don't notify AbManager unless we have the parent + if (!aParent) + return NS_OK; + + NS_ENSURE_ARG_POINTER(aCard); + nsCOMPtr<nsISupports> cardSupports(do_QueryInterface(aCard)); + nsresult rv; + + // Notify when + // - any operation is done to a card belonging to this + // => if <this> is <aParent>, or + // - a card belonging to a directory which is parent of this is deleted + // => if aAbCode is AB_NotifyDeleted && <this> is child of <aParent>, or + // - a card belonging to a directory which is child of this is added/modified + // => if aAbCode is !AB_NotifyDeleted && <this> is parent of <aParent> + + if (aParent != this) + { + bool isChild = false; + if (aAbCode != AB_NotifyDeleted) + isChild = ContainsDirectory(this, aParent); + else + isChild = ContainsDirectory(aParent, this); + + if (!isChild) + return NS_OK; + } + + switch (aAbCode) { + case AB_NotifyInserted: + rv = NotifyItemAdded(cardSupports); + break; + case AB_NotifyDeleted: + rv = NotifyItemDeleted(cardSupports); + break; + case AB_NotifyPropertyChanged: + rv = NotifyItemChanged(cardSupports); + break; + default: + rv = NS_ERROR_UNEXPECTED; + break; + } + + NS_ENSURE_SUCCESS(rv, rv); + return rv; +} + +NS_IMETHODIMP nsAbMDBDirectory::OnListEntryChange +(uint32_t abCode, nsIAbDirectory *list) +{ + nsresult rv = NS_OK; + + if (abCode == AB_NotifyPropertyChanged && list) + { + bool bIsMailList = false; + rv = list->GetIsMailList(&bIsMailList); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIAbMDBDirectory> dblist(do_QueryInterface(list, &rv)); + NS_ENSURE_SUCCESS(rv,rv); + + if (bIsMailList) { + nsString listName; + rv = list->GetDirName(listName); + NS_ENSURE_SUCCESS(rv,rv); + + rv = NotifyPropertyChanged(list, "DirName", nullptr, listName.get()); + NS_ENSURE_SUCCESS(rv,rv); + } + } + return rv; +} + +NS_IMETHODIMP nsAbMDBDirectory::OnAnnouncerGoingAway() +{ + if (mDatabase) + mDatabase->RemoveListener(this); + return NS_OK; +} + +// nsIAbDirectorySearch methods + +NS_IMETHODIMP nsAbMDBDirectory::StartSearch() +{ + if (!mIsQueryURI) + return NS_ERROR_FAILURE; + + nsresult rv; + + mPerformingQuery = true; + mSearchCache.Clear(); + + nsCOMPtr<nsIAbDirectoryQueryArguments> arguments = do_CreateInstance(NS_ABDIRECTORYQUERYARGUMENTS_CONTRACTID,&rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbBooleanExpression> expression; + rv = nsAbQueryStringToExpression::Convert(mQueryString, + getter_AddRefs(expression)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = arguments->SetExpression(expression); + NS_ENSURE_SUCCESS(rv, rv); + + // don't search the subdirectories + // if the current directory is a mailing list, it won't have any subdirectories + // if the current directory is a addressbook, searching both it + // and the subdirectories (the mailing lists), will yield duplicate results + // because every entry in a mailing list will be an entry in the parent addressbook + rv = arguments->SetQuerySubDirectories(false); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbManager> abManager = + do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the directory without the query + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectory(mURINoQuery, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + // Bug 280232 - something was causing continuous loops in searching. Add a + // check here for the directory to search not being a query uri as well in + // the hopes that will at least break us out of the continuous loop even if + // we don't know how we got into it. + bool isQuery; + rv = directory->GetIsQuery(&isQuery); + NS_ENSURE_SUCCESS(rv, rv); + + if (isQuery) + { + NS_ERROR("Attempting to search a directory within a search"); + return NS_ERROR_FAILURE; + } + + // Initiate the proxy query with the no query directory + nsCOMPtr<nsIAbDirectoryQueryProxy> queryProxy = + do_CreateInstance(NS_ABDIRECTORYQUERYPROXY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = queryProxy->Initiate(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = queryProxy->DoQuery(directory, arguments, this, -1, 0, &mContext); + return NS_OK; +} + +NS_IMETHODIMP nsAbMDBDirectory::StopSearch() +{ + if (!mIsQueryURI) + return NS_ERROR_FAILURE; + + return NS_OK; +} + + +// nsAbDirSearchListenerContext methods + +NS_IMETHODIMP nsAbMDBDirectory::OnSearchFinished(int32_t aResult, + const nsAString &aErrorMsg) +{ + mPerformingQuery = false; + return NS_OK; +} + +NS_IMETHODIMP nsAbMDBDirectory::OnSearchFoundCard(nsIAbCard* card) +{ + mSearchCache.Put(card, card); + + // TODO + // Search is synchronous so asserting on the + // datasource will not work since the getChildCards + // method will not have returned with results. + // NotifyItemAdded (card); + return NS_OK; +} + +nsresult nsAbMDBDirectory::GetAbDatabase() +{ + if (mURI.IsEmpty()) + return NS_ERROR_NOT_INITIALIZED; + + if (mDatabase) + return NS_OK; + + nsresult rv; + + if (m_IsMailList) + { + // Get the database of the parent directory. + nsAutoCString parentURI(mURINoQuery); + + int32_t pos = parentURI.RFindChar('/'); + + // If we didn't find a / something really bad has happened + if (pos == -1) + return NS_ERROR_FAILURE; + + parentURI = StringHead(parentURI, pos); + + nsCOMPtr<nsIAbManager> abManager = + do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectory(parentURI, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbMDBDirectory> mdbDir(do_QueryInterface(directory, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mdbDir->GetDatabase(getter_AddRefs(mDatabase)); + } + else + rv = GetDatabase(getter_AddRefs(mDatabase)); + + if (NS_SUCCEEDED(rv)) + rv = mDatabase->AddListener(this); + + return rv; +} + +NS_IMETHODIMP nsAbMDBDirectory::CardForEmailAddress(const nsACString &aEmailAddress, nsIAbCard ** aAbCard) +{ + NS_ENSURE_ARG_POINTER(aAbCard); + + *aAbCard = nullptr; + + // Ensure that if we've not been given an email address we never match + // so that we don't fail out unnecessarily and we don't match a blank email + // address against random cards that the user hasn't supplied an email for. + if (aEmailAddress.IsEmpty()) + return NS_OK; + + nsresult rv = NS_OK; + if (!mDatabase) + rv = GetAbDatabase(); + if (rv == NS_ERROR_FILE_NOT_FOUND) + { + // If file wasn't found, the card cannot exist. + return NS_OK; + } + NS_ENSURE_SUCCESS(rv, rv); + + // Convert Email to lower case in UTF-16 format. This correctly lower-cases + // it and doing this change means that we can use a hash lookup in the + // database rather than searching and comparing each line individually. + NS_ConvertUTF8toUTF16 lowerEmail(aEmailAddress); + ToLowerCase(lowerEmail); + + // If lower email is empty, something went wrong somewhere, e.g. the conversion. + // Hence, don't go looking for a card with no email address. Something is wrong. + if (lowerEmail.IsEmpty()) + return NS_ERROR_FAILURE; + + mDatabase->GetCardFromAttribute(this, kLowerPriEmailColumn, + NS_ConvertUTF16toUTF8(lowerEmail), + false, aAbCard); + if (!*aAbCard) + { + mDatabase->GetCardFromAttribute(this, kLower2ndEmailColumn, + NS_ConvertUTF16toUTF8(lowerEmail), + false, aAbCard); + } + return NS_OK; +} + +NS_IMETHODIMP nsAbMDBDirectory::GetCardFromProperty(const char *aProperty, + const nsACString &aValue, + bool caseSensitive, + nsIAbCard **result) +{ + NS_ENSURE_ARG(aProperty); + NS_ENSURE_ARG_POINTER(result); + + *result = nullptr; + + // If the value is empty, don't match. + if (aValue.IsEmpty()) + return NS_OK; + + nsresult rv; + if (!mDatabase) + { + rv = GetAbDatabase(); + // We can't find the database file, so we can't find the card at all. + if (rv == NS_ERROR_FILE_NOT_FOUND) + return NS_OK; + NS_ENSURE_SUCCESS(rv, rv); + } + + // nsIAddrDatabase has aCaseInsensitive as its parameter + return mDatabase->GetCardFromAttribute(this, aProperty, aValue, + !caseSensitive, result); +} + +NS_IMETHODIMP nsAbMDBDirectory::GetCardsFromProperty(const char *aProperty, + const nsACString &aValue, + bool caseSensitive, + nsISimpleEnumerator **result) +{ + NS_ENSURE_ARG(aProperty); + NS_ENSURE_ARG_POINTER(result); + + *result = nullptr; + + if (aValue.IsEmpty()) + return NS_OK; + + if (!mDatabase) + { + nsresult rv = GetAbDatabase(); + if (rv == NS_ERROR_FILE_NOT_FOUND) + return NS_OK; + NS_ENSURE_SUCCESS(rv, rv); + } + + return mDatabase->GetCardsFromAttribute(this, aProperty, aValue, + !caseSensitive, result); +} diff --git a/mailnews/addrbook/src/nsAbMDBDirectory.h b/mailnews/addrbook/src/nsAbMDBDirectory.h new file mode 100644 index 000000000..fb25c5708 --- /dev/null +++ b/mailnews/addrbook/src/nsAbMDBDirectory.h @@ -0,0 +1,104 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/******************************************************************************************************** + + Interface for representing Address Book Directory + +*********************************************************************************************************/ + +#ifndef nsAbMDBDirectory_h__ +#define nsAbMDBDirectory_h__ + +#include "mozilla/Attributes.h" +#include "nsAbMDBDirProperty.h" +#include "nsIAbCard.h" +#include "nsCOMArray.h" +#include "nsCOMPtr.h" +#include "nsDirPrefs.h" +#include "nsIAbDirectorySearch.h" +#include "nsIAbDirSearchListener.h" +#include "nsInterfaceHashtable.h" +#include "nsIAddrDBListener.h" + +/* + * Address Book Directory + */ + +class nsAbMDBDirectory: + public nsAbMDBDirProperty, // nsIAbDirectory, nsIAbMDBDirectory + public nsIAbDirSearchListener, + public nsIAddrDBListener, + public nsIAbDirectorySearch +{ +public: + nsAbMDBDirectory(void); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIADDRDBLISTENER + + // Override nsAbMDBDirProperty::Init + NS_IMETHOD Init(const char *aUri) override; + + // nsIAbMDBDirectory methods + NS_IMETHOD GetURI(nsACString &aURI) override; + NS_IMETHOD ClearDatabase() override; + NS_IMETHOD NotifyDirItemAdded(nsISupports *item) override { return NotifyItemAdded(item);} + NS_IMETHOD RemoveElementsFromAddressList() override; + NS_IMETHOD RemoveEmailAddressAt(uint32_t aIndex) override; + NS_IMETHOD AddDirectory(const char *uriName, nsIAbDirectory **childDir) override; + NS_IMETHOD GetDatabaseFile(nsIFile **aResult) override; + NS_IMETHOD GetDatabase(nsIAddrDatabase **aResult) override; + + // nsIAbDirectory methods: + NS_IMETHOD GetChildNodes(nsISimpleEnumerator* *result) override; + NS_IMETHOD GetChildCards(nsISimpleEnumerator* *result) override; + NS_IMETHOD GetIsQuery(bool *aResult) override; + NS_IMETHOD DeleteDirectory(nsIAbDirectory *directory) override; + NS_IMETHOD DeleteCards(nsIArray *cards) override; + NS_IMETHOD HasCard(nsIAbCard *cards, bool *hasCard) override; + NS_IMETHOD HasDirectory(nsIAbDirectory *dir, bool *hasDir) override; + NS_IMETHOD HasMailListWithName(const char16_t *aName, bool *aHasList) override; + NS_IMETHOD AddMailList(nsIAbDirectory *list, nsIAbDirectory **addedList) override; + NS_IMETHOD AddCard(nsIAbCard *card, nsIAbCard **addedCard) override; + NS_IMETHOD ModifyCard(nsIAbCard *aModifiedCard) override; + NS_IMETHOD DropCard(nsIAbCard *card, bool needToCopyCard) override; + NS_IMETHOD EditMailListToDatabase(nsIAbCard *listCard) override; + NS_IMETHOD CardForEmailAddress(const nsACString &aEmailAddress, + nsIAbCard ** aAbCard) override; + NS_IMETHOD GetCardFromProperty(const char *aProperty, + const nsACString &aValue, + bool caseSensitive, nsIAbCard **result) override; + NS_IMETHOD GetCardsFromProperty(const char *aProperty, + const nsACString &aValue, + bool caseSensitive, + nsISimpleEnumerator **result) override; + + // nsIAbDirectorySearch methods + NS_DECL_NSIABDIRECTORYSEARCH + + // nsIAbDirSearchListener methods + NS_DECL_NSIABDIRSEARCHLISTENER + +protected: + virtual ~nsAbMDBDirectory(); + nsresult NotifyPropertyChanged(nsIAbDirectory *list, const char *property, const char16_t* oldValue, const char16_t* newValue); + nsresult NotifyItemAdded(nsISupports *item); + nsresult NotifyItemDeleted(nsISupports *item); + nsresult NotifyItemChanged(nsISupports *item); + nsresult RemoveCardFromAddressList(nsIAbCard* card); + + nsresult GetAbDatabase(); + nsCOMPtr<nsIAddrDatabase> mDatabase; + + nsCOMArray<nsIAbDirectory> mSubDirectories; + + int32_t mContext; + bool mPerformingQuery; + + nsInterfaceHashtable<nsISupportsHashKey, nsIAbCard> mSearchCache; +}; + +#endif diff --git a/mailnews/addrbook/src/nsAbManager.cpp b/mailnews/addrbook/src/nsAbManager.cpp new file mode 100644 index 000000000..2de1b4468 --- /dev/null +++ b/mailnews/addrbook/src/nsAbManager.cpp @@ -0,0 +1,1422 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbManager.h" +#include "nsAbBaseCID.h" +#include "nsAddrDatabase.h" +#include "nsIAbMDBDirectory.h" +#include "nsIOutputStream.h" +#include "nsNetUtil.h" +#include "nsMsgI18N.h" +#include "nsIStringBundle.h" +#include "nsMsgUtils.h" +#include "nsAppDirectoryServiceDefs.h" +#include "plstr.h" +#include "prmem.h" +#include "nsIServiceManager.h" +#include "mozIDOMWindow.h" +#include "nsIFilePicker.h" +#include "plbase64.h" +#include "nsIWindowWatcher.h" +#include "nsDirectoryServiceUtils.h" +#include "nsVCard.h" +#include "nsVCardObj.h" +#include "nsIAbLDAPAttributeMap.h" +#include "nsICommandLine.h" +#include "nsIFile.h" +#include "nsIMutableArray.h" +#include "nsArrayUtils.h" +#include "nsDirectoryServiceUtils.h" +#include "nsIObserverService.h" +#include "nsDirPrefs.h" +#include "nsThreadUtils.h" +#include "nsIAbDirFactory.h" +#include "nsComponentManagerUtils.h" +#include "nsIIOService.h" +#include "nsAbQueryStringToExpression.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Services.h" +using namespace mozilla; + +struct ExportAttributesTableStruct +{ + const char* abPropertyName; + uint32_t plainTextStringID; +}; + +// our schema is not fixed yet, but we still want some sort of objectclass +// for now, use obsolete in the class name, hinting that this will change +// see bugs bug #116692 and #118454 +#define MOZ_AB_OBJECTCLASS "mozillaAbPersonAlpha" + +// for now, the oder of the attributes with true for includeForPlainText +// should be in the same order as they are in the import code +// see importMsgProperties and nsImportStringBundle. +// +// XXX todo, merge with what's in nsAbLDAPProperties.cpp, so we can +// use this for LDAP and LDIF export +// +// here's how we're coming up with the ldapPropertyName values +// if they are specified in RFC 2798, use them +// else use the 4.x LDIF attribute names (for example, "xmozillanickname" +// as we want to allow export from mozilla back to 4.x, and other apps +// are probably out there that can handle 4.x LDIF) +// else use the MOZ_AB_LDIF_PREFIX prefix, see nsIAddrDatabase.idl + +const ExportAttributesTableStruct EXPORT_ATTRIBUTES_TABLE[] = { + {kFirstNameProperty, 2100}, + {kLastNameProperty, 2101}, + {kDisplayNameProperty, 2102}, + {kNicknameProperty, 2103}, + {kPriEmailProperty, 2104}, + {k2ndEmailProperty, 2105}, + {kScreenNameProperty, 2136}, + {kPreferMailFormatProperty, 0}, + {kLastModifiedDateProperty, 0}, + {kWorkPhoneProperty, 2106}, + {kWorkPhoneTypeProperty, 0}, + {kHomePhoneProperty, 2107}, + {kHomePhoneTypeProperty, 0}, + {kFaxProperty, 2108}, + {kFaxTypeProperty, 0}, + {kPagerProperty, 2109}, + {kPagerTypeProperty, 0}, + {kCellularProperty, 2110}, + {kCellularTypeProperty, 0}, + {kHomeAddressProperty, 2111}, + {kHomeAddress2Property, 2112}, + {kHomeCityProperty, 2113}, + {kHomeStateProperty, 2114}, + {kHomeZipCodeProperty, 2115}, + {kHomeCountryProperty, 2116}, + {kWorkAddressProperty, 2117}, + {kWorkAddress2Property, 2118}, + {kWorkCityProperty, 2119}, + {kWorkStateProperty, 2120}, + {kWorkZipCodeProperty, 2121}, + {kWorkCountryProperty, 2122}, + {kJobTitleProperty, 2123}, + {kDepartmentProperty, 2124}, + {kCompanyProperty, 2125}, + {kWorkWebPageProperty, 2126}, + {kHomeWebPageProperty, 2127}, + {kBirthYearProperty, 2128}, // unused for now + {kBirthMonthProperty, 2129}, // unused for now + {kBirthDayProperty, 2130}, // unused for now + {kCustom1Property, 2131}, + {kCustom2Property, 2132}, + {kCustom3Property, 2133}, + {kCustom4Property, 2134}, + {kNotesProperty, 2135}, + {kAnniversaryYearProperty, 0}, + {kAnniversaryMonthProperty, 0}, + {kAnniversaryDayProperty, 0}, + {kSpouseNameProperty, 0}, + {kFamilyNameProperty, 0}, +}; + +// +// nsAbManager +// +nsAbManager::nsAbManager() +{ +} + +nsAbManager::~nsAbManager() +{ +} + +NS_IMPL_ISUPPORTS(nsAbManager, nsIAbManager, nsICommandLineHandler, + nsIObserver) + +nsresult nsAbManager::Init() +{ + NS_ENSURE_TRUE(NS_IsMainThread(), NS_ERROR_FAILURE); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED); + + nsresult rv = observerService->AddObserver(this, "profile-do-change", false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, + false); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP nsAbManager::Observe(nsISupports *aSubject, const char *aTopic, + const char16_t *someData) +{ + // The nsDirPrefs code caches all the directories that it got + // from the first profiles prefs.js. + // When we profile switch, we need to force it to shut down. + // We'll re-load all the directories from the second profiles prefs.js + // that happens in nsAbBSDirectory::GetChildNodes() + // when we call DIR_GetDirectories(). + if (!strcmp(aTopic, "profile-do-change")) + { + DIR_ShutDown(); + return NS_OK; + } + + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) + { + DIR_ShutDown(); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + NS_ENSURE_TRUE(observerService, NS_ERROR_UNEXPECTED); + + nsresult rv = observerService->RemoveObserver(this, "profile-do-change"); + NS_ENSURE_SUCCESS(rv, rv); + + rv = observerService->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +// +// nsIAbManager +// + +NS_IMETHODIMP nsAbManager::GetDirectories(nsISimpleEnumerator **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + // We cache the top level AB to ensure that nsIAbDirectory items are not + // created and dumped every time GetDirectories is called. This was causing + // performance problems, especially with the content policy on messages + // with lots of urls. + nsresult rv; + nsCOMPtr<nsIAbDirectory> rootAddressBook; + rv = GetRootDirectory(getter_AddRefs(rootAddressBook)); + NS_ENSURE_SUCCESS(rv, rv); + + return rootAddressBook->GetChildNodes(aResult); +} + +nsresult +nsAbManager::GetRootDirectory(nsIAbDirectory **aResult) +{ + // We cache the top level AB to ensure that nsIAbDirectory items are not + // created and dumped every time GetDirectories is called. This was causing + // performance problems, especially with the content policy on messages + // with lots of urls. + nsresult rv; + + if (!mCacheTopLevelAb) + { + nsCOMPtr<nsIAbDirectory> rootAddressBook(do_GetService(NS_ABDIRECTORY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + mCacheTopLevelAb = rootAddressBook; + } + + NS_IF_ADDREF(*aResult = mCacheTopLevelAb); + return NS_OK; +} + +NS_IMETHODIMP nsAbManager::GetDirectoryFromId(const nsACString &aDirPrefId, + nsIAbDirectory **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + nsCOMPtr<nsISimpleEnumerator> enumerator; + nsresult rv = GetDirectories(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsISupports> support; + nsCOMPtr<nsIAbDirectory> directory; + + bool hasMore = false; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) { + rv = enumerator->GetNext(getter_AddRefs(support)); + NS_ENSURE_SUCCESS(rv, rv); + directory = do_QueryInterface(support, &rv); + if (NS_FAILED(rv)) { + NS_WARNING("Unable to select Address book nsAbManager::GetDirectoryFromId()"); + continue; + } + + nsCString dirPrefId; + directory->GetDirPrefId(dirPrefId); + if (dirPrefId.Equals(aDirPrefId)) { + directory.forget(aResult); + return NS_OK; + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsAbManager::GetDirectory(const nsACString &aURI, + nsIAbDirectory **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + nsresult rv; + nsCOMPtr<nsIAbDirectory> directory; + + // Was the directory root requested? + if (aURI.EqualsLiteral(kAllDirectoryRoot)) + { + rv = GetRootDirectory(getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + NS_IF_ADDREF(*aResult = directory); + return NS_OK; + } + + // Do we have a copy of this directory already within our look-up table? + if (!mAbStore.Get(aURI, getter_AddRefs(directory))) + { + // The directory wasn't in our look-up table, so we need to instantiate + // it. First, extract the scheme from the URI... + + nsAutoCString scheme; + + int32_t colon = aURI.FindChar(':'); + if (colon <= 0) + return NS_ERROR_MALFORMED_URI; + scheme = Substring(aURI, 0, colon); + + // Construct the appropriate nsIAbDirectory... + nsAutoCString contractID; + contractID.AssignLiteral(NS_AB_DIRECTORY_TYPE_CONTRACTID_PREFIX); + contractID.Append(scheme); + directory = do_CreateInstance(contractID.get(), &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Init it with the URI + rv = directory->Init(PromiseFlatCString(aURI).get()); + NS_ENSURE_SUCCESS(rv, rv); + + // Check if this directory was initiated with a search query. If so, + // we don't cache it. + bool isQuery = false; + rv = directory->GetIsQuery(&isQuery); + NS_ENSURE_SUCCESS(rv, rv); + + if (!isQuery) + mAbStore.Put(aURI, directory); + } + NS_IF_ADDREF(*aResult = directory); + + return NS_OK; +} + +NS_IMETHODIMP nsAbManager::NewAddressBook(const nsAString &aDirName, + const nsACString &aURI, + const uint32_t aType, + const nsACString &aPrefName, + nsACString &aResult) +{ + nsresult rv; + + nsCOMPtr<nsIAbDirectory> parentDir; + rv = GetRootDirectory(getter_AddRefs(parentDir)); + NS_ENSURE_SUCCESS(rv, rv); + return parentDir->CreateNewDirectory(aDirName, aURI, aType, aPrefName, aResult); +} + +NS_IMETHODIMP nsAbManager::DeleteAddressBook(const nsACString &aURI) +{ + // Find the address book + nsresult rv; + + nsCOMPtr<nsIAbDirectory> directory; + rv = GetDirectory(aURI, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> rootDirectory; + rv = GetRootDirectory(getter_AddRefs(rootDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + + // Go through each of the children of the address book + // (so, the mailing lists) and remove their entries from + // the look up table. + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = directory->GetChildNodes(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupports> item; + nsCOMPtr<nsIAbDirectory> childDirectory; + bool hasMore = false; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + rv = enumerator->GetNext(getter_AddRefs(item)); + NS_ENSURE_SUCCESS(rv, rv); + + childDirectory = do_QueryInterface(item, &rv); + if (NS_SUCCEEDED(rv)) + { + nsCString childURI; + rv = childDirectory->GetURI(childURI); + NS_ENSURE_SUCCESS(rv, rv); + + mAbStore.Remove(childURI); + } + } + + mAbStore.Remove(aURI); + + bool isMailList; + rv = directory->GetIsMailList(&isMailList); + NS_ENSURE_SUCCESS(rv, rv); + + if (!isMailList) + // If we're not a mailing list, then our parent + // must be the root address book directory. + return rootDirectory->DeleteDirectory(directory); + + nsCString parentUri; + parentUri.Append(aURI); + int32_t pos = parentUri.RFindChar('/'); + + // If we didn't find a /, we're in trouble. + if (pos == -1) + return NS_ERROR_FAILURE; + + parentUri = StringHead(parentUri, pos); + nsCOMPtr<nsIAbDirectory> parentDirectory; + rv = GetDirectory(parentUri, getter_AddRefs(parentDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + + return parentDirectory->DeleteDirectory(directory); +} + +NS_IMETHODIMP nsAbManager::AddAddressBookListener(nsIAbListener *aListener, + abListenerNotifyFlagValue aNotifyFlags) +{ + NS_ENSURE_ARG_POINTER(aListener); + + abListener newListener(aListener, aNotifyFlags); + mListeners.AppendElementUnlessExists(newListener); + return NS_OK; +} + +NS_IMETHODIMP nsAbManager::RemoveAddressBookListener(nsIAbListener *aListener) +{ + NS_ENSURE_ARG_POINTER(aListener); + + mListeners.RemoveElement(aListener); + return NS_OK; +} + +#define NOTIFY_AB_LISTENERS(propertyflag_, propertyfunc_, params_) \ + PR_BEGIN_MACRO \ + nsTObserverArray<abListener>::ForwardIterator iter(mListeners); \ + while (iter.HasMore()) { \ + const abListener &abL = iter.GetNext(); \ + if (abL.mNotifyFlags & nsIAbListener::propertyflag_) \ + abL.mListener->propertyfunc_ params_; \ + } \ + PR_END_MACRO + +NS_IMETHODIMP nsAbManager::NotifyItemPropertyChanged(nsISupports *aItem, + const char *aProperty, + const char16_t* aOldValue, + const char16_t* aNewValue) +{ + NOTIFY_AB_LISTENERS(itemChanged, OnItemPropertyChanged, + (aItem, aProperty, aOldValue, aNewValue)); + return NS_OK; +} + +NS_IMETHODIMP nsAbManager::NotifyDirectoryItemAdded(nsIAbDirectory *aParentDirectory, + nsISupports *aItem) +{ + NOTIFY_AB_LISTENERS(itemAdded, OnItemAdded, (aParentDirectory, aItem)); + return NS_OK; +} + +NS_IMETHODIMP nsAbManager::NotifyDirectoryItemDeleted(nsIAbDirectory *aParentDirectory, + nsISupports *aItem) +{ + NOTIFY_AB_LISTENERS(directoryItemRemoved, OnItemRemoved, + (aParentDirectory, aItem)); + return NS_OK; +} + +NS_IMETHODIMP nsAbManager::NotifyDirectoryDeleted(nsIAbDirectory *aParentDirectory, + nsISupports *aDirectory) +{ + NOTIFY_AB_LISTENERS(directoryRemoved, OnItemRemoved, + (aParentDirectory, aDirectory)); + return NS_OK; +} + +NS_IMETHODIMP nsAbManager::GetUserProfileDirectory(nsIFile **userDir) +{ + NS_ENSURE_ARG_POINTER(userDir); + *userDir = nullptr; + + nsresult rv; + nsCOMPtr<nsIFile> profileDir; + nsAutoCString pathBuf; + + rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(profileDir)); + NS_ENSURE_SUCCESS(rv, rv); + + profileDir.forget(userDir); + return NS_OK; +} + +NS_IMETHODIMP nsAbManager::MailListNameExists(const char16_t *aName, bool *aExists) +{ + nsresult rv; + NS_ENSURE_ARG_POINTER(aExists); + + *aExists = false; + + // now get the top-level book + nsCOMPtr<nsIAbDirectory> topDirectory; + rv = GetRootDirectory(getter_AddRefs(topDirectory)); + + // now go through the address books + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = topDirectory->GetChildNodes(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr<nsISupports> item; + rv = enumerator->GetNext(getter_AddRefs(item)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> directory = do_QueryInterface(item, &rv); + if (NS_FAILED(rv)) + continue; + + rv = directory->HasMailListWithName(aName, aExists); + if (NS_SUCCEEDED(rv) && *aExists) + return NS_OK; + } + + *aExists = false; + return NS_OK; +} + +#define CSV_DELIM "," +#define CSV_DELIM_LEN 1 +#define TAB_DELIM "\t" +#define TAB_DELIM_LEN 1 + +#define CSV_FILE_EXTENSION ".csv" +#define TAB_FILE_EXTENSION ".tab" +#define TXT_FILE_EXTENSION ".txt" +#define VCF_FILE_EXTENSION ".vcf" +#define LDIF_FILE_EXTENSION ".ldi" +#define LDIF_FILE_EXTENSION2 ".ldif" + +enum ADDRESSBOOK_EXPORT_FILE_TYPE +{ + CSV_EXPORT_TYPE = 0, + CSV_EXPORT_TYPE_UTF8 = 1, + TAB_EXPORT_TYPE = 2, + TAB_EXPORT_TYPE_UTF8 = 3, + VCF_EXPORT_TYPE = 4, + LDIF_EXPORT_TYPE = 5, +}; + +NS_IMETHODIMP nsAbManager::ExportAddressBook(mozIDOMWindowProxy *aParentWin, nsIAbDirectory *aDirectory) +{ + NS_ENSURE_ARG_POINTER(aParentWin); + + nsresult rv; + nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString dirName; + aDirectory->GetDirName(dirName); + const char16_t *formatStrings[] = { dirName.get() }; + + nsString title; + rv = bundle->FormatStringFromName(u"ExportAddressBookNameTitle", formatStrings, + ArrayLength(formatStrings), getter_Copies(title)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = filePicker->Init(aParentWin, title, nsIFilePicker::modeSave); + NS_ENSURE_SUCCESS(rv, rv); + + filePicker->SetDefaultString(dirName); + + nsString filterString; + + // CSV: System charset and UTF-8. + rv = bundle->GetStringFromName(u"CSVFilesSysCharset", getter_Copies(filterString)); + NS_ENSURE_SUCCESS(rv, rv); + rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.csv")); + NS_ENSURE_SUCCESS(rv, rv); + rv = bundle->GetStringFromName(u"CSVFilesUTF8", getter_Copies(filterString)); + NS_ENSURE_SUCCESS(rv, rv); + rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.csv")); + NS_ENSURE_SUCCESS(rv, rv); + + // Tab separated: System charset and UTF-8. + rv = bundle->GetStringFromName(u"TABFilesSysCharset", getter_Copies(filterString)); + NS_ENSURE_SUCCESS(rv, rv); + rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.tab; *.txt")); + NS_ENSURE_SUCCESS(rv, rv); + rv = bundle->GetStringFromName(u"TABFilesUTF8", getter_Copies(filterString)); + NS_ENSURE_SUCCESS(rv, rv); + rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.tab; *.txt")); + NS_ENSURE_SUCCESS(rv, rv); + + rv = bundle->GetStringFromName(u"VCFFiles", getter_Copies(filterString)); + NS_ENSURE_SUCCESS(rv, rv); + rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.vcf")); + NS_ENSURE_SUCCESS(rv, rv); + + rv = bundle->GetStringFromName(u"LDIFFiles", getter_Copies(filterString)); + NS_ENSURE_SUCCESS(rv, rv); + rv = filePicker->AppendFilter(filterString, NS_LITERAL_STRING("*.ldi; *.ldif")); + NS_ENSURE_SUCCESS(rv, rv); + + int16_t dialogResult; + filePicker->Show(&dialogResult); + + if (dialogResult == nsIFilePicker::returnCancel) + return rv; + + nsCOMPtr<nsIFile> localFile; + rv = filePicker->GetFile(getter_AddRefs(localFile)); + NS_ENSURE_SUCCESS(rv, rv); + + if (dialogResult == nsIFilePicker::returnReplace) { + // be extra safe and only delete when the file is really a file + bool isFile; + rv = localFile->IsFile(&isFile); + if (NS_SUCCEEDED(rv) && isFile) { + rv = localFile->Remove(false /* recursive delete */); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + // The type of export is determined by the drop-down in + // the file picker dialog. + int32_t exportType; + rv = filePicker->GetFilterIndex(&exportType); + NS_ENSURE_SUCCESS(rv,rv); + + nsAutoString fileName; + rv = localFile->GetLeafName(fileName); + NS_ENSURE_SUCCESS(rv, rv); + + switch ( exportType ) + { + default: + case LDIF_EXPORT_TYPE: // ldif + // If filename does not have the correct ext, add one. + if ((MsgFind(fileName, LDIF_FILE_EXTENSION, true, fileName.Length() - strlen(LDIF_FILE_EXTENSION)) == -1) && + (MsgFind(fileName, LDIF_FILE_EXTENSION2, true, fileName.Length() - strlen(LDIF_FILE_EXTENSION2)) == -1)) { + + // Add the extension and build a new localFile. + fileName.AppendLiteral(LDIF_FILE_EXTENSION2); + localFile->SetLeafName(fileName); + } + rv = ExportDirectoryToLDIF(aDirectory, localFile); + break; + + case CSV_EXPORT_TYPE: // csv + case CSV_EXPORT_TYPE_UTF8: + // If filename does not have the correct ext, add one. + if (MsgFind(fileName, CSV_FILE_EXTENSION, true, fileName.Length() - strlen(CSV_FILE_EXTENSION)) == -1) { + + // Add the extension and build a new localFile. + fileName.AppendLiteral(CSV_FILE_EXTENSION); + localFile->SetLeafName(fileName); + } + rv = ExportDirectoryToDelimitedText(aDirectory, CSV_DELIM, CSV_DELIM_LEN, localFile, + exportType==CSV_EXPORT_TYPE_UTF8); + break; + + case TAB_EXPORT_TYPE: // tab & text + case TAB_EXPORT_TYPE_UTF8: + // If filename does not have the correct ext, add one. + if ((MsgFind(fileName, TXT_FILE_EXTENSION, true, fileName.Length() - strlen(TXT_FILE_EXTENSION)) == -1) && + (MsgFind(fileName, TAB_FILE_EXTENSION, true, fileName.Length() - strlen(TAB_FILE_EXTENSION)) == -1)) { + + // Add the extension and build a new localFile. + fileName.AppendLiteral(TXT_FILE_EXTENSION); + localFile->SetLeafName(fileName); + } + rv = ExportDirectoryToDelimitedText(aDirectory, TAB_DELIM, TAB_DELIM_LEN, localFile, + exportType==TAB_EXPORT_TYPE_UTF8); + break; + + case VCF_EXPORT_TYPE: // vCard + // If filename does not have the correct ext, add one. + if (MsgFind(fileName, VCF_FILE_EXTENSION, true, fileName.Length() - strlen(VCF_FILE_EXTENSION)) == -1) { + + // Add the extension and build a new localFile. + fileName.AppendLiteral(VCF_FILE_EXTENSION); + localFile->SetLeafName(fileName); + } + rv = ExportDirectoryToVCard(aDirectory, localFile); + break; + }; + + return rv; +} + +nsresult +nsAbManager::ExportDirectoryToDelimitedText(nsIAbDirectory *aDirectory, + const char *aDelim, + uint32_t aDelimLen, + nsIFile *aLocalFile, + bool useUTF8) +{ + nsCOMPtr <nsISimpleEnumerator> cardsEnumerator; + nsCOMPtr <nsIAbCard> card; + + nsresult rv; + + nsCOMPtr <nsIOutputStream> outputStream; + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(outputStream), + aLocalFile, + PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE, + 0664); + + // the desired file may be read only + if (NS_FAILED(rv)) + return rv; + + uint32_t i; + uint32_t writeCount; + uint32_t length; + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/importMsgs.properties", getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString revisedName; + nsString columnName; + + for (i = 0; i < ArrayLength(EXPORT_ATTRIBUTES_TABLE); i++) { + if (EXPORT_ATTRIBUTES_TABLE[i].plainTextStringID != 0) { + + // We don't need to truncate the string here as getter_Copies will + // do that for us. + if (NS_FAILED(bundle->GetStringFromID(EXPORT_ATTRIBUTES_TABLE[i].plainTextStringID, getter_Copies(columnName)))) + columnName.AppendInt(EXPORT_ATTRIBUTES_TABLE[i].plainTextStringID); + + rv = nsMsgI18NConvertFromUnicode(useUTF8 ? "UTF-8" : nsMsgI18NFileSystemCharset(), + columnName, revisedName); + NS_ENSURE_SUCCESS(rv,rv); + + rv = outputStream->Write(revisedName.get(), + revisedName.Length(), + &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + + if (revisedName.Length() != writeCount) + return NS_ERROR_FAILURE; + + if (i < ArrayLength(EXPORT_ATTRIBUTES_TABLE) - 1) { + rv = outputStream->Write(aDelim, aDelimLen, &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + + if (aDelimLen != writeCount) + return NS_ERROR_FAILURE; + } + } + } + rv = outputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + if (MSG_LINEBREAK_LEN != writeCount) + return NS_ERROR_FAILURE; + + rv = aDirectory->GetChildCards(getter_AddRefs(cardsEnumerator)); + if (NS_SUCCEEDED(rv) && cardsEnumerator) { + nsCOMPtr<nsISupports> item; + bool more; + while (NS_SUCCEEDED(cardsEnumerator->HasMoreElements(&more)) && more) { + rv = cardsEnumerator->GetNext(getter_AddRefs(item)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr <nsIAbCard> card = do_QueryInterface(item, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + bool isMailList; + rv = card->GetIsMailList(&isMailList); + NS_ENSURE_SUCCESS(rv,rv); + + + if (isMailList) { + // .tab, .txt and .csv aren't able to export mailing lists + // use LDIF for that. + } + else { + nsString value; + nsCString valueCStr; + + for (i = 0; i < ArrayLength(EXPORT_ATTRIBUTES_TABLE); i++) { + if (EXPORT_ATTRIBUTES_TABLE[i].plainTextStringID != 0) { + rv = card->GetPropertyAsAString(EXPORT_ATTRIBUTES_TABLE[i].abPropertyName, value); + if (NS_FAILED(rv)) + value.Truncate(); + + // If a string contains at least one comma, tab or double quote then + // we need to quote the entire string. Also if double quote is part + // of the string we need to quote the double quote(s) as well. + nsAutoString newValue(value); + bool needsQuotes = false; + if(newValue.FindChar('"') != -1) + { + needsQuotes = true; + + int32_t match = 0; + uint32_t offset = 0; + nsString oldSubstr = NS_LITERAL_STRING("\""); + nsString newSubstr = NS_LITERAL_STRING("\"\""); + while (offset < newValue.Length()) { + match = newValue.Find(oldSubstr, offset); + if (match == -1) + break; + + newValue.Replace(offset + match, oldSubstr.Length(), newSubstr); + offset += (match + newSubstr.Length()); + } + } + if (!needsQuotes && (newValue.FindChar(',') != -1 || newValue.FindChar('\x09') != -1)) + needsQuotes = true; + + // Make sure we quote if containing CR/LF. + if (newValue.FindChar('\r') != -1 || + newValue.FindChar('\n') != -1) + needsQuotes = true; + + if (needsQuotes) + { + newValue.Insert(NS_LITERAL_STRING("\""), 0); + newValue.AppendLiteral("\""); + } + + rv = nsMsgI18NConvertFromUnicode(useUTF8 ? "UTF-8" : nsMsgI18NFileSystemCharset(), + newValue, valueCStr); + NS_ENSURE_SUCCESS(rv,rv); + + if (NS_FAILED(rv)) { + NS_ERROR("failed to convert string to system charset. use LDIF"); + valueCStr = "?"; + } + + length = valueCStr.Length(); + if (length) { + rv = outputStream->Write(valueCStr.get(), length, &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + if (length != writeCount) + return NS_ERROR_FAILURE; + } + valueCStr = ""; + } + else { + // something we don't support for the current export + // for example, .tab doesn't export preferred html format + continue; // go to next field + } + + if (i < ArrayLength(EXPORT_ATTRIBUTES_TABLE) - 1) { + rv = outputStream->Write(aDelim, aDelimLen, &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + if (aDelimLen != writeCount) + return NS_ERROR_FAILURE; + } + } + + // write out the linebreak that separates the cards + rv = outputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + if (MSG_LINEBREAK_LEN != writeCount) + return NS_ERROR_FAILURE; + } + } + } + } + + rv = outputStream->Flush(); + NS_ENSURE_SUCCESS(rv,rv); + + rv = outputStream->Close(); + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; +} + +nsresult +nsAbManager::ExportDirectoryToVCard(nsIAbDirectory *aDirectory, nsIFile *aLocalFile) +{ + nsCOMPtr <nsISimpleEnumerator> cardsEnumerator; + nsCOMPtr <nsIAbCard> card; + + nsresult rv; + + nsCOMPtr <nsIOutputStream> outputStream; + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(outputStream), + aLocalFile, + PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE, + 0664); + + // the desired file may be read only + if (NS_FAILED(rv)) + return rv; + + uint32_t writeCount; + uint32_t length; + + rv = aDirectory->GetChildCards(getter_AddRefs(cardsEnumerator)); + if (NS_SUCCEEDED(rv) && cardsEnumerator) { + nsCOMPtr<nsISupports> item; + bool more; + while (NS_SUCCEEDED(cardsEnumerator->HasMoreElements(&more)) && more) { + rv = cardsEnumerator->GetNext(getter_AddRefs(item)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr <nsIAbCard> card = do_QueryInterface(item, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + bool isMailList; + rv = card->GetIsMailList(&isMailList); + NS_ENSURE_SUCCESS(rv,rv); + + if (isMailList) { + // we don't know how to export mailing lists to vcf + // use LDIF for that. + } + else { + nsCString escapedValue; + rv = card->TranslateTo(NS_LITERAL_CSTRING("vcard"), escapedValue); + NS_ENSURE_SUCCESS(rv,rv); + + nsCString valueCStr; + MsgUnescapeString(escapedValue, 0, valueCStr); + + length = valueCStr.Length(); + rv = outputStream->Write(valueCStr.get(), length, &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + if (length != writeCount) + return NS_ERROR_FAILURE; + } + } + } + } + + rv = outputStream->Flush(); + NS_ENSURE_SUCCESS(rv,rv); + + rv = outputStream->Close(); + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; +} + + +nsresult +nsAbManager::ExportDirectoryToLDIF(nsIAbDirectory *aDirectory, nsIFile *aLocalFile) +{ + nsCOMPtr <nsISimpleEnumerator> cardsEnumerator; + nsCOMPtr <nsIAbCard> card; + + nsresult rv; + + nsCOMPtr <nsIOutputStream> outputStream; + rv = MsgNewBufferedFileOutputStream(getter_AddRefs(outputStream), + aLocalFile, + PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE, + 0664); + + // the desired file may be read only + if (NS_FAILED(rv)) + return rv; + + // Get the default attribute map for ldap. We use the default attribute + // map rather than one for a specific server because if people want an + // ldif export using a servers specific schema, then they can use ldapsearch + nsCOMPtr<nsIAbLDAPAttributeMapService> mapSrv = + do_GetService("@mozilla.org/addressbook/ldap-attribute-map-service;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbLDAPAttributeMap> attrMap; + rv = mapSrv->GetMapForPrefBranch(NS_LITERAL_CSTRING("ldap_2.servers.default.attrmap"), + getter_AddRefs(attrMap)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t i; + uint32_t writeCount; + uint32_t length; + + rv = aDirectory->GetChildCards(getter_AddRefs(cardsEnumerator)); + if (NS_SUCCEEDED(rv) && cardsEnumerator) { + nsCOMPtr<nsISupports> item; + bool more; + while (NS_SUCCEEDED(cardsEnumerator->HasMoreElements(&more)) && more) { + rv = cardsEnumerator->GetNext(getter_AddRefs(item)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr <nsIAbCard> card = do_QueryInterface(item, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + bool isMailList; + rv = card->GetIsMailList(&isMailList); + NS_ENSURE_SUCCESS(rv,rv); + + if (isMailList) { + nsCString mailListCStr; + + rv = AppendLDIFForMailList(card, attrMap, mailListCStr); + NS_ENSURE_SUCCESS(rv,rv); + + length = mailListCStr.Length(); + rv = outputStream->Write(mailListCStr.get(), length, &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + if (length != writeCount) + return NS_ERROR_FAILURE; + } + else { + nsString value; + nsCString valueCStr; + + rv = AppendBasicLDIFForCard(card, attrMap, valueCStr); + NS_ENSURE_SUCCESS(rv,rv); + + length = valueCStr.Length(); + rv = outputStream->Write(valueCStr.get(), length, &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + if (length != writeCount) + return NS_ERROR_FAILURE; + + valueCStr.Truncate(); + + nsAutoCString ldapAttribute; + + for (i = 0; i < ArrayLength(EXPORT_ATTRIBUTES_TABLE); i++) { + if (NS_SUCCEEDED(attrMap->GetFirstAttribute(nsDependentCString(EXPORT_ATTRIBUTES_TABLE[i].abPropertyName), + ldapAttribute)) && + !ldapAttribute.IsEmpty()) { + + rv = card->GetPropertyAsAString(EXPORT_ATTRIBUTES_TABLE[i].abPropertyName, value); + if (NS_FAILED(rv)) + value.Truncate(); + + if (!PL_strcmp(EXPORT_ATTRIBUTES_TABLE[i].abPropertyName, kPreferMailFormatProperty)) { + if (value.EqualsLiteral("html")) + value.AssignLiteral("true"); + else if (value.EqualsLiteral("plaintext")) + value.AssignLiteral("false"); + else + value.Truncate(); // unknown. + } + + if (!value.IsEmpty()) { + rv = AppendProperty(ldapAttribute.get(), value.get(), valueCStr); + NS_ENSURE_SUCCESS(rv,rv); + + valueCStr += MSG_LINEBREAK; + } + else + valueCStr.Truncate(); + + length = valueCStr.Length(); + if (length) { + rv = outputStream->Write(valueCStr.get(), length, &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + if (length != writeCount) + return NS_ERROR_FAILURE; + } + valueCStr.Truncate(); + } + else { + // something we don't support yet + // ldif doesn't export multiple addresses + } + } + + // write out the linebreak that separates the cards + rv = outputStream->Write(MSG_LINEBREAK, MSG_LINEBREAK_LEN, &writeCount); + NS_ENSURE_SUCCESS(rv,rv); + if (MSG_LINEBREAK_LEN != writeCount) + return NS_ERROR_FAILURE; + } + } + } + } + + rv = outputStream->Flush(); + NS_ENSURE_SUCCESS(rv,rv); + + rv = outputStream->Close(); + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; +} + +nsresult nsAbManager::AppendLDIFForMailList(nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult) +{ + nsresult rv; + nsString attrValue; + + rv = AppendDNForCard("dn", aCard, aAttrMap, aResult); + NS_ENSURE_SUCCESS(rv,rv); + + aResult += MSG_LINEBREAK \ + "objectclass: top" MSG_LINEBREAK \ + "objectclass: groupOfNames" MSG_LINEBREAK; + + rv = aCard->GetDisplayName(attrValue); + NS_ENSURE_SUCCESS(rv,rv); + + nsAutoCString ldapAttributeName; + + rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kDisplayNameProperty), + ldapAttributeName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AppendProperty(ldapAttributeName.get(), attrValue.get(), aResult); + NS_ENSURE_SUCCESS(rv,rv); + aResult += MSG_LINEBREAK; + + rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kNicknameProperty), + ldapAttributeName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aCard->GetPropertyAsAString(kNicknameProperty, attrValue); + if (NS_SUCCEEDED(rv) && !attrValue.IsEmpty()) { + rv = AppendProperty(ldapAttributeName.get(), attrValue.get(), aResult); + NS_ENSURE_SUCCESS(rv,rv); + aResult += MSG_LINEBREAK; + } + + rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kNotesProperty), + ldapAttributeName); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aCard->GetPropertyAsAString(kNotesProperty, attrValue); + if (NS_SUCCEEDED(rv) && !attrValue.IsEmpty()) { + rv = AppendProperty(ldapAttributeName.get(), attrValue.get(), aResult); + NS_ENSURE_SUCCESS(rv,rv); + aResult += MSG_LINEBREAK; + } + + nsCString mailListURI; + rv = aCard->GetMailListURI(getter_Copies(mailListURI)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsIAbDirectory> mailList; + rv = GetDirectory(mailListURI, getter_AddRefs(mailList)); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr<nsIMutableArray> addresses; + rv = mailList->GetAddressLists(getter_AddRefs(addresses)); + if (addresses) { + uint32_t total = 0; + addresses->GetLength(&total); + if (total) { + uint32_t i; + for (i = 0; i < total; i++) { + nsCOMPtr <nsIAbCard> listCard = do_QueryElementAt(addresses, i, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = AppendDNForCard("member", listCard, aAttrMap, aResult); + NS_ENSURE_SUCCESS(rv,rv); + + aResult += MSG_LINEBREAK; + } + } + } + + aResult += MSG_LINEBREAK; + return NS_OK; +} + +nsresult nsAbManager::AppendDNForCard(const char *aProperty, nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult) +{ + nsString email; + nsString displayName; + nsAutoCString ldapAttributeName; + + nsresult rv = aCard->GetPrimaryEmail(email); + NS_ENSURE_SUCCESS(rv,rv); + + rv = aCard->GetDisplayName(displayName); + NS_ENSURE_SUCCESS(rv,rv); + + nsString cnStr; + + rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kDisplayNameProperty), + ldapAttributeName); + NS_ENSURE_SUCCESS(rv, rv); + + if (!displayName.IsEmpty()) { + cnStr += NS_ConvertUTF8toUTF16(ldapAttributeName).get(); + cnStr.AppendLiteral("="); + cnStr.Append(displayName); + if (!email.IsEmpty()) { + cnStr.AppendLiteral(","); + } + } + + rv = aAttrMap->GetFirstAttribute(NS_LITERAL_CSTRING(kPriEmailProperty), + ldapAttributeName); + NS_ENSURE_SUCCESS(rv, rv); + + if (!email.IsEmpty()) { + cnStr += NS_ConvertUTF8toUTF16(ldapAttributeName).get(); + cnStr.AppendLiteral("="); + cnStr.Append(email); + } + + rv = AppendProperty(aProperty, cnStr.get(), aResult); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +nsresult nsAbManager::AppendBasicLDIFForCard(nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult) +{ + nsresult rv = AppendDNForCard("dn", aCard, aAttrMap, aResult); + NS_ENSURE_SUCCESS(rv,rv); + aResult += MSG_LINEBREAK \ + "objectclass: top" MSG_LINEBREAK \ + "objectclass: person" MSG_LINEBREAK \ + "objectclass: organizationalPerson" MSG_LINEBREAK \ + "objectclass: inetOrgPerson" MSG_LINEBREAK \ + "objectclass: " MOZ_AB_OBJECTCLASS MSG_LINEBREAK; + return rv; +} + +bool nsAbManager::IsSafeLDIFString(const char16_t *aStr) +{ + // follow RFC 2849 to determine if something is safe "as is" for LDIF + if (aStr[0] == char16_t(' ') || + aStr[0] == char16_t(':') || + aStr[0] == char16_t('<')) + return false; + + uint32_t i; + uint32_t len = NS_strlen(aStr); + for (i=0; i<len; i++) { + // If string contains CR or LF, it is not safe for LDIF + // and MUST be base64 encoded + if ((aStr[i] == char16_t('\n')) || + (aStr[i] == char16_t('\r')) || + (!NS_IsAscii(aStr[i]))) + return false; + } + return true; +} + +nsresult nsAbManager::AppendProperty(const char *aProperty, const char16_t *aValue, nsACString &aResult) +{ + NS_ENSURE_ARG_POINTER(aValue); + + aResult += aProperty; + + // if the string is not safe "as is", base64 encode it + if (IsSafeLDIFString(aValue)) { + aResult.AppendLiteral(": "); + aResult.Append(NS_LossyConvertUTF16toASCII(aValue)); + } + else { + char *base64Str = PL_Base64Encode(NS_ConvertUTF16toUTF8(aValue).get(), 0, nullptr); + if (!base64Str) + return NS_ERROR_OUT_OF_MEMORY; + + aResult.AppendLiteral(":: "); + aResult.Append(nsDependentCString(base64Str)); + PR_Free(base64Str); + } + + return NS_OK; +} + +char *getCString(VObject *vObj) +{ + if (VALUE_TYPE(vObj) == VCVT_USTRINGZ) + return fakeCString(vObjectUStringZValue(vObj)); + if (VALUE_TYPE(vObj) == VCVT_STRINGZ) + return PL_strdup(vObjectStringZValue(vObj)); + return NULL; +} + +static void convertNameValue(VObject *vObj, nsIAbCard *aCard) +{ + const char *cardPropName = NULL; + + // if the vCard property is not a root property then we need to determine its exact property. + // a good example of this is VCTelephoneProp, this prop has four objects underneath it: + // fax, work and home and cellular. + if (PL_strcasecmp(VCCityProp, vObjectName(vObj)) == 0) + cardPropName = kWorkCityProperty; + else if (PL_strcasecmp(VCTelephoneProp, vObjectName(vObj)) == 0) + { + if (isAPropertyOf(vObj, VCFaxProp)) + cardPropName = kFaxProperty; + else if (isAPropertyOf(vObj, VCWorkProp)) + cardPropName = kWorkPhoneProperty; + else if (isAPropertyOf(vObj, VCHomeProp)) + cardPropName = kHomePhoneProperty; + else if (isAPropertyOf(vObj, VCCellularProp)) + cardPropName = kCellularProperty; + else if (isAPropertyOf(vObj, VCPagerProp)) + cardPropName = kPagerProperty; + else + return; + } + else if (PL_strcasecmp(VCEmailAddressProp, vObjectName(vObj)) == 0) + cardPropName = kPriEmailProperty; + else if (PL_strcasecmp(VCFamilyNameProp, vObjectName(vObj)) == 0) + cardPropName = kLastNameProperty; + else if (PL_strcasecmp(VCFullNameProp, vObjectName(vObj)) == 0) + cardPropName = kDisplayNameProperty; + else if (PL_strcasecmp(VCGivenNameProp, vObjectName(vObj)) == 0) + cardPropName = kFirstNameProperty; + else if (PL_strcasecmp(VCOrgNameProp, vObjectName(vObj)) == 0) + cardPropName = kCompanyProperty; + else if (PL_strcasecmp(VCOrgUnitProp, vObjectName(vObj)) == 0) + cardPropName = kDepartmentProperty; + else if (PL_strcasecmp(VCPostalCodeProp, vObjectName(vObj)) == 0) + cardPropName = kWorkZipCodeProperty; + else if (PL_strcasecmp(VCRegionProp, vObjectName(vObj)) == 0) + cardPropName = kWorkStateProperty; + else if (PL_strcasecmp(VCStreetAddressProp, vObjectName(vObj)) == 0) + cardPropName = kWorkAddressProperty; + else if (PL_strcasecmp(VCPostalBoxProp, vObjectName(vObj)) == 0) + cardPropName = kWorkAddress2Property; + else if (PL_strcasecmp(VCCountryNameProp, vObjectName(vObj)) == 0) + cardPropName = kWorkCountryProperty; + else if (PL_strcasecmp(VCTitleProp, vObjectName(vObj)) == 0) + cardPropName = kJobTitleProperty; + else if (PL_strcasecmp(VCUseHTML, vObjectName(vObj)) == 0) + cardPropName = kPreferMailFormatProperty; + else if (PL_strcasecmp(VCNoteProp, vObjectName(vObj)) == 0) + cardPropName = kNotesProperty; + else if (PL_strcasecmp(VCURLProp, vObjectName(vObj)) == 0) + cardPropName = kWorkWebPageProperty; + else + return; + + if (!VALUE_TYPE(vObj)) + return; + + char *cardPropValue = getCString(vObj); + if (PL_strcmp(cardPropName, kPreferMailFormatProperty)) { + aCard->SetPropertyAsAUTF8String(cardPropName, nsDependentCString(cardPropValue)); + } else { + if (!PL_strcmp(cardPropValue, "TRUE")) + aCard->SetPropertyAsUint32(cardPropName, nsIAbPreferMailFormat::html); + else if (!PL_strcmp(cardPropValue, "FALSE")) + aCard->SetPropertyAsUint32(cardPropName, nsIAbPreferMailFormat::plaintext); + else + aCard->SetPropertyAsUint32(cardPropName, nsIAbPreferMailFormat::unknown); + } + PR_FREEIF(cardPropValue); + return; +} + +static void convertFromVObject(VObject *vObj, nsIAbCard *aCard) +{ + if (vObj) + { + VObjectIterator t; + + convertNameValue(vObj, aCard); + + initPropIterator(&t, vObj); + while (moreIteration(&t)) + { + VObject * nextObject = nextVObject(&t); + convertFromVObject(nextObject, aCard); + } + } + return; +} + +NS_IMETHODIMP nsAbManager::EscapedVCardToAbCard(const char *aEscapedVCardStr, nsIAbCard **aCard) +{ + NS_ENSURE_ARG_POINTER(aEscapedVCardStr); + NS_ENSURE_ARG_POINTER(aCard); + + nsCOMPtr <nsIAbCard> cardFromVCard = do_CreateInstance(NS_ABCARDPROPERTY_CONTRACTID); + if (!cardFromVCard) + return NS_ERROR_FAILURE; + + // aEscapedVCardStr will be "" the first time, before you have a vCard + if (*aEscapedVCardStr != '\0') { + nsCString unescapedData; + MsgUnescapeString(nsDependentCString(aEscapedVCardStr), 0, unescapedData); + + VObject *vObj = parse_MIME(unescapedData.get(), unescapedData.Length()); + if (vObj) + { + convertFromVObject(vObj, cardFromVCard); + + cleanVObject(vObj); + } + else + NS_WARNING("Parse of vCard failed"); + } + + NS_IF_ADDREF(*aCard = cardFromVCard); + return NS_OK; +} + +NS_IMETHODIMP +nsAbManager::Handle(nsICommandLine* aCmdLine) +{ + nsresult rv; + bool found; + + rv = aCmdLine->HandleFlag(NS_LITERAL_STRING("addressbook"), false, &found); + NS_ENSURE_SUCCESS(rv, rv); + + if (!found) + return NS_OK; + + nsCOMPtr<nsIWindowWatcher> wwatch (do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + NS_ENSURE_TRUE(wwatch, NS_ERROR_FAILURE); + + nsCOMPtr<mozIDOMWindowProxy> opened; + wwatch->OpenWindow(nullptr, + "chrome://messenger/content/addressbook/addressbook.xul", + "_blank", + "chrome,extrachrome,menubar,resizable,scrollbars,status,toolbar", + nullptr, getter_AddRefs(opened)); + aCmdLine->SetPreventDefault(true); + return NS_OK; +} + +NS_IMETHODIMP +nsAbManager::GetHelpInfo(nsACString& aResult) +{ + aResult.Assign(NS_LITERAL_CSTRING(" -addressbook Open the address book at startup.\n")); + return NS_OK; +} + +NS_IMETHODIMP +nsAbManager::GenerateUUID(const nsACString &aDirectoryId, + const nsACString &aLocalId, nsACString &uuid) +{ + uuid.Assign(aDirectoryId); + uuid.Append('#'); + uuid.Append(aLocalId); + return NS_OK; +} + +NS_IMETHODIMP +nsAbManager::ConvertQueryStringToExpression(const nsACString &aQueryString, + nsIAbBooleanExpression **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + return nsAbQueryStringToExpression::Convert(aQueryString, + _retval); +} diff --git a/mailnews/addrbook/src/nsAbManager.h b/mailnews/addrbook/src/nsAbManager.h new file mode 100644 index 000000000..066fa8a08 --- /dev/null +++ b/mailnews/addrbook/src/nsAbManager.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef __nsAbManager_h +#define __nsAbManager_h + +#include "nsIAbManager.h" +#include "nsTObserverArray.h" +#include "nsCOMPtr.h" +#include "nsICommandLineHandler.h" +#include "nsIObserver.h" +#include "nsInterfaceHashtable.h" +#include "nsIAbDirFactoryService.h" +#include "nsIAbDirectory.h" + +class nsIAbLDAPAttributeMap; + +class nsAbManager : public nsIAbManager, + public nsICommandLineHandler, + public nsIObserver +{ + +public: + nsAbManager(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIABMANAGER + NS_DECL_NSIOBSERVER + NS_DECL_NSICOMMANDLINEHANDLER + + nsresult Init(); + +private: + virtual ~nsAbManager(); + nsresult GetRootDirectory(nsIAbDirectory **aResult); + nsresult ExportDirectoryToDelimitedText(nsIAbDirectory *aDirectory, const char *aDelim, + uint32_t aDelimLen, nsIFile *aLocalFile, bool useUTF8); + nsresult ExportDirectoryToVCard(nsIAbDirectory *aDirectory, nsIFile *aLocalFile); + nsresult ExportDirectoryToLDIF(nsIAbDirectory *aDirectory, nsIFile *aLocalFile); + nsresult AppendLDIFForMailList(nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult); + nsresult AppendDNForCard(const char *aProperty, nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult); + nsresult AppendBasicLDIFForCard(nsIAbCard *aCard, nsIAbLDAPAttributeMap *aAttrMap, nsACString &aResult); + nsresult AppendProperty(const char *aProperty, const char16_t *aValue, nsACString &aResult); + bool IsSafeLDIFString(const char16_t *aStr); + + struct abListener { + nsCOMPtr<nsIAbListener> mListener; + uint32_t mNotifyFlags; + + abListener(nsIAbListener *aListener, uint32_t aNotifyFlags) + : mListener(aListener), mNotifyFlags(aNotifyFlags) {} + abListener(const abListener &aListener) + : mListener(aListener.mListener), mNotifyFlags(aListener.mNotifyFlags) {} + ~abListener() {} + + int operator==(nsIAbListener* aListener) const { + return mListener == aListener; + } + int operator==(const abListener &aListener) const { + return mListener == aListener.mListener; + } + }; + + nsTObserverArray<abListener> mListeners; + nsCOMPtr<nsIAbDirectory> mCacheTopLevelAb; + nsInterfaceHashtable<nsCStringHashKey, nsIAbDirectory> mAbStore; +}; + +#endif diff --git a/mailnews/addrbook/src/nsAbOSXCard.h b/mailnews/addrbook/src/nsAbOSXCard.h new file mode 100644 index 000000000..51f41c1d7 --- /dev/null +++ b/mailnews/addrbook/src/nsAbOSXCard.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsAbOSXCard_h___ +#define nsAbOSXCard_h___ + +#include "mozilla/Attributes.h" +#include "nsAbCardProperty.h" + +#define NS_ABOSXCARD_URI_PREFIX NS_ABOSXCARD_PREFIX "://" + +#define NS_IABOSXCARD_IID \ + { 0xa7e5b697, 0x772d, 0x4fb5, \ + { 0x81, 0x16, 0x23, 0xb7, 0x5a, 0xac, 0x94, 0x56 } } + +class nsIAbOSXCard : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IABOSXCARD_IID) + + virtual nsresult Init(const char *aUri) = 0; + virtual nsresult Update(bool aNotify) = 0; + virtual nsresult GetURI(nsACString &aURI) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIAbOSXCard, NS_IABOSXCARD_IID) + +class nsAbOSXCard : public nsAbCardProperty, + public nsIAbOSXCard +{ +public: + NS_DECL_ISUPPORTS_INHERITED + + nsresult Update(bool aNotify) override; + nsresult GetURI(nsACString &aURI) override; + nsresult Init(const char *aUri) override; + // this is needed so nsAbOSXUtils.mm can get at nsAbCardProperty + friend class nsAbOSXUtils; +private: + nsCString mURI; + + virtual ~nsAbOSXCard() {} +}; + +#endif // nsAbOSXCard_h___ diff --git a/mailnews/addrbook/src/nsAbOSXCard.mm b/mailnews/addrbook/src/nsAbOSXCard.mm new file mode 100644 index 000000000..28f978c24 --- /dev/null +++ b/mailnews/addrbook/src/nsAbOSXCard.mm @@ -0,0 +1,401 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbOSXCard.h" +#include "nsAbOSXDirectory.h" +#include "nsAbOSXUtils.h" +#include "nsAutoPtr.h" +#include "nsIAbManager.h" +#include "nsObjCExceptions.h" +#include "nsServiceManagerUtils.h" + +#include <AddressBook/AddressBook.h> + +NS_IMPL_ISUPPORTS_INHERITED(nsAbOSXCard, + nsAbCardProperty, + nsIAbOSXCard) + +#ifdef DEBUG +static ABPropertyType +GetPropertType(ABRecord *aCard, NSString *aProperty) +{ + ABPropertyType propertyType = kABErrorInProperty; + if ([aCard isKindOfClass:[ABPerson class]]) + propertyType = [ABPerson typeOfProperty:aProperty]; + else if ([aCard isKindOfClass:[ABGroup class]]) + propertyType = [ABGroup typeOfProperty:aProperty]; + return propertyType; +} +#endif + +static void +SetStringProperty(nsAbOSXCard *aCard, const nsString &aValue, + const char *aMemberName, bool aNotify, + nsIAbManager *aAbManager) +{ + nsString oldValue; + nsresult rv = aCard->GetPropertyAsAString(aMemberName, oldValue); + if (NS_FAILED(rv)) + oldValue.Truncate(); + + if (!aNotify) { + aCard->SetPropertyAsAString(aMemberName, aValue); + } + else if (!oldValue.Equals(aValue)) { + aCard->SetPropertyAsAString(aMemberName, aValue); + + nsISupports *supports = NS_ISUPPORTS_CAST(nsAbCardProperty*, aCard); + + aAbManager->NotifyItemPropertyChanged(supports, aMemberName, + oldValue.get(), aValue.get()); + } +} + +static void +SetStringProperty(nsAbOSXCard *aCard, NSString *aValue, const char *aMemberName, + bool aNotify, nsIAbManager *aAbManager) +{ + nsAutoString value; + if (aValue) + AppendToString(aValue, value); + + SetStringProperty(aCard, value, aMemberName, aNotify, aAbManager); +} + +static void +MapStringProperty(nsAbOSXCard *aCard, ABRecord *aOSXCard, NSString *aProperty, + const char *aMemberName, bool aNotify, + nsIAbManager *aAbManager) +{ + NS_ASSERTION(aProperty, "This is bad! You asked for an unresolved symbol."); + NS_ASSERTION(GetPropertType(aOSXCard, aProperty) == kABStringProperty, + "Wrong type!"); + + SetStringProperty(aCard, [aOSXCard valueForProperty:aProperty], aMemberName, + aNotify, aAbManager); +} + +static ABMutableMultiValue* +GetMultiValue(ABRecord *aCard, NSString *aProperty) +{ + NS_ASSERTION(aProperty, "This is bad! You asked for an unresolved symbol."); + NS_ASSERTION(GetPropertType(aCard, aProperty) & kABMultiValueMask, + "Wrong type!"); + + return [aCard valueForProperty:aProperty]; +} + +static void +MapDate(nsAbOSXCard *aCard, NSDate *aDate, const char *aYearPropName, + const char *aMonthPropName, const char *aDayPropName, bool aNotify, + nsIAbManager *aAbManager) +{ + // XXX Should we pass a format and timezone? + NSCalendarDate *date = [aDate dateWithCalendarFormat:nil timeZone:nil]; + + nsAutoString value; + value.AppendInt(static_cast<int32_t>([date yearOfCommonEra])); + SetStringProperty(aCard, value, aYearPropName, aNotify, aAbManager); + value.Truncate(); + value.AppendInt(static_cast<int32_t>([date monthOfYear])); + SetStringProperty(aCard, value, aMonthPropName, aNotify, aAbManager); + value.Truncate(); + value.AppendInt(static_cast<int32_t>([date dayOfMonth])); + SetStringProperty(aCard, value, aDayPropName, aNotify, aAbManager); +} + +static bool +MapMultiValue(nsAbOSXCard *aCard, ABRecord *aOSXCard, + const nsAbOSXPropertyMap &aMap, bool aNotify, + nsIAbManager *aAbManager) +{ + ABMultiValue *value = GetMultiValue(aOSXCard, aMap.mOSXProperty); + if (value) { + unsigned int j; + unsigned int count = [value count]; + for (j = 0; j < count; ++j) { + if ([[value labelAtIndex:j] isEqualToString:aMap.mOSXLabel]) { + NSString *stringValue = (aMap.mOSXKey) + ? [[value valueAtIndex:j] objectForKey:aMap.mOSXKey] + : [value valueAtIndex:j]; + + SetStringProperty(aCard, stringValue, aMap.mPropertyName, aNotify, + aAbManager); + + return true; + } + } + } + // String wasn't found, set value of card to empty if it was set previously + SetStringProperty(aCard, EmptyString(), aMap.mPropertyName, aNotify, + aAbManager); + + return false; +} + +// Maps Address Book's instant messenger name to the corresponding nsIAbCard field name. +static const char* +InstantMessengerFieldName(NSString* aInstantMessengerName) +{ + if ([aInstantMessengerName isEqualToString:@"AIMInstant"]) { + return "_AimScreenName"; + } + if ([aInstantMessengerName isEqualToString:@"GoogleTalkInstant"]) { + return "_GoogleTalk"; + } + if ([aInstantMessengerName isEqualToString:@"ICQInstant"]) { + return "_ICQ"; + } + if ([aInstantMessengerName isEqualToString:@"JabberInstant"]) { + return "_JabberId"; + } + if ([aInstantMessengerName isEqualToString:@"MSNInstant"]) { + return "_MSN"; + } + if ([aInstantMessengerName isEqualToString:@"QQInstant"]) { + return "_QQ"; + } + if ([aInstantMessengerName isEqualToString:@"SkypeInstant"]) { + return "_Skype"; + } + if ([aInstantMessengerName isEqualToString:@"YahooInstant"]) { + return "_Yahoo"; + } + + // Fall back to AIM for everything else. + // We don't have nsIAbCard fields for FacebookInstant and GaduGaduInstant. + return "_AimScreenName"; +} + +nsresult +nsAbOSXCard::Init(const char *aUri) +{ + if (strncmp(aUri, NS_ABOSXCARD_URI_PREFIX, + sizeof(NS_ABOSXCARD_URI_PREFIX) - 1) != 0) + return NS_ERROR_FAILURE; + + mURI = aUri; + + SetLocalId(nsDependentCString(aUri)); + + return Update(false); +} + +nsresult +nsAbOSXCard::GetURI(nsACString &aURI) +{ + if (mURI.IsEmpty()) + return NS_ERROR_NOT_INITIALIZED; + + aURI = mURI; + return NS_OK; +} + +nsresult +nsAbOSXCard::Update(bool aNotify) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + ABAddressBook *addressBook = [ABAddressBook sharedAddressBook]; + + const char *uid = &((mURI.get())[16]); + ABRecord *card = [addressBook recordForUniqueId:[NSString stringWithUTF8String:uid]]; + NS_ENSURE_TRUE(card, NS_ERROR_FAILURE); + + nsCOMPtr<nsIAbManager> abManager; + nsresult rv; + if (aNotify) { + abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + if ([card isKindOfClass:[ABGroup class]]) { + m_IsMailList = true; + m_MailListURI.AssignLiteral(NS_ABOSXDIRECTORY_URI_PREFIX); + m_MailListURI.Append(uid); + MapStringProperty(this, card, kABGroupNameProperty, "DisplayName", aNotify, + abManager); + MapStringProperty(this, card, kABGroupNameProperty, "LastName", aNotify, + abManager); + + return NS_OK; + } + + bool foundHome = false, foundWork = false; + + uint32_t i; + for (i = 0; i < nsAbOSXUtils::kPropertyMapSize; ++i) { + const nsAbOSXPropertyMap &propertyMap = nsAbOSXUtils::kPropertyMap[i]; + if (!propertyMap.mOSXProperty) + continue; + + if (propertyMap.mOSXLabel) { + if (MapMultiValue(this, card, propertyMap, aNotify, + abManager) && propertyMap.mOSXProperty == kABAddressProperty) { + if (propertyMap.mOSXLabel == kABAddressHomeLabel) + foundHome = true; + else + foundWork = true; + } + } + else { + MapStringProperty(this, card, propertyMap.mOSXProperty, + propertyMap.mPropertyName, aNotify, abManager); + } + } + + int flags = 0; + if (kABPersonFlags) + flags = [[card valueForProperty:kABPersonFlags] intValue]; + +#define SET_STRING(_value, _name, _notify, _session) \ + SetStringProperty(this, _value, #_name, _notify, _session) + + // If kABShowAsCompany is set we use the company name as display name. + if (kABPersonFlags && (flags & kABShowAsCompany)) { + nsString company; + nsresult rv = GetPropertyAsAString(kCompanyProperty, company); + if (NS_FAILED(rv)) + company.Truncate(); + SET_STRING(company, DisplayName, aNotify, abManager); + } + else { + // Use the order used in the OS X address book to set DisplayName. + int order = kABPersonFlags && (flags & kABNameOrderingMask); + if (kABPersonFlags && (order == kABDefaultNameOrdering)) { + order = [addressBook defaultNameOrdering]; + } + + nsAutoString displayName, tempName; + if (kABPersonFlags && (order == kABFirstNameFirst)) { + GetFirstName(tempName); + displayName.Append(tempName); + + GetLastName(tempName); + + // Only append a space if the last name and the first name are not empty + if (!tempName.IsEmpty() && !displayName.IsEmpty()) + displayName.Append(' '); + + displayName.Append(tempName); + } + else { + GetLastName(tempName); + displayName.Append(tempName); + + GetFirstName(tempName); + + // Only append a space if the last name and the first name are not empty + if (!tempName.IsEmpty() && !displayName.IsEmpty()) + displayName.Append(' '); + + displayName.Append(tempName); + } + SET_STRING(displayName, DisplayName, aNotify, abManager); + } + + ABMultiValue *value = GetMultiValue(card, kABEmailProperty); + if (value) { + unsigned int count = [value count]; + if (count > 0) { + unsigned int j = [value indexForIdentifier:[value primaryIdentifier]]; + + if (j < count) + SET_STRING([value valueAtIndex:j], PrimaryEmail, aNotify, + abManager); + + // If j is 0 (first in the list) we want the second in the list + // (index 1), if j is anything else we want the first in the list + // (index 0). + j = (j == 0); + if (j < count) + SET_STRING([value valueAtIndex:j], SecondEmail, aNotify, + abManager); + } + } + + // We map the first home address we can find and the first work address + // we can find. If we find none, we map the primary address to the home + // address. + if (!foundHome && !foundWork) { + value = GetMultiValue(card, kABAddressProperty); + if (value) { + unsigned int count = [value count]; + unsigned int j = [value indexForIdentifier:[value primaryIdentifier]]; + + if (j < count) { + NSDictionary *address = [value valueAtIndex:j]; + if (address) { + SET_STRING([address objectForKey:kABAddressStreetKey], + HomeAddress, aNotify, abManager); + SET_STRING([address objectForKey:kABAddressCityKey], + HomeCity, aNotify, abManager); + SET_STRING([address objectForKey:kABAddressStateKey], + HomeState, aNotify, abManager); + SET_STRING([address objectForKey:kABAddressZIPKey], + HomeZipCode, aNotify, abManager); + SET_STRING([address objectForKey:kABAddressCountryKey], + HomeCountry, aNotify, abManager); + } + } + } + } + // This was kABAIMInstantProperty previously, but it was deprecated in OS X 10.7. + value = GetMultiValue(card, kABInstantMessageProperty); + if (value) { + unsigned int count = [value count]; + for (size_t i = 0; i < count; i++) { + id imValue = [value valueAtIndex:i]; + // Depending on the macOS version, imValue can be an NSString or an NSDictionary. + if ([imValue isKindOfClass:[NSString class]]) { + if (i == [value indexForIdentifier:[value primaryIdentifier]]) { + SET_STRING(imValue, _AimScreenName, aNotify, abManager); + } + } else if ([imValue isKindOfClass:[NSDictionary class]]) { + NSString* instantMessageService = [imValue objectForKey:@"InstantMessageService"]; + const char* fieldName = InstantMessengerFieldName(instantMessageService); + NSString* userName = [imValue objectForKey:@"InstantMessageUsername"]; + SetStringProperty(this, userName, fieldName, aNotify, abManager); + } + } + } + +#define MAP_DATE(_date, _name, _notify, _session) \ + MapDate(this, _date, #_name"Year", #_name"Month", #_name"Day", _notify, \ + _session) + + NSDate *date = [card valueForProperty:kABBirthdayProperty]; + if (date) + MAP_DATE(date, Birth, aNotify, abManager); + + if (kABOtherDatesProperty) { + value = GetMultiValue(card, kABOtherDatesProperty); + if (value) { + unsigned int j, count = [value count]; + for (j = 0; j < count; ++j) { + if ([[value labelAtIndex:j] isEqualToString:kABAnniversaryLabel]) { + date = [value valueAtIndex:j]; + if (date) { + MAP_DATE(date, Anniversary, aNotify, abManager); + + break; + } + } + } + } + } +#undef MAP_DATE +#undef SET_STRING + + date = [card valueForProperty:kABModificationDateProperty]; + if (date) + SetPropertyAsUint32("LastModifiedDate", + uint32_t([date timeIntervalSince1970])); + // XXX No way to notify about this? + + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} diff --git a/mailnews/addrbook/src/nsAbOSXDirFactory.cpp b/mailnews/addrbook/src/nsAbOSXDirFactory.cpp new file mode 100644 index 000000000..e9f2d7d3b --- /dev/null +++ b/mailnews/addrbook/src/nsAbOSXDirFactory.cpp @@ -0,0 +1,50 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbOSXDirFactory.h" +#include "nsAbBaseCID.h" +#include "nsEnumeratorUtils.h" +#include "nsIAbDirectory.h" +#include "nsIAbManager.h" +#include "nsStringGlue.h" +#include "nsServiceManagerUtils.h" +#include "nsAbOSXDirectory.h" + +NS_IMPL_ISUPPORTS(nsAbOSXDirFactory, nsIAbDirFactory) + +NS_IMETHODIMP +nsAbOSXDirFactory::GetDirectories(const nsAString &aDirName, + const nsACString &aURI, + const nsACString &aPrefName, + nsISimpleEnumerator **aDirectories) +{ + NS_ENSURE_ARG_POINTER(aDirectories); + + *aDirectories = nullptr; + + nsresult rv; + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectory(NS_LITERAL_CSTRING(NS_ABOSXDIRECTORY_URI_PREFIX "/"), + getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbOSXDirectory> osxDirectory(do_QueryInterface(directory, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = osxDirectory->AssertChildNodes(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_NewSingletonEnumerator(aDirectories, osxDirectory); +} + +// No actual deletion, since you cannot create the address books from Mozilla. +NS_IMETHODIMP +nsAbOSXDirFactory::DeleteDirectory(nsIAbDirectory *aDirectory) +{ + return NS_OK; +} diff --git a/mailnews/addrbook/src/nsAbOSXDirFactory.h b/mailnews/addrbook/src/nsAbOSXDirFactory.h new file mode 100644 index 000000000..4c86211d3 --- /dev/null +++ b/mailnews/addrbook/src/nsAbOSXDirFactory.h @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsAbOSXDirFactory_h___ +#define nsAbOSXDirFactory_h___ + +#include "nsIAbDirFactory.h" + +class nsAbOSXDirFactory final : public nsIAbDirFactory +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIABDIRFACTORY + +private: + ~nsAbOSXDirFactory() {} +}; + +#endif // nsAbOSXDirFactory_h___ diff --git a/mailnews/addrbook/src/nsAbOSXDirectory.h b/mailnews/addrbook/src/nsAbOSXDirectory.h new file mode 100644 index 000000000..7e3fad96c --- /dev/null +++ b/mailnews/addrbook/src/nsAbOSXDirectory.h @@ -0,0 +1,126 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsAbOSXDirectory_h___ +#define nsAbOSXDirectory_h___ + +#include "mozilla/Attributes.h" +#include "nsISupports.h" +#include "nsAbBaseCID.h" +#include "nsAbDirProperty.h" +#include "nsIAbDirectoryQuery.h" +#include "nsIAbDirectorySearch.h" +#include "nsIAbDirSearchListener.h" +#include "nsIMutableArray.h" +#include "nsInterfaceHashtable.h" +#include "nsAbOSXCard.h" + +#include <CoreFoundation/CoreFoundation.h> +class nsIAbManager; +class nsIAbBooleanExpression; + +#define NS_ABOSXDIRECTORY_URI_PREFIX NS_ABOSXDIRECTORY_PREFIX "://" + +#define NS_IABOSXDIRECTORY_IID \ +{ 0x87ee4bd9, 0x8552, 0x498f, \ + { 0x80, 0x85, 0x34, 0xf0, 0x2a, 0xbb, 0x56, 0x16 } } + +class nsIAbOSXDirectory : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IABOSXDIRECTORY_IID) + + virtual nsresult AssertChildNodes() = 0; + virtual nsresult Update() = 0; + virtual nsresult AssertDirectory(nsIAbManager *aManager, + nsIAbDirectory *aDirectory) = 0; + virtual nsresult AssertCard(nsIAbManager *aManager, + nsIAbCard *aCard) = 0; + virtual nsresult UnassertCard(nsIAbManager *aManager, + nsIAbCard *aCard, + nsIMutableArray *aCardList) = 0; + virtual nsresult UnassertDirectory(nsIAbManager *aManager, + nsIAbDirectory *aDirectory) = 0; + virtual nsresult DeleteUid(const nsACString &aUid) = 0; + virtual nsresult GetURI(nsACString &aURI) = 0; + virtual nsresult Init(const char *aUri) = 0; + virtual nsresult GetCardByUri(const nsACString &aUri, nsIAbOSXCard **aResult) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIAbOSXDirectory, NS_IABOSXDIRECTORY_IID) + +class nsAbOSXDirectory final : public nsAbDirProperty, +public nsIAbDirSearchListener, +public nsIAbOSXDirectory +{ +public: + nsAbOSXDirectory(); + + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIABDIRSEARCHLISTENER + + // nsIAbOSXDirectory method + NS_IMETHOD Init(const char *aUri) override; + + // nsAbDirProperty methods + NS_IMETHOD GetReadOnly(bool *aReadOnly) override; + NS_IMETHOD GetChildCards(nsISimpleEnumerator **aCards) override; + NS_IMETHOD GetChildNodes(nsISimpleEnumerator **aNodes) override; + NS_IMETHOD GetIsQuery(bool *aResult) override; + NS_IMETHOD HasCard(nsIAbCard *aCard, bool *aHasCard) override; + NS_IMETHOD HasDirectory(nsIAbDirectory *aDirectory, bool *aHasDirectory) override; + NS_IMETHOD GetURI(nsACString &aURI) override; + NS_IMETHOD GetCardFromProperty(const char *aProperty, + const nsACString &aValue, + bool caseSensitive, + nsIAbCard **aResult) override; + NS_IMETHOD GetCardsFromProperty(const char *aProperty, + const nsACString &aValue, + bool aCaseSensitive, + nsISimpleEnumerator **aResult) override; + NS_IMETHOD CardForEmailAddress(const nsACString &aEmailAddress, + nsIAbCard **aResult) override; + + // nsIAbOSXDirectory + nsresult AssertChildNodes() override; + nsresult AssertDirectory(nsIAbManager *aManager, + nsIAbDirectory *aDirectory) override; + nsresult AssertCard(nsIAbManager *aManager, + nsIAbCard *aCard) override; + nsresult UnassertCard(nsIAbManager *aManager, + nsIAbCard *aCard, + nsIMutableArray *aCardList) override; + nsresult UnassertDirectory(nsIAbManager *aManager, + nsIAbDirectory *aDirectory) override; + + nsresult Update() override; + + nsresult DeleteUid(const nsACString &aUid) override; + + nsresult GetCardByUri(const nsACString &aUri, nsIAbOSXCard **aResult) override; + + nsresult GetRootOSXDirectory(nsIAbOSXDirectory **aResult); + +private: + ~nsAbOSXDirectory(); + nsresult FallbackSearch(nsIAbBooleanExpression *aExpression, + nsISimpleEnumerator **aCards); + + // This is a list of nsIAbCards, kept separate from m_AddressList because: + // - nsIAbDirectory items that are mailing lists, must keep a list of + // nsIAbCards in m_AddressList, however + // - nsIAbDirectory items that are address books, must keep a list of + // nsIAbDirectory (i.e. mailing lists) in m_AddressList, AND no nsIAbCards. + // + // This wasn't too bad for mork, as that just gets a list from its database, + // but because we store our own copy of the list, we must store a separate + // list of nsIAbCards here. nsIMutableArray is used, because then it is + // interchangeable with m_AddressList. + nsCOMPtr<nsIMutableArray> mCardList; + nsInterfaceHashtable<nsCStringHashKey, nsIAbOSXCard> mCardStore; + nsCOMPtr<nsIAbOSXDirectory> mCacheTopLevelOSXAb; +}; + +#endif // nsAbOSXDirectory_h___ diff --git a/mailnews/addrbook/src/nsAbOSXDirectory.mm b/mailnews/addrbook/src/nsAbOSXDirectory.mm new file mode 100644 index 000000000..e195fe3a7 --- /dev/null +++ b/mailnews/addrbook/src/nsAbOSXDirectory.mm @@ -0,0 +1,1374 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbOSXDirectory.h" +#include "nsAbOSXCard.h" +#include "nsAbOSXUtils.h" +#include "nsAbQueryStringToExpression.h" +#include "nsArrayEnumerator.h" +#include "nsAutoPtr.h" +#include "nsCOMArray.h" +#include "nsEnumeratorUtils.h" +#include "nsIAbDirectoryQueryProxy.h" +#include "nsIAbManager.h" +#include "nsObjCExceptions.h" +#include "nsServiceManagerUtils.h" +#include "nsIMutableArray.h" +#include "nsArrayUtils.h" +#include "nsIAbBooleanExpression.h" +#include "nsComponentManagerUtils.h" +#include "nsISimpleEnumerator.h" + +#include <AddressBook/AddressBook.h> + +#define kABDeletedRecords (kABDeletedRecords? kABDeletedRecords : @"ABDeletedRecords") +#define kABUpdatedRecords (kABUpdatedRecords ? kABUpdatedRecords : @"ABUpdatedRecords") +#define kABInsertedRecords (kABInsertedRecords ? kABInsertedRecords : @"ABInsertedRecords") + +static nsresult +GetOrCreateGroup(NSString *aUid, nsIAbDirectory **aResult) +{ + NS_ASSERTION(aUid, "No UID for group!."); + + nsAutoCString uri(NS_ABOSXDIRECTORY_URI_PREFIX); + AppendToCString(aUid, uri); + + nsresult rv; + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectory(uri, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*aResult = directory); + return NS_OK; +} + +static nsresult +GetCard(ABRecord *aRecord, nsIAbCard **aResult, nsIAbOSXDirectory *osxDirectory) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + NSString *uid = [aRecord uniqueId]; + NS_ASSERTION(uid, "No UID for card!."); + if (!uid) + return NS_ERROR_FAILURE; + + nsAutoCString uri(NS_ABOSXCARD_URI_PREFIX); + AppendToCString(uid, uri); + nsCOMPtr<nsIAbOSXCard> osxCard; + nsresult rv = osxDirectory->GetCardByUri(uri, getter_AddRefs(osxCard)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbCard> card = do_QueryInterface(osxCard, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*aResult = card); + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +static nsresult +CreateCard(ABRecord *aRecord, nsIAbCard **aResult) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + NSString *uid = [aRecord uniqueId]; + NS_ASSERTION(uid, "No UID for card!."); + if (!uid) + return NS_ERROR_FAILURE; + + nsresult rv; + nsCOMPtr<nsIAbOSXCard> osxCard = do_CreateInstance(NS_ABOSXCARD_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString uri(NS_ABOSXCARD_URI_PREFIX); + AppendToCString(uid, uri); + + rv = osxCard->Init(uri.get()); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbCard> card = do_QueryInterface(osxCard, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*aResult = card); + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +static nsresult +Sync(NSString *aUid) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + ABAddressBook *addressBook = [ABAddressBook sharedAddressBook]; + ABRecord *card = [addressBook recordForUniqueId:aUid]; + if ([card isKindOfClass:[ABGroup class]]) + { + nsCOMPtr<nsIAbDirectory> directory; + GetOrCreateGroup(aUid, getter_AddRefs(directory)); + nsCOMPtr<nsIAbOSXDirectory> osxDirectory = + do_QueryInterface(directory); + + if (osxDirectory) { + osxDirectory->Update(); + } + } + else { + nsCOMPtr<nsIAbCard> abCard; + nsresult rv; + + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectory(NS_LITERAL_CSTRING(NS_ABOSXDIRECTORY_URI_PREFIX"/"), + getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbOSXDirectory> osxDirectory = + do_QueryInterface(directory, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetCard(card, getter_AddRefs(abCard), osxDirectory); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbOSXCard> osxCard = do_QueryInterface(abCard); + osxCard->Update(true); + } + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +@interface ABChangedMonitor : NSObject +-(void)ABChanged:(NSNotification *)aNotification; +@end + +@implementation ABChangedMonitor +-(void)ABChanged:(NSNotification *)aNotification +{ + NSDictionary *changes = [aNotification userInfo]; + + nsresult rv; + NSArray *inserted = [changes objectForKey:kABInsertedRecords]; + + if (inserted) { + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectory(NS_LITERAL_CSTRING(NS_ABOSXDIRECTORY_URI_PREFIX"/"), + getter_AddRefs(directory)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIAbOSXDirectory> osxDirectory = + do_QueryInterface(directory, &rv); + NS_ENSURE_SUCCESS_VOID(rv); + + unsigned int i, count = [inserted count]; + for (i = 0; i < count; ++i) { + ABAddressBook *addressBook = + [ABAddressBook sharedAddressBook]; + ABRecord *card = + [addressBook recordForUniqueId:[inserted objectAtIndex:i]]; + if ([card isKindOfClass:[ABGroup class]]) { + nsCOMPtr<nsIAbDirectory> directory; + GetOrCreateGroup([inserted objectAtIndex:i], + getter_AddRefs(directory)); + + rv = osxDirectory->AssertDirectory(abManager, directory); + NS_ENSURE_SUCCESS_VOID(rv); + } + else { + nsCOMPtr<nsIAbCard> abCard; + // Construct a card + nsresult rv = CreateCard(card, getter_AddRefs(abCard)); + NS_ENSURE_SUCCESS_VOID(rv); + rv = osxDirectory->AssertCard(abManager, abCard); + NS_ENSURE_SUCCESS_VOID(rv); + } + } + } + + NSArray *updated = [changes objectForKey:kABUpdatedRecords]; + if (updated) { + unsigned int i, count = [updated count]; + for (i = 0; i < count; ++i) { + NSString *uid = [updated objectAtIndex:i]; + Sync(uid); + } + } + + NSArray *deleted = [changes objectForKey:kABDeletedRecords]; + if (deleted) { + + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectory(NS_LITERAL_CSTRING(NS_ABOSXDIRECTORY_URI_PREFIX"/"), + getter_AddRefs(directory)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIAbOSXDirectory> osxDirectory = + do_QueryInterface(directory, &rv); + NS_ENSURE_SUCCESS_VOID(rv); + + unsigned int i, count = [deleted count]; + for (i = 0; i < count; ++i) { + NSString *deletedUid = [deleted objectAtIndex:i]; + + nsAutoCString uid; + AppendToCString(deletedUid, uid); + + rv = osxDirectory->DeleteUid(uid); + NS_ENSURE_SUCCESS_VOID(rv); + } + } + + if (!inserted && !updated && !deleted) { + // XXX This is supposed to mean "everything was updated", but we get + // this whenever something has changed, so not sure what to do. + } +} +@end + +static nsresult +MapConditionString(nsIAbBooleanConditionString *aCondition, bool aNegate, + bool &aCanHandle, ABSearchElement **aResult) +{ + aCanHandle = false; + + nsAbBooleanConditionType conditionType = 0; + nsresult rv = aCondition->GetCondition(&conditionType); + NS_ENSURE_SUCCESS(rv, rv); + + ABSearchComparison comparison; + switch (conditionType) { + case nsIAbBooleanConditionTypes::Contains: + { + if (!aNegate) { + comparison = kABContainsSubString; + aCanHandle = true; + } + break; + } + case nsIAbBooleanConditionTypes::DoesNotContain: + { + if (aNegate) { + comparison = kABContainsSubString; + aCanHandle = true; + } + break; + } + case nsIAbBooleanConditionTypes::Is: + { + comparison = aNegate ? kABNotEqual : kABEqual; + aCanHandle = true; + break; + } + case nsIAbBooleanConditionTypes::IsNot: + { + comparison = aNegate ? kABEqual : kABNotEqual; + aCanHandle = true; + break; + } + case nsIAbBooleanConditionTypes::BeginsWith: + { + if (!aNegate) { + comparison = kABPrefixMatch; + aCanHandle = true; + } + break; + } + case nsIAbBooleanConditionTypes::EndsWith: + { + //comparison = kABSuffixMatch; + break; + } + case nsIAbBooleanConditionTypes::LessThan: + { + comparison = aNegate ? kABGreaterThanOrEqual : kABLessThan; + aCanHandle = true; + break; + } + case nsIAbBooleanConditionTypes::GreaterThan: + { + comparison = aNegate ? kABLessThanOrEqual : kABGreaterThan; + aCanHandle = true; + break; + } + } + + if (!aCanHandle) + return NS_OK; + + nsCString name; + rv = aCondition->GetName(getter_Copies(name)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString value; + rv = aCondition->GetValue(getter_Copies(value)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t length = value.Length(); + + uint32_t i; + for (i = 0; i < nsAbOSXUtils::kPropertyMapSize; ++i) { + if (name.Equals(nsAbOSXUtils::kPropertyMap[i].mPropertyName)) { + *aResult = + [ABPerson searchElementForProperty:nsAbOSXUtils::kPropertyMap[i].mOSXProperty + label:nsAbOSXUtils::kPropertyMap[i].mOSXLabel + key:nsAbOSXUtils::kPropertyMap[i].mOSXKey + value:[NSString stringWithCharacters:reinterpret_cast<const unichar*>(value.get()) length:length] + comparison:comparison]; + + return NS_OK; + } + } + + if (name.EqualsLiteral("DisplayName") && comparison == kABContainsSubString) { + ABSearchElement *first = + [ABPerson searchElementForProperty:kABFirstNameProperty + label:nil + key:nil + value:[NSString stringWithCharacters:reinterpret_cast<const unichar*>(value.get()) length:length] + comparison:comparison]; + ABSearchElement *second = + [ABPerson searchElementForProperty:kABLastNameProperty + label:nil + key:nil + value:[NSString stringWithCharacters:reinterpret_cast<const unichar*>(value.get()) length:length] + comparison:comparison]; + ABSearchElement *third = + [ABGroup searchElementForProperty:kABGroupNameProperty + label:nil + key:nil + value:[NSString stringWithCharacters:reinterpret_cast<const unichar*>(value.get()) length:length] + comparison:comparison]; + + *aResult = [ABSearchElement searchElementForConjunction:kABSearchOr children:[NSArray arrayWithObjects:first, second, third, nil]]; + + return NS_OK; + } + + aCanHandle = false; + + return NS_OK; +} + +static nsresult +BuildSearchElements(nsIAbBooleanExpression *aExpression, + bool &aCanHandle, + ABSearchElement **aResult) +{ + aCanHandle = true; + + nsCOMPtr<nsIArray> expressions; + nsresult rv = aExpression->GetExpressions(getter_AddRefs(expressions)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAbBooleanOperationType operation; + rv = aExpression->GetOperation(&operation); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t count; + rv = expressions->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(count > 1 && operation != nsIAbBooleanOperationTypes::NOT, + "This doesn't make sense!"); + + NSMutableArray *array = nullptr; + if (count > 1) + array = [[NSMutableArray alloc] init]; + + uint32_t i; + nsCOMPtr<nsIAbBooleanConditionString> condition; + nsCOMPtr<nsIAbBooleanExpression> subExpression; + for (i = 0; i < count; ++i) { + ABSearchElement *element = nullptr; + + condition = do_QueryElementAt(expressions, i); + if (condition) { + rv = MapConditionString(condition, operation == nsIAbBooleanOperationTypes::NOT, aCanHandle, &element); + if (NS_FAILED(rv)) + break; + } + else { + subExpression = do_QueryElementAt(expressions, i); + if (subExpression) { + rv = BuildSearchElements(subExpression, aCanHandle, &element); + if (NS_FAILED(rv)) + break; + } + } + + if (!aCanHandle) { + // remember to free the array when returning early + [array release]; + return NS_OK; + } + + if (element) { + if (array) + [array addObject:element]; + else + *aResult = element; + } + } + + if (array) { + if (NS_SUCCEEDED(rv)) { + ABSearchConjunction conjunction = operation == nsIAbBooleanOperationTypes::AND ? kABSearchAnd : kABSearchOr; + *aResult = [ABSearchElement searchElementForConjunction:conjunction children:array]; + } + [array release]; + } + + return rv; +} + +static bool +Search(nsIAbBooleanExpression *aExpression, NSArray **aResult) +{ + bool canHandle = false; + ABSearchElement *searchElement; + nsresult rv = BuildSearchElements(aExpression, canHandle, &searchElement); + NS_ENSURE_SUCCESS(rv, false); + + if (canHandle) + *aResult = [[ABAddressBook sharedAddressBook] recordsMatchingSearchElement:searchElement]; + + return canHandle; +} + +static uint32_t sObserverCount = 0; +static ABChangedMonitor *sObserver = nullptr; + +nsAbOSXDirectory::nsAbOSXDirectory() +{ +} + +nsAbOSXDirectory::~nsAbOSXDirectory() +{ + if (--sObserverCount == 0) { + [[NSNotificationCenter defaultCenter] removeObserver:sObserver]; + [sObserver release]; + } +} + +NS_IMPL_ISUPPORTS_INHERITED(nsAbOSXDirectory, + nsAbDirProperty, + nsIAbOSXDirectory, + nsIAbDirSearchListener) + +NS_IMETHODIMP +nsAbOSXDirectory::Init(const char *aUri) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + nsresult rv; + rv = nsAbDirProperty::Init(aUri); + NS_ENSURE_SUCCESS(rv, rv); + + ABAddressBook *addressBook = [ABAddressBook sharedAddressBook]; + if (sObserverCount == 0) { + sObserver = [[ABChangedMonitor alloc] init]; + [[NSNotificationCenter defaultCenter] addObserver:(ABChangedMonitor*)sObserver + selector:@selector(ABChanged:) + name:kABDatabaseChangedExternallyNotification + object:nil]; + } + ++sObserverCount; + + NSArray *cards; + nsCOMPtr<nsIMutableArray> cardList; + bool isRootOSXDirectory = false; + + if (!mIsQueryURI && mURINoQuery.Length() <= sizeof(NS_ABOSXDIRECTORY_URI_PREFIX)) + isRootOSXDirectory = true; + + if (mIsQueryURI || isRootOSXDirectory) + { + m_DirPrefId.AssignLiteral("ldap_2.servers.osx"); + + cards = [[addressBook people] arrayByAddingObjectsFromArray:[addressBook groups]]; + if (!mCardList) + mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + else + rv = mCardList->Clear(); + NS_ENSURE_SUCCESS(rv, rv); + + cardList = mCardList; + } + else + { + nsAutoCString uid(Substring(mURINoQuery, sizeof(NS_ABOSXDIRECTORY_URI_PREFIX) - 1)); + ABRecord *card = [addressBook recordForUniqueId:[NSString stringWithUTF8String:uid.get()]]; + NS_ASSERTION([card isKindOfClass:[ABGroup class]], "Huh."); + + m_IsMailList = true; + AppendToString([card valueForProperty:kABGroupNameProperty], m_ListDirName); + + ABGroup *group = (ABGroup*)[addressBook recordForUniqueId:[NSString stringWithUTF8String:nsAutoCString(Substring(mURINoQuery, 21)).get()]]; + cards = [[group members] arrayByAddingObjectsFromArray:[group subgroups]]; + + if (!m_AddressList) + m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + else + rv = m_AddressList->Clear(); + NS_ENSURE_SUCCESS(rv, rv); + + cardList = m_AddressList; + } + + + nsAutoCString ourUuid; + GetUuid(ourUuid); + + unsigned int nbCards = [cards count]; + nsCOMPtr<nsIAbCard> card; + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbOSXDirectory> rootOSXDirectory; + if (!isRootOSXDirectory) + { + rv = GetRootOSXDirectory(getter_AddRefs(rootOSXDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + } + + for (unsigned int i = 0; i < nbCards; ++i) + { + // If we're a Group, it's likely that the cards we're going + // to create were already created in the root nsAbOSXDirectory, + if (!isRootOSXDirectory) + rv = GetCard([cards objectAtIndex:i], getter_AddRefs(card), + rootOSXDirectory); + else + { + // If we're not a Group, that means we're the root nsAbOSXDirectory, + // which means we have to create the cards from scratch. + rv = CreateCard([cards objectAtIndex:i], getter_AddRefs(card)); + } + + NS_ENSURE_SUCCESS(rv, rv); + + // If we're not a query directory, we're going to want to + // tell the AB Manager that we've added some cards so that they + // show up in the address book views. + if (!mIsQueryURI) + AssertCard(abManager, card); + + } + + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +nsAbOSXDirectory::GetURI(nsACString &aURI) +{ + if (mURI.IsEmpty()) + return NS_ERROR_NOT_INITIALIZED; + + aURI = mURI; + return NS_OK; +} + +NS_IMETHODIMP +nsAbOSXDirectory::GetReadOnly(bool *aReadOnly) +{ + NS_ENSURE_ARG_POINTER(aReadOnly); + + *aReadOnly = true; + return NS_OK; +} + +static bool +CheckRedundantCards(nsIAbManager *aManager, nsIAbDirectory *aDirectory, + nsIAbCard *aCard, NSMutableArray *aCardList) +{ + nsresult rv; + nsCOMPtr<nsIAbOSXCard> osxCard = do_QueryInterface(aCard, &rv); + NS_ENSURE_SUCCESS(rv, false); + + nsAutoCString uri; + rv = osxCard->GetURI(uri); + NS_ENSURE_SUCCESS(rv, false); + NSString *uid = [NSString stringWithUTF8String:(uri.get() + 21)]; + + unsigned int i, count = [aCardList count]; + for (i = 0; i < count; ++i) { + if ([[[aCardList objectAtIndex:i] uniqueId] isEqualToString:uid]) { + [aCardList removeObjectAtIndex:i]; + break; + } + } + + if (i == count) { + aManager->NotifyDirectoryItemDeleted(aDirectory, aCard); + return true; + } + + return false; +} + +nsresult +nsAbOSXDirectory::GetRootOSXDirectory(nsIAbOSXDirectory **aResult) +{ + if (!mCacheTopLevelOSXAb) + { + // Attempt to get card from the toplevel directories + nsresult rv; + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectory(NS_LITERAL_CSTRING(NS_ABOSXDIRECTORY_URI_PREFIX"/"), + getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbOSXDirectory> osxDirectory = + do_QueryInterface(directory, &rv); + NS_ENSURE_SUCCESS(rv, rv); + mCacheTopLevelOSXAb = osxDirectory; + } + + NS_IF_ADDREF(*aResult = mCacheTopLevelOSXAb); + return NS_OK; +} + +nsresult +nsAbOSXDirectory::Update() +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + nsresult rv; + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (mIsQueryURI) { + return NS_OK; + } + + ABAddressBook *addressBook = [ABAddressBook sharedAddressBook]; + // Due to the horrible way the address book code works wrt mailing lists + // we have to use a different list depending on what we are. This pointer + // holds a reference to that list. + nsIMutableArray* cardList; + NSArray *groups, *cards; + if (m_IsMailList) { + ABGroup *group = (ABGroup*)[addressBook recordForUniqueId:[NSString stringWithUTF8String:nsAutoCString(Substring(mURINoQuery, 21)).get()]]; + groups = nil; + cards = [[group members] arrayByAddingObjectsFromArray:[group subgroups]]; + + if (!m_AddressList) + { + m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + // For mailing lists, store the cards in m_AddressList + cardList = m_AddressList; + } + else { + groups = [addressBook groups]; + cards = [[addressBook people] arrayByAddingObjectsFromArray:groups]; + + if (!mCardList) + { + mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + // For directories, store the cards in mCardList + cardList = mCardList; + } + + NSMutableArray *mutableArray = [NSMutableArray arrayWithArray:cards]; + uint32_t addressCount; + rv = cardList->GetLength(&addressCount); + NS_ENSURE_SUCCESS(rv, rv); + + while (addressCount--) + { + nsCOMPtr<nsIAbCard> card(do_QueryElementAt(cardList, addressCount, &rv)); + if (NS_FAILED(rv)) + break; + + if (CheckRedundantCards(abManager, this, card, mutableArray)) + cardList->RemoveElementAt(addressCount); + } + + NSEnumerator *enumerator = [mutableArray objectEnumerator]; + ABRecord *card; + nsCOMPtr<nsIAbCard> abCard; + nsCOMPtr<nsIAbOSXDirectory> rootOSXDirectory; + rv = GetRootOSXDirectory(getter_AddRefs(rootOSXDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + + while ((card = [enumerator nextObject])) + { + rv = GetCard(card, getter_AddRefs(abCard), rootOSXDirectory); + if (NS_FAILED(rv)) + rv = CreateCard(card, getter_AddRefs(abCard)); + NS_ENSURE_SUCCESS(rv, rv); + AssertCard(abManager, abCard); + } + + card = (ABRecord*)[addressBook recordForUniqueId:[NSString stringWithUTF8String:nsAutoCString(Substring(mURINoQuery, 21)).get()]]; + NSString * stringValue = [card valueForProperty:kABGroupNameProperty]; + if (![stringValue isEqualToString:WrapString(m_ListDirName)]) + { + nsAutoString oldValue(m_ListDirName); + AssignToString(stringValue, m_ListDirName); + nsCOMPtr<nsISupports> supports = do_QueryInterface(static_cast<nsIAbDirectory *>(this), &rv); + NS_ENSURE_SUCCESS(rv, rv); + abManager->NotifyItemPropertyChanged(supports, "DirName", + oldValue.get(), m_ListDirName.get()); + } + + if (groups) + { + mutableArray = [NSMutableArray arrayWithArray:groups]; + nsCOMPtr<nsIAbDirectory> directory; + // It is ok to use m_AddressList here as only top-level directories have + // groups, and they will be in m_AddressList + if (m_AddressList) + { + rv = m_AddressList->GetLength(&addressCount); + NS_ENSURE_SUCCESS(rv, rv); + + while (addressCount--) + { + directory = do_QueryElementAt(m_AddressList, addressCount, &rv); + if (NS_FAILED(rv)) + continue; + + nsAutoCString uri; + directory->GetURI(uri); + uri.Cut(0, 21); + NSString *uid = [NSString stringWithUTF8String:uri.get()]; + + unsigned int j, arrayCount = [mutableArray count]; + for (j = 0; j < arrayCount; ++j) { + if ([[[mutableArray objectAtIndex:j] uniqueId] isEqualToString:uid]) { + [mutableArray removeObjectAtIndex:j]; + break; + } + } + + if (j == arrayCount) { + UnassertDirectory(abManager, directory); + } + } + } + + enumerator = [mutableArray objectEnumerator]; + while ((card = [enumerator nextObject])) { + rv = GetOrCreateGroup([card uniqueId], getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + AssertDirectory(abManager, directory); + } + } + + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult +nsAbOSXDirectory::AssertChildNodes() +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + // Queries and mailing lists can't have childnodes. + if (mIsQueryURI || m_IsMailList) { + return NS_OK; + } + + nsresult rv; + nsCOMPtr<nsIAbManager> abManager = + do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + NSArray *groups = [[ABAddressBook sharedAddressBook] groups]; + + unsigned int i, count = [groups count]; + + if (count > 0 && !m_AddressList) { + m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIAbDirectory> directory; + for (i = 0; i < count; ++i) { + rv = GetOrCreateGroup([[groups objectAtIndex:i] uniqueId], + getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AssertDirectory(abManager, directory); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +nsresult +nsAbOSXDirectory::AssertDirectory(nsIAbManager *aManager, + nsIAbDirectory *aDirectory) +{ + uint32_t pos; + if (m_AddressList && + NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos))) + // We already have this directory, so no point in adding it again. + return NS_OK; + + nsresult rv; + if (!m_AddressList) { + m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = m_AddressList->AppendElement(aDirectory, false); + NS_ENSURE_SUCCESS(rv, rv); + + return aManager->NotifyDirectoryItemAdded(this, aDirectory); +} + +nsresult +nsAbOSXDirectory::AssertCard(nsIAbManager *aManager, + nsIAbCard *aCard) +{ + nsAutoCString ourUuid; + GetUuid(ourUuid); + aCard->SetDirectoryId(ourUuid); + + nsresult rv = m_IsMailList ? m_AddressList->AppendElement(aCard, false) : + mCardList->AppendElement(aCard, false); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the card's URI and add it to our card store + nsCOMPtr<nsIAbOSXCard> osxCard = do_QueryInterface(aCard, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString uri; + rv = osxCard->GetURI(uri); + + nsCOMPtr<nsIAbOSXCard> retrievedCard; + if (!mCardStore.Get(uri, getter_AddRefs(retrievedCard))) + mCardStore.Put(uri, osxCard); + + return aManager->NotifyDirectoryItemAdded(this, aCard); +} + +nsresult +nsAbOSXDirectory::UnassertCard(nsIAbManager *aManager, + nsIAbCard *aCard, + nsIMutableArray *aCardList) +{ + nsresult rv; + uint32_t pos; + + if (NS_SUCCEEDED(aCardList->IndexOf(0, aCard, &pos))) + rv = aCardList->RemoveElementAt(pos); + + return aManager->NotifyDirectoryItemDeleted(this, aCard); +} + +nsresult +nsAbOSXDirectory::UnassertDirectory(nsIAbManager *aManager, + nsIAbDirectory *aDirectory) +{ + NS_ENSURE_TRUE(m_AddressList, NS_ERROR_NULL_POINTER); + + uint32_t pos; + if (NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos))) + { + nsresult rv = m_AddressList->RemoveElementAt(pos); + NS_ENSURE_SUCCESS(rv, rv); + } + + return aManager->NotifyDirectoryItemDeleted(this, aDirectory); +} + +NS_IMETHODIMP +nsAbOSXDirectory::GetChildNodes(nsISimpleEnumerator **aNodes) +{ + NS_ENSURE_ARG_POINTER(aNodes); + + // Queries don't have childnodes. + if (mIsQueryURI || m_IsMailList || !m_AddressList) + return NS_NewEmptyEnumerator(aNodes); + + return NS_NewArrayEnumerator(aNodes, m_AddressList); +} + +NS_IMETHODIMP +nsAbOSXDirectory::GetChildCards(nsISimpleEnumerator **aCards) +{ + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + NS_ENSURE_ARG_POINTER(aCards); + + nsresult rv; + NSArray *cards; + if (mIsQueryURI) + { + nsCOMPtr<nsIAbBooleanExpression> expression; + rv = nsAbQueryStringToExpression::Convert(mQueryString, + getter_AddRefs(expression)); + NS_ENSURE_SUCCESS(rv, rv); + + bool canHandle = !m_IsMailList && Search(expression, &cards); + if (!canHandle) + return FallbackSearch(expression, aCards); + + if (!mCardList) + mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + else + mCardList->Clear(); + NS_ENSURE_SUCCESS(rv, rv); + + // The uuid for initializing cards + nsAutoCString ourUuid; + GetUuid(ourUuid); + + // Fill the results array and update the card list + unsigned int nbCards = [cards count]; + + unsigned int i; + nsCOMPtr<nsIAbCard> card; + nsCOMPtr<nsIAbOSXDirectory> rootOSXDirectory; + rv = GetRootOSXDirectory(getter_AddRefs(rootOSXDirectory)); + NS_ENSURE_SUCCESS(rv, rv); + + for (i = 0; i < nbCards; ++i) + { + rv = GetCard([cards objectAtIndex:i], getter_AddRefs(card), + rootOSXDirectory); + + if (NS_FAILED(rv)) + rv = CreateCard([cards objectAtIndex:i], + getter_AddRefs(card)); + + NS_ENSURE_SUCCESS(rv, rv); + card->SetDirectoryId(ourUuid); + + mCardList->AppendElement(card, false); + } + + return NS_NewArrayEnumerator(aCards, mCardList); + } + + // Not a search, so just return the appropriate list of items. + return m_IsMailList ? NS_NewArrayEnumerator(aCards, m_AddressList) : + NS_NewArrayEnumerator(aCards, mCardList); + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +nsAbOSXDirectory::GetIsQuery(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mIsQueryURI; + return NS_OK; +} + +/* Recursive method that searches for a child card by URI. If it cannot find + * it within this directory, it checks all subfolders. + */ +NS_IMETHODIMP +nsAbOSXDirectory::GetCardByUri(const nsACString &aUri, nsIAbOSXCard **aResult) +{ + nsCOMPtr<nsIAbOSXCard> osxCard; + + // Base Case + if (mCardStore.Get(aUri, getter_AddRefs(osxCard))) + { + NS_IF_ADDREF(*aResult = osxCard); + return NS_OK; + } + // Search children + nsCOMPtr<nsISimpleEnumerator> enumerator; + nsresult rv = this->GetChildNodes(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupports> item; + bool hasMore = false; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) + { + rv = enumerator->GetNext(getter_AddRefs(item)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbOSXDirectory> childDirectory; + childDirectory = do_QueryInterface(item, &rv); + if (NS_SUCCEEDED(rv)) + { + rv = childDirectory->GetCardByUri(aUri, getter_AddRefs(osxCard)); + if (NS_SUCCEEDED(rv)) + { + NS_IF_ADDREF(*aResult = osxCard); + return NS_OK; + } + } + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsAbOSXDirectory::GetCardFromProperty(const char *aProperty, + const nsACString &aValue, + bool aCaseSensitive, + nsIAbCard **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = nullptr; + + if (aValue.IsEmpty()) + return NS_OK; + + nsIMutableArray *list = m_IsMailList ? m_AddressList : mCardList; + + if (!list) + return NS_OK; + + uint32_t length; + nsresult rv = list->GetLength(&length); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbCard> card; + nsAutoCString cardValue; + + for (uint32_t i = 0; i < length && !*aResult; ++i) + { + card = do_QueryElementAt(list, i, &rv); + if (NS_SUCCEEDED(rv)) + { + rv = card->GetPropertyAsAUTF8String(aProperty, cardValue); + if (NS_SUCCEEDED(rv)) + { +#ifdef MOZILLA_INTERNAL_API + bool equal = aCaseSensitive ? cardValue.Equals(aValue) : + cardValue.Equals(aValue, nsCaseInsensitiveCStringComparator()); +#else + bool equal = aCaseSensitive ? cardValue.Equals(aValue) : + cardValue.Equals(aValue, CaseInsensitiveCompare); +#endif + if (equal) + NS_IF_ADDREF(*aResult = card); + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsAbOSXDirectory::GetCardsFromProperty(const char *aProperty, + const nsACString &aValue, + bool aCaseSensitive, + nsISimpleEnumerator **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = nullptr; + + if (aValue.IsEmpty()) + return NS_NewEmptyEnumerator(aResult); + + nsIMutableArray *list = m_IsMailList ? m_AddressList : mCardList; + + if (!list) + return NS_NewEmptyEnumerator(aResult); + + uint32_t length; + nsresult rv = list->GetLength(&length); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMArray<nsIAbCard> resultArray; + nsCOMPtr<nsIAbCard> card; + nsAutoCString cardValue; + + for (uint32_t i = 0; i < length; ++i) + { + card = do_QueryElementAt(list, i, &rv); + if (NS_SUCCEEDED(rv)) + { + rv = card->GetPropertyAsAUTF8String(aProperty, cardValue); + if (NS_SUCCEEDED(rv)) + { +#ifdef MOZILLA_INTERNAL_API + bool equal = aCaseSensitive ? cardValue.Equals(aValue) : + cardValue.Equals(aValue, nsCaseInsensitiveCStringComparator()); +#else + bool equal = aCaseSensitive ? cardValue.Equals(aValue) : + cardValue.Equals(aValue, CaseInsensitiveCompare); +#endif + if (equal) + resultArray.AppendObject(card); + } + } + } + + return NS_NewArrayEnumerator(aResult, resultArray); +} + +NS_IMETHODIMP +nsAbOSXDirectory::CardForEmailAddress(const nsACString &aEmailAddress, + nsIAbCard **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = nullptr; + + if (aEmailAddress.IsEmpty()) + return NS_OK; + + nsIMutableArray *list = m_IsMailList ? m_AddressList : mCardList; + + if (!list) + return NS_OK; + + uint32_t length; + nsresult rv = list->GetLength(&length); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbCard> card; + + for (uint32_t i = 0; i < length && !*aResult; ++i) + { + card = do_QueryElementAt(list, i, &rv); + if (NS_SUCCEEDED(rv)) + { + bool hasEmailAddress = false; + + rv = card->HasEmailAddress(aEmailAddress, &hasEmailAddress); + if (NS_SUCCEEDED(rv) && hasEmailAddress) + NS_IF_ADDREF(*aResult = card); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsAbOSXDirectory::HasCard(nsIAbCard *aCard, bool *aHasCard) +{ + NS_ENSURE_ARG_POINTER(aCard); + NS_ENSURE_ARG_POINTER(aHasCard); + + nsresult rv = NS_OK; + uint32_t index; + if (m_IsMailList) + { + if (m_AddressList) + rv = m_AddressList->IndexOf(0, aCard, &index); + } + else if (mCardList) + rv = mCardList->IndexOf(0, aCard, &index); + + *aHasCard = NS_SUCCEEDED(rv); + + return NS_OK; +} + +NS_IMETHODIMP +nsAbOSXDirectory::HasDirectory(nsIAbDirectory *aDirectory, + bool *aHasDirectory) +{ + NS_ENSURE_ARG_POINTER(aDirectory); + NS_ENSURE_ARG_POINTER(aHasDirectory); + + *aHasDirectory = false; + + uint32_t pos; + if (m_AddressList && NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos))) + *aHasDirectory = true; + + return NS_OK; +} + +NS_IMETHODIMP +nsAbOSXDirectory::OnSearchFinished(int32_t aResult, const nsAString &aErrorMsg) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsAbOSXDirectory::OnSearchFoundCard(nsIAbCard *aCard) +{ + nsresult rv; + if (!m_AddressList) { + m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (!mCardList) { + mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = m_AddressList->AppendElement(aCard, false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mCardList->AppendElement(aCard, false); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString ourUuid; + GetUuid(ourUuid); + aCard->SetDirectoryId(ourUuid); + + return NS_OK; +} + +nsresult +nsAbOSXDirectory::FallbackSearch(nsIAbBooleanExpression *aExpression, + nsISimpleEnumerator **aCards) +{ + nsresult rv; + + if (mCardList) + rv = mCardList->Clear(); + else + mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (m_AddressList) { + m_AddressList->Clear(); + } + else { + m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIAbDirectoryQueryArguments> arguments = + do_CreateInstance(NS_ABDIRECTORYQUERYARGUMENTS_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = arguments->SetExpression(aExpression); + NS_ENSURE_SUCCESS(rv, rv); + + // Don't search the subdirectories. If the current directory is a mailing + // list, it won't have any subdirectories. If the current directory is an + // addressbook, searching both it and the subdirectories (the mailing + // lists), will yield duplicate results because every entry in a mailing + // list will be an entry in the parent addressbook. + rv = arguments->SetQuerySubDirectories(false); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the directory without the query + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectory(mURINoQuery, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + // Initiate the proxy query with the no query directory + nsCOMPtr<nsIAbDirectoryQueryProxy> queryProxy = + do_CreateInstance(NS_ABDIRECTORYQUERYPROXY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = queryProxy->Initiate(); + NS_ENSURE_SUCCESS(rv, rv); + + int32_t context = 0; + rv = queryProxy->DoQuery(directory, arguments, this, -1, 0, &context); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_NewArrayEnumerator(aCards, m_AddressList); +} + +nsresult nsAbOSXDirectory::DeleteUid(const nsACString &aUid) +{ + if (!m_AddressList) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + nsCOMPtr<nsIAbManager> abManager = + do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // At this stage we don't know if aUid represents a card or group. The OS X + // interfaces don't give us chance to find out, so we have to go through + // our lists to find it. + + // First, we'll see if its in the group list as it is likely to be shorter. + + // See if this item is in our address list + uint32_t addressCount; + rv = m_AddressList->GetLength(&addressCount); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString uri(NS_ABOSXDIRECTORY_URI_PREFIX); + uri.Append(aUid); + + // Iterate backwards in case we remove something + while (addressCount--) + { + nsCOMPtr<nsIAbItem> abItem(do_QueryElementAt(m_AddressList, + addressCount, &rv)); + if (NS_FAILED(rv)) + continue; + + nsCOMPtr<nsIAbDirectory> directory(do_QueryInterface(abItem, &rv)); + if (NS_SUCCEEDED(rv)) + { + nsAutoCString dirUri; + directory->GetURI(dirUri); + if (uri.Equals(dirUri)) + return UnassertDirectory(abManager, directory); + } else { + nsCOMPtr<nsIAbOSXCard> osxCard(do_QueryInterface(abItem, &rv)); + if (NS_SUCCEEDED(rv)) + { + nsAutoCString cardUri; + osxCard->GetURI(cardUri); + if (uri.Equals(cardUri)) + { + nsCOMPtr<nsIAbCard> card(do_QueryInterface(osxCard, &rv)); + if (NS_SUCCEEDED(rv)) + return UnassertCard(abManager, card, m_AddressList); + } + } + } + } + + // Second, see if it is one of the cards. + if (!mCardList) + return NS_ERROR_FAILURE; + + uri = NS_ABOSXCARD_URI_PREFIX; + uri.Append(aUid); + + rv = mCardList->GetLength(&addressCount); + NS_ENSURE_SUCCESS(rv, rv); + + while (addressCount--) + { + nsCOMPtr<nsIAbOSXCard> osxCard(do_QueryElementAt(mCardList, addressCount, &rv)); + if (NS_FAILED(rv)) + continue; + + nsAutoCString cardUri; + osxCard->GetURI(cardUri); + + if (uri.Equals(cardUri)) { + nsCOMPtr<nsIAbCard> card(do_QueryInterface(osxCard, &rv)); + if (NS_SUCCEEDED(rv)) + return UnassertCard(abManager, card, mCardList); + } + } + return NS_OK; +} diff --git a/mailnews/addrbook/src/nsAbOSXUtils.h b/mailnews/addrbook/src/nsAbOSXUtils.h new file mode 100644 index 000000000..fc2f2a546 --- /dev/null +++ b/mailnews/addrbook/src/nsAbOSXUtils.h @@ -0,0 +1,36 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef nsAbOSXUtils_h___ +#define nsAbOSXUtils_h___ + +#include <Foundation/NSString.h> +#include "nsStringGlue.h" + +class nsString; +class nsCString; +class nsAbCardProperty; + +NSString *WrapString(const nsString &aString); +void AppendToString(const NSString *aString, nsString &aResult); +void AssignToString(const NSString *aString, nsString &aResult); +void AppendToCString(const NSString *aString, nsCString &aResult); + +struct nsAbOSXPropertyMap +{ + NSString * const mOSXProperty; + NSString * const mOSXLabel; + NSString * const mOSXKey; + const char *mPropertyName; +}; + +class nsAbOSXUtils +{ +public: + static const nsAbOSXPropertyMap kPropertyMap[]; + static const uint32_t kPropertyMapSize; +}; + +#endif // nsAbOSXUtils_h___ diff --git a/mailnews/addrbook/src/nsAbOSXUtils.mm b/mailnews/addrbook/src/nsAbOSXUtils.mm new file mode 100644 index 000000000..60802d772 --- /dev/null +++ b/mailnews/addrbook/src/nsAbOSXUtils.mm @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAbOSXUtils.h" +#include "nsStringGlue.h" +#include "nsAbOSXCard.h" +#include "nsMemory.h" +#include "mozilla/ArrayUtils.h" +using namespace mozilla; + +#include <AddressBook/AddressBook.h> +#define kABDepartmentProperty (kABDepartmentProperty ? kABDepartmentProperty : @"ABDepartment") + +NSString* +WrapString(const nsString &aString) +{ + unichar* chars = reinterpret_cast<unichar*>(const_cast<char16_t*>(aString.get())); + + return [NSString stringWithCharacters:chars + length:aString.Length()]; +} + +void +AppendToString(const NSString *aString, nsString &aResult) +{ + if (aString) { + const char *chars = [aString UTF8String]; + if (chars) { + aResult.Append(NS_ConvertUTF8toUTF16(chars)); + } + } +} + +void +AssignToString(const NSString *aString, nsString &aResult) +{ + if (aString) { + const char *chars = [aString UTF8String]; + if (chars) + CopyUTF8toUTF16(nsDependentCString(chars), aResult); + } +} + +void +AppendToCString(const NSString *aString, nsCString &aResult) +{ + if (aString) { + const char *chars = [aString UTF8String]; + if (chars) { + aResult.Append(chars); + } + } +} + +// Some properties can't be easily mapped back and forth. +#define DONT_MAP(moz_name, osx_property, osx_label, osx_key) + +#define DEFINE_PROPERTY(moz_name, osx_property, osx_label, osx_key) \ + { osx_property, osx_label, osx_key, #moz_name }, + +const nsAbOSXPropertyMap nsAbOSXUtils::kPropertyMap[] = { + DEFINE_PROPERTY(FirstName, kABFirstNameProperty, nil, nil) + DEFINE_PROPERTY(LastName, kABLastNameProperty, nil, nil) + DONT_MAP("DisplayName", nil, nil, nil) + DEFINE_PROPERTY(PhoneticFirstName, kABFirstNamePhoneticProperty, nil, nil) + DEFINE_PROPERTY(PhoneticLastName, kABLastNamePhoneticProperty, nil, nil) + DEFINE_PROPERTY(NickName, kABNicknameProperty, nil, nil) + DONT_MAP(PrimaryEmail, kABEmailProperty, nil, nil) + DONT_MAP(SecondEmail, kABEmailProperty, nil, nil) + DEFINE_PROPERTY(WorkPhone, kABPhoneProperty, kABPhoneWorkLabel, nil) + DEFINE_PROPERTY(HomePhone, kABPhoneProperty, kABPhoneHomeLabel, nil) + DEFINE_PROPERTY(FaxNumber, kABPhoneProperty, kABPhoneWorkFAXLabel, nil) + DEFINE_PROPERTY(PagerNumber, kABPhoneProperty, kABPhonePagerLabel, nil) + DEFINE_PROPERTY(CellularNumber, kABPhoneProperty, kABPhoneMobileLabel, nil) + DEFINE_PROPERTY(HomeAddress, kABAddressProperty, kABAddressHomeLabel, + kABAddressStreetKey) + DEFINE_PROPERTY(HomeCity, kABAddressProperty, kABAddressHomeLabel, + kABAddressCityKey) + DEFINE_PROPERTY(HomeState, kABAddressProperty, kABAddressHomeLabel, + kABAddressStateKey) + DEFINE_PROPERTY(HomeZipCode, kABAddressProperty, kABAddressHomeLabel, + kABAddressZIPKey) + DEFINE_PROPERTY(HomeCountry, kABAddressProperty, kABAddressHomeLabel, + kABAddressCountryKey) + DEFINE_PROPERTY(WorkAddress, kABAddressProperty, kABAddressWorkLabel, + kABAddressStreetKey) + DEFINE_PROPERTY(WorkCity, kABAddressProperty, kABAddressWorkLabel, + kABAddressCityKey) + DEFINE_PROPERTY(WorkState, kABAddressProperty, kABAddressWorkLabel, + kABAddressStateKey) + DEFINE_PROPERTY(WorkZipCode, kABAddressProperty, kABAddressWorkLabel, + kABAddressZIPKey) + DEFINE_PROPERTY(WorkCountry, kABAddressProperty, kABAddressWorkLabel, + kABAddressCountryKey) + DEFINE_PROPERTY(JobTitle, kABJobTitleProperty, nil, nil) + DEFINE_PROPERTY(Department, kABDepartmentProperty, nil, nil) + DEFINE_PROPERTY(Company, kABOrganizationProperty, nil, nil) + // This was kABAIMInstantProperty previously, but it was deprecated in OS X 10.7. + DONT_MAP(_AimScreenName, kABInstantMessageProperty, nil, nil) + DEFINE_PROPERTY(WebPage1, kABHomePageProperty, nil, nil) + DONT_MAP(WebPage2, kABHomePageProperty, nil, nil) + DONT_MAP(BirthYear, "birthyear", nil, nil) + DONT_MAP(BirthMonth, "birthmonth", nil, nil) + DONT_MAP(BirthDay, "birthday", nil, nil) + DONT_MAP(Custom1, "custom1", nil, nil) + DONT_MAP(Custom2, "custom2", nil, nil) + DONT_MAP(Custom3, "custom3", nil, nil) + DONT_MAP(Custom4, "custom4", nil, nil) + DEFINE_PROPERTY(Note, kABNoteProperty, nil, nil) + DONT_MAP("PreferMailFormat", nil, nil, nil) + DONT_MAP("LastModifiedDate", modifytimestamp, nil, nil) +}; + +const uint32_t nsAbOSXUtils::kPropertyMapSize = + ArrayLength(nsAbOSXUtils::kPropertyMap); diff --git a/mailnews/addrbook/src/nsAbOutlookDirFactory.cpp b/mailnews/addrbook/src/nsAbOutlookDirFactory.cpp new file mode 100644 index 000000000..164e5a475 --- /dev/null +++ b/mailnews/addrbook/src/nsAbOutlookDirFactory.cpp @@ -0,0 +1,87 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ +#include "nsAbOutlookDirFactory.h" +#include "nsAbWinHelper.h" +#include "nsIAbDirectory.h" +#include "nsIAbManager.h" +#include "nsEnumeratorUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsIMutableArray.h" +#include "nsArrayEnumerator.h" +#include "nsAbBaseCID.h" +#include "mozilla/Logging.h" + +#ifdef PR_LOGGING +static PRLogModuleInfo* gAbOutlookDirFactoryLog + = PR_NewLogModule("nsAbOutlookDirFactoryLog"); +#endif + +#define PRINTF(args) MOZ_LOG(nsAbOutlookDirFactoryLog, mozilla::LogLevel::Debug, args) + + +NS_IMPL_ISUPPORTS(nsAbOutlookDirFactory, nsIAbDirFactory) + +nsAbOutlookDirFactory::nsAbOutlookDirFactory(void) +{ +} + +nsAbOutlookDirFactory::~nsAbOutlookDirFactory(void) +{ +} + +extern const char *kOutlookDirectoryScheme; + +NS_IMETHODIMP +nsAbOutlookDirFactory::GetDirectories(const nsAString &aDirName, + const nsACString &aURI, + const nsACString &aPrefName, + nsISimpleEnumerator **aDirectories) +{ + NS_ENSURE_ARG_POINTER(aDirectories); + + *aDirectories = nullptr; + nsresult rv = NS_OK; + nsCString stub; + nsCString entry; + nsAbWinType abType = getAbWinType(kOutlookDirectoryScheme, + nsCString(aURI).get(), stub, entry); + + if (abType == nsAbWinType_Unknown) { + return NS_ERROR_FAILURE; + } + nsAbWinHelperGuard mapiAddBook(abType); + nsMapiEntryArray folders; + ULONG nbFolders = 0; + nsCOMPtr<nsIMutableArray> directories(do_CreateInstance(NS_ARRAY_CONTRACTID)); + NS_ENSURE_SUCCESS(rv, rv); + if (!mapiAddBook->IsOK() || !mapiAddBook->GetFolders(folders)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString entryId; + nsAutoCString uri; + + for (ULONG i = 0; i < folders.mNbEntries; ++i) { + folders.mEntries[i].ToString(entryId); + buildAbWinUri(kOutlookDirectoryScheme, abType, uri); + uri.Append(entryId); + + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectory(uri, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + directories->AppendElement(directory, false); + } + return NS_NewArrayEnumerator(aDirectories, directories); +} + +// No actual deletion, since you cannot create the address books from Mozilla. +NS_IMETHODIMP nsAbOutlookDirFactory::DeleteDirectory(nsIAbDirectory *aDirectory) +{ + return NS_OK; +} + diff --git a/mailnews/addrbook/src/nsAbOutlookDirFactory.h b/mailnews/addrbook/src/nsAbOutlookDirFactory.h new file mode 100644 index 000000000..c66a9c904 --- /dev/null +++ b/mailnews/addrbook/src/nsAbOutlookDirFactory.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ +#ifndef nsAbOutlookDirFactory_h___ +#define nsAbOutlookDirFactory_h___ + +#include "nsIAbDirFactory.h" + +class nsAbOutlookDirFactory : public nsIAbDirFactory +{ +public: + nsAbOutlookDirFactory(void); + + NS_DECL_ISUPPORTS + NS_DECL_NSIABDIRFACTORY + +private: + virtual ~nsAbOutlookDirFactory(void); +}; + +#endif // nsAbOutlookDirFactory_h___ diff --git a/mailnews/addrbook/src/nsAbOutlookDirectory.cpp b/mailnews/addrbook/src/nsAbOutlookDirectory.cpp new file mode 100644 index 000000000..0d39d1d17 --- /dev/null +++ b/mailnews/addrbook/src/nsAbOutlookDirectory.cpp @@ -0,0 +1,1539 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ +#include "nsAbOutlookDirectory.h" +#include "nsAbWinHelper.h" + +#include "nsAbBaseCID.h" +#include "nsIAbCard.h" +#include "nsStringGlue.h" +#include "nsAbDirectoryQuery.h" +#include "nsIAbBooleanExpression.h" +#include "nsIAbManager.h" +#include "nsIAbMDBDirectory.h" +#include "nsAbQueryStringToExpression.h" +#include "nsAbUtils.h" +#include "nsEnumeratorUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "mozilla/Logging.h" +#include "prthread.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsCRTGlue.h" +#include "nsArrayUtils.h" +#include "nsArrayEnumerator.h" +#include "nsMsgUtils.h" + +#ifdef PR_LOGGING +static PRLogModuleInfo* gAbOutlookDirectoryLog + = PR_NewLogModule("nsAbOutlookDirectoryLog"); +#endif + +#define PRINTF(args) MOZ_LOG(gAbOutlookDirectoryLog, mozilla::LogLevel::Debug, args) + +nsAbOutlookDirectory::nsAbOutlookDirectory(void) + : nsAbDirProperty(), + mCurrentQueryId(0), mSearchContext(-1), + mAbWinType(nsAbWinType_Unknown), mMapiData(nullptr) +{ + mMapiData = new nsMapiEntry ; + mProtector = PR_NewLock() ; +} + +nsAbOutlookDirectory::~nsAbOutlookDirectory(void) +{ + if (mMapiData) { delete mMapiData ; } + if (mProtector) { PR_DestroyLock(mProtector) ; } +} + +NS_IMPL_ISUPPORTS_INHERITED(nsAbOutlookDirectory, nsAbDirProperty, + nsIAbDirectoryQuery, nsIAbDirectorySearch, + nsIAbDirSearchListener) + +NS_IMETHODIMP nsAbOutlookDirectory::Init(const char *aUri) +{ + nsresult rv = nsAbDirProperty::Init(aUri); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString entry; + nsAutoCString stub; + + mAbWinType = getAbWinType(kOutlookDirectoryScheme, mURINoQuery.get(), stub, entry); + if (mAbWinType == nsAbWinType_Unknown) { + PRINTF(("Huge problem URI=%s.\n", mURINoQuery)); + return NS_ERROR_INVALID_ARG; + } + nsAbWinHelperGuard mapiAddBook (mAbWinType); + nsString prefix; + nsAutoString unichars; + ULONG objectType = 0; + + if (!mapiAddBook->IsOK()) + return NS_ERROR_FAILURE; + + mMapiData->Assign(entry); + if (!mapiAddBook->GetPropertyLong(*mMapiData, PR_OBJECT_TYPE, objectType)) { + PRINTF(("Cannot get type.\n")); + return NS_ERROR_FAILURE; + } + if (!mapiAddBook->GetPropertyUString(*mMapiData, PR_DISPLAY_NAME_W, unichars)) { + PRINTF(("Cannot get name.\n")); + return NS_ERROR_FAILURE; + } + + if (mAbWinType == nsAbWinType_Outlook) + prefix.AssignLiteral("OP "); + else + prefix.AssignLiteral("OE "); + prefix.Append(unichars); + + if (objectType == MAPI_DISTLIST) { + m_IsMailList = true; + SetDirName(unichars); + } + else { + m_IsMailList = false; + SetDirName(prefix); + } + + return UpdateAddressList(); +} + +// nsIAbDirectory methods + +NS_IMETHODIMP nsAbOutlookDirectory::GetDirType(int32_t *aDirType) +{ + NS_ENSURE_ARG_POINTER(aDirType); + *aDirType = MAPIDirectory; + return NS_OK; +} + +NS_IMETHODIMP nsAbOutlookDirectory::GetURI(nsACString &aURI) +{ + if (mURI.IsEmpty()) + return NS_ERROR_NOT_INITIALIZED; + + aURI = mURI; + return NS_OK; +} + +NS_IMETHODIMP nsAbOutlookDirectory::GetChildNodes(nsISimpleEnumerator **aNodes) +{ + NS_ENSURE_ARG_POINTER(aNodes); + + *aNodes = nullptr; + + if (mIsQueryURI) { + return NS_NewEmptyEnumerator(aNodes); + } + + nsresult rv; + nsCOMPtr<nsIMutableArray> nodeList(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetChildNodes(nodeList); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_NewArrayEnumerator(aNodes, nodeList); +} + +NS_IMETHODIMP nsAbOutlookDirectory::GetChildCards(nsISimpleEnumerator **aCards) +{ + NS_ENSURE_ARG_POINTER(aCards); + *aCards = nullptr; + + nsresult rv; + nsCOMPtr<nsIMutableArray> cardList(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + mCardList.Clear(); + + rv = mIsQueryURI ? StartSearch() : GetChildCards(cardList, nullptr); + + NS_ENSURE_SUCCESS(rv, rv); + if (!m_AddressList) + { + m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Fill the results array and update the card list + // Also update the address list and notify any changes. + uint32_t nbCards = 0; + + NS_NewArrayEnumerator(aCards, cardList); + cardList->GetLength(&nbCards); + + nsCOMPtr<nsIAbCard> card; + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + + for (uint32_t i = 0; i < nbCards; ++i) + { + card = do_QueryElementAt(cardList, i, &rv); + if (NS_FAILED(rv)) + continue; + + if (!mCardList.Get(card, nullptr)) + { + // We are dealing with a new element (probably directly + // added from Outlook), we may need to sync m_AddressList + mCardList.Put(card, card); + + bool isMailList = false; + + rv = card->GetIsMailList(&isMailList); + NS_ENSURE_SUCCESS(rv, rv); + if (isMailList) + { + // We can have mailing lists only in folder, + // we must add the directory to m_AddressList + nsCString mailListUri; + rv = card->GetMailListURI(getter_Copies(mailListUri)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIAbDirectory> mailList; + rv = abManager->GetDirectory(mailListUri, getter_AddRefs(mailList)); + NS_ENSURE_SUCCESS(rv, rv); + + m_AddressList->AppendElement(mailList, false); + NotifyItemAddition(mailList); + } + else if (m_IsMailList) + { + m_AddressList->AppendElement(card, false); + NotifyItemAddition(card); + } + } + } + return rv; +} + +NS_IMETHODIMP nsAbOutlookDirectory::GetIsQuery(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mIsQueryURI; + return NS_OK; +} + +NS_IMETHODIMP nsAbOutlookDirectory::HasCard(nsIAbCard *aCard, bool *aHasCard) +{ + if (!aCard || !aHasCard) + return NS_ERROR_NULL_POINTER; + + *aHasCard = mCardList.Get(aCard, nullptr); + return NS_OK; +} + +NS_IMETHODIMP nsAbOutlookDirectory::HasDirectory(nsIAbDirectory *aDirectory, bool *aHasDirectory) +{ + NS_ENSURE_ARG_POINTER(aDirectory); + NS_ENSURE_ARG_POINTER(aHasDirectory); + + *aHasDirectory = false; + + uint32_t pos; + if (m_AddressList && NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos))) + *aHasDirectory = true; + + return NS_OK; +} + + +static nsresult ExtractCardEntry(nsIAbCard *aCard, nsCString& aEntry) +{ + aEntry.Truncate(); + + nsCString uri; + aCard->GetPropertyAsAUTF8String("OutlookEntryURI", uri); + + // If we don't have a URI, uri will be empty. getAbWinType doesn't set + // aEntry to anything if uri is empty, so it will be truncated, allowing us + // to accept cards not initialized by us. + nsAutoCString stub; + getAbWinType(kOutlookCardScheme, uri.get(), stub, aEntry); + return NS_OK; +} + +static nsresult ExtractDirectoryEntry(nsIAbDirectory *aDirectory, nsCString& aEntry) +{ + aEntry.Truncate(); + nsCString uri; + nsresult rv = aDirectory->GetURI(uri); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString stub; + nsAbWinType objType = getAbWinType(kOutlookDirectoryScheme, uri.get(), stub, aEntry); + + return NS_OK; +} + +NS_IMETHODIMP nsAbOutlookDirectory::DeleteCards(nsIArray *aCardList) +{ + if (mIsQueryURI) { return NS_ERROR_NOT_IMPLEMENTED ; } + if (!aCardList) { return NS_ERROR_NULL_POINTER ; } + uint32_t nbCards = 0 ; + nsresult retCode = NS_OK ; + nsAbWinHelperGuard mapiAddBook (mAbWinType) ; + + if (!mapiAddBook->IsOK()) { return NS_ERROR_FAILURE ; } + + retCode = aCardList->GetLength(&nbCards); + NS_ENSURE_SUCCESS(retCode, retCode) ; + uint32_t i = 0 ; + nsAutoCString entryString ; + nsMapiEntry cardEntry ; + + for (i = 0 ; i < nbCards ; ++ i) { + nsCOMPtr<nsIAbCard> card(do_QueryElementAt(aCardList, i, &retCode)); + NS_ENSURE_SUCCESS(retCode, retCode); + + retCode = ExtractCardEntry(card, entryString) ; + if (NS_SUCCEEDED(retCode) && !entryString.IsEmpty()) { + card->SetDirectoryId(EmptyCString()); + + cardEntry.Assign(entryString) ; + if (!mapiAddBook->DeleteEntry(*mMapiData, cardEntry)) { + PRINTF(("Cannot delete card %s.\n", entryString.get())) ; + } + else { + mCardList.Remove(card); + if (m_IsMailList && m_AddressList) + { + uint32_t pos; + if (NS_SUCCEEDED(m_AddressList->IndexOf(0, card, &pos))) + m_AddressList->RemoveElementAt(pos); + } + retCode = NotifyItemDeletion(card); + NS_ENSURE_SUCCESS(retCode, retCode) ; + } + } + else { + PRINTF(("Card doesn't belong in this directory.\n")) ; + } + } + return NS_OK ; +} + +NS_IMETHODIMP nsAbOutlookDirectory::DeleteDirectory(nsIAbDirectory *aDirectory) +{ + if (mIsQueryURI) { return NS_ERROR_NOT_IMPLEMENTED ; } + if (!aDirectory) { return NS_ERROR_NULL_POINTER ; } + nsresult retCode = NS_OK ; + nsAbWinHelperGuard mapiAddBook (mAbWinType) ; + nsAutoCString entryString ; + + if (!mapiAddBook->IsOK()) { return NS_ERROR_FAILURE ; } + retCode = ExtractDirectoryEntry(aDirectory, entryString) ; + if (NS_SUCCEEDED(retCode) && !entryString.IsEmpty()) { + nsMapiEntry directoryEntry ; + + directoryEntry.Assign(entryString) ; + if (!mapiAddBook->DeleteEntry(*mMapiData, directoryEntry)) { + PRINTF(("Cannot delete directory %s.\n", entryString.get())) ; + } + else { + uint32_t pos; + if (m_AddressList && NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos))) + m_AddressList->RemoveElementAt(pos); + + retCode = NotifyItemDeletion(aDirectory); + NS_ENSURE_SUCCESS(retCode, retCode); + } + } + else { + PRINTF(("Directory doesn't belong to this folder.\n")) ; + } + return retCode ; +} + +NS_IMETHODIMP nsAbOutlookDirectory::AddCard(nsIAbCard *aData, nsIAbCard **addedCard) +{ + if (mIsQueryURI) + return NS_ERROR_NOT_IMPLEMENTED; + + NS_ENSURE_ARG_POINTER(aData); + + nsresult retCode = NS_OK ; + bool hasCard = false ; + + retCode = HasCard(aData, &hasCard) ; + NS_ENSURE_SUCCESS(retCode, retCode) ; + if (hasCard) { + PRINTF(("Has card.\n")) ; + NS_IF_ADDREF(*addedCard = aData); + return NS_OK ; + } + retCode = CreateCard(aData, addedCard) ; + NS_ENSURE_SUCCESS(retCode, retCode) ; + + mCardList.Put(*addedCard, *addedCard); + + if (!m_AddressList) + { + m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &retCode); + NS_ENSURE_SUCCESS(retCode, retCode); + } + + if (m_IsMailList) + m_AddressList->AppendElement(*addedCard, false); + NotifyItemAddition(*addedCard) ; + return retCode ; +} + +NS_IMETHODIMP nsAbOutlookDirectory::DropCard(nsIAbCard *aData, bool needToCopyCard) +{ + nsCOMPtr <nsIAbCard> addedCard; + return AddCard(aData, getter_AddRefs(addedCard)); +} + +NS_IMETHODIMP nsAbOutlookDirectory::AddMailList(nsIAbDirectory *aMailList, nsIAbDirectory **addedList) +{ + if (mIsQueryURI) + return NS_ERROR_NOT_IMPLEMENTED; + NS_ENSURE_ARG_POINTER(aMailList); + NS_ENSURE_ARG_POINTER(addedList); + if (m_IsMailList) + return NS_OK; + nsAbWinHelperGuard mapiAddBook (mAbWinType); + nsAutoCString entryString; + nsMapiEntry newEntry; + bool didCopy = false; + + if (!mapiAddBook->IsOK()) + return NS_ERROR_FAILURE; + nsresult rv = ExtractDirectoryEntry(aMailList, entryString); + if (NS_SUCCEEDED(rv) && !entryString.IsEmpty()) + { + nsMapiEntry sourceEntry; + + sourceEntry.Assign(entryString); + mapiAddBook->CopyEntry(*mMapiData, sourceEntry, newEntry); + } + if (newEntry.mByteCount == 0) + { + if (!mapiAddBook->CreateDistList(*mMapiData, newEntry)) + return NS_ERROR_FAILURE; + } + else { + didCopy = true; + } + newEntry.ToString(entryString); + nsAutoCString uri; + + buildAbWinUri(kOutlookDirectoryScheme, mAbWinType, uri); + uri.Append(entryString); + + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> newList; + rv = abManager->GetDirectory(uri, getter_AddRefs(newList)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!didCopy) + { + rv = newList->CopyMailList(aMailList); + NS_ENSURE_SUCCESS(rv, rv); + rv = newList->EditMailListToDatabase(nullptr); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (!m_AddressList) + { + m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + m_AddressList->AppendElement(newList, false); + NotifyItemAddition(newList); + NS_IF_ADDREF(*addedList = newList); + + return rv; +} + +NS_IMETHODIMP nsAbOutlookDirectory::EditMailListToDatabase(nsIAbCard *listCard) +{ + if (mIsQueryURI) + return NS_ERROR_NOT_IMPLEMENTED; + + nsresult rv; + nsString name; + nsAbWinHelperGuard mapiAddBook(mAbWinType); + + if (!mapiAddBook->IsOK()) + return NS_ERROR_FAILURE; + + rv = GetDirName(name); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mapiAddBook->SetPropertyUString(*mMapiData, PR_DISPLAY_NAME_W, + name.get())) + return NS_ERROR_FAILURE; + + return CommitAddressList(); +} + +struct OutlookTableAttr +{ + const char *mOuterName ; + ULONG mMapiProp ; +} ; + +// Here, we are forced to use the Ascii versions of the properties +// instead of the widechar ones, because the content restriction +// operators do not work on unicode strings in mapi. +static const OutlookTableAttr OutlookTableStringToProp [] = +{ + {kFirstNameProperty, PR_GIVEN_NAME_A}, + {kLastNameProperty, PR_SURNAME_A}, + {kDisplayNameProperty, PR_DISPLAY_NAME_A}, + {kNicknameProperty, PR_NICKNAME_A}, + {kPriEmailProperty, PR_EMAIL_ADDRESS_A}, + {kWorkPhoneProperty, PR_BUSINESS_TELEPHONE_NUMBER_A}, + {kHomePhoneProperty, PR_HOME_TELEPHONE_NUMBER_A}, + {kFaxProperty, PR_BUSINESS_FAX_NUMBER_A}, + {kPagerProperty, PR_PAGER_TELEPHONE_NUMBER_A}, + {kCellularProperty, PR_MOBILE_TELEPHONE_NUMBER_A}, + {kHomeAddressProperty, PR_HOME_ADDRESS_STREET_A}, + {kHomeCityProperty, PR_HOME_ADDRESS_CITY_A}, + {kHomeStateProperty, PR_HOME_ADDRESS_STATE_OR_PROVINCE_A}, + {kHomeZipCodeProperty, PR_HOME_ADDRESS_POSTAL_CODE_A}, + {kHomeCountryProperty, PR_HOME_ADDRESS_COUNTRY_A}, + {kWorkAddressProperty, PR_BUSINESS_ADDRESS_STREET_A}, + {kWorkCityProperty, PR_BUSINESS_ADDRESS_CITY_A}, + {kWorkStateProperty, PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_A}, + {kWorkZipCodeProperty, PR_BUSINESS_ADDRESS_POSTAL_CODE_A}, + {kWorkCountryProperty, PR_BUSINESS_ADDRESS_COUNTRY_A}, + {kJobTitleProperty, PR_TITLE_A}, + {kDepartmentProperty, PR_DEPARTMENT_NAME_A}, + {kCompanyProperty, PR_COMPANY_NAME_A}, + {kWorkWebPageProperty, PR_BUSINESS_HOME_PAGE_A}, + {kHomeWebPageProperty, PR_PERSONAL_HOME_PAGE_A}, + // For the moment, we don't support querying on the birthday + // sub-elements. +#if 0 + {kBirthYearProperty, PR_BIRTHDAY}, + {kBirthMonthProperty, PR_BIRTHDAY}, + {kBirthDayProperty, PR_BIRTHDAY}, +#endif // 0 + {kNotesProperty, PR_COMMENT_A} +} ; + +static const uint32_t OutlookTableNbProps = sizeof(OutlookTableStringToProp) / + sizeof(OutlookTableStringToProp [0]) ; + +static ULONG findPropertyTag(const char *aName) { + uint32_t i = 0 ; + + for (i = 0 ; i < OutlookTableNbProps ; ++ i) { + if (strcmp(aName, OutlookTableStringToProp [i].mOuterName) == 0) { + return OutlookTableStringToProp [i].mMapiProp ; + } + } + return 0 ; +} + +static nsresult BuildRestriction(nsIAbBooleanConditionString *aCondition, + SRestriction& aRestriction, + bool& aSkipItem) +{ + if (!aCondition) { return NS_ERROR_NULL_POINTER ; } + aSkipItem = false ; + nsAbBooleanConditionType conditionType = 0 ; + nsresult retCode = NS_OK ; + nsCString name; + nsString value; + ULONG propertyTag = 0 ; + nsAutoCString valueAscii ; + + retCode = aCondition->GetCondition(&conditionType) ; + NS_ENSURE_SUCCESS(retCode, retCode) ; + retCode = aCondition->GetName(getter_Copies(name)) ; + NS_ENSURE_SUCCESS(retCode, retCode) ; + retCode = aCondition->GetValue(getter_Copies(value)) ; + NS_ENSURE_SUCCESS(retCode, retCode) ; + LossyCopyUTF16toASCII(value, valueAscii); + propertyTag = findPropertyTag(name.get()) ; + if (propertyTag == 0) { + aSkipItem = true ; + return retCode ; + } + switch (conditionType) { + case nsIAbBooleanConditionTypes::Exists : + aRestriction.rt = RES_EXIST ; + aRestriction.res.resExist.ulPropTag = propertyTag ; + break ; + case nsIAbBooleanConditionTypes::DoesNotExist : + aRestriction.rt = RES_NOT ; + aRestriction.res.resNot.lpRes = new SRestriction ; + aRestriction.res.resNot.lpRes->rt = RES_EXIST ; + aRestriction.res.resNot.lpRes->res.resExist.ulPropTag = propertyTag ; + break ; + case nsIAbBooleanConditionTypes::Contains : + aRestriction.rt = RES_CONTENT ; + aRestriction.res.resContent.ulFuzzyLevel = FL_SUBSTRING | FL_LOOSE ; + aRestriction.res.resContent.ulPropTag = propertyTag ; + aRestriction.res.resContent.lpProp = new SPropValue ; + aRestriction.res.resContent.lpProp->ulPropTag = propertyTag ; + aRestriction.res.resContent.lpProp->Value.lpszA = strdup(valueAscii.get()) ; + break ; + case nsIAbBooleanConditionTypes::DoesNotContain : + aRestriction.rt = RES_NOT ; + aRestriction.res.resNot.lpRes = new SRestriction ; + aRestriction.res.resNot.lpRes->rt = RES_CONTENT ; + aRestriction.res.resNot.lpRes->res.resContent.ulFuzzyLevel = FL_SUBSTRING | FL_LOOSE ; + aRestriction.res.resNot.lpRes->res.resContent.ulPropTag = propertyTag ; + aRestriction.res.resNot.lpRes->res.resContent.lpProp = new SPropValue ; + aRestriction.res.resNot.lpRes->res.resContent.lpProp->ulPropTag = propertyTag ; + aRestriction.res.resNot.lpRes->res.resContent.lpProp->Value.lpszA = strdup(valueAscii.get()) ; + break ; + case nsIAbBooleanConditionTypes::Is : + aRestriction.rt = RES_CONTENT ; + aRestriction.res.resContent.ulFuzzyLevel = FL_FULLSTRING | FL_LOOSE ; + aRestriction.res.resContent.ulPropTag = propertyTag ; + aRestriction.res.resContent.lpProp = new SPropValue ; + aRestriction.res.resContent.lpProp->ulPropTag = propertyTag ; + aRestriction.res.resContent.lpProp->Value.lpszA = strdup(valueAscii.get()) ; + break ; + case nsIAbBooleanConditionTypes::IsNot : + aRestriction.rt = RES_NOT ; + aRestriction.res.resNot.lpRes = new SRestriction ; + aRestriction.res.resNot.lpRes->rt = RES_CONTENT ; + aRestriction.res.resNot.lpRes->res.resContent.ulFuzzyLevel = FL_FULLSTRING | FL_LOOSE ; + aRestriction.res.resNot.lpRes->res.resContent.ulPropTag = propertyTag ; + aRestriction.res.resNot.lpRes->res.resContent.lpProp = new SPropValue ; + aRestriction.res.resNot.lpRes->res.resContent.lpProp->ulPropTag = propertyTag ; + aRestriction.res.resNot.lpRes->res.resContent.lpProp->Value.lpszA = strdup(valueAscii.get()) ; + break ; + case nsIAbBooleanConditionTypes::BeginsWith : + aRestriction.rt = RES_CONTENT ; + aRestriction.res.resContent.ulFuzzyLevel = FL_PREFIX | FL_LOOSE ; + aRestriction.res.resContent.ulPropTag = propertyTag ; + aRestriction.res.resContent.lpProp = new SPropValue ; + aRestriction.res.resContent.lpProp->ulPropTag = propertyTag ; + aRestriction.res.resContent.lpProp->Value.lpszA = strdup(valueAscii.get()) ; + break ; + case nsIAbBooleanConditionTypes::EndsWith : + // This condition should be implemented through regular expressions, + // but MAPI doesn't match them correctly. +#if 0 + aRestriction.rt = RES_PROPERTY ; + aRestriction.res.resProperty.relop = RELOP_RE ; + aRestriction.res.resProperty.ulPropTag = propertyTag ; + aRestriction.res.resProperty.lpProp = new SPropValue ; + aRestriction.res.resProperty.lpProp->ulPropTag = propertyTag ; + aRestriction.res.resProperty.lpProp->Value.lpszA = strdup(valueAscii.get()) ; +#else + aSkipItem = true ; +#endif // 0 + break ; + case nsIAbBooleanConditionTypes::SoundsLike : + // This condition cannot be implemented in MAPI. + aSkipItem = true ; + break ; + case nsIAbBooleanConditionTypes::RegExp : + // This condition should be implemented this way, but the following + // code will never match (through MAPI's fault). +#if 0 + aRestriction.rt = RES_PROPERTY ; + aRestriction.res.resProperty.relop = RELOP_RE ; + aRestriction.res.resProperty.ulPropTag = propertyTag ; + aRestriction.res.resProperty.lpProp = new SPropValue ; + aRestriction.res.resProperty.lpProp->ulPropTag = propertyTag ; + aRestriction.res.resProperty.lpProp->Value.lpszA = strdup(valueAscii.get()) ; +#else + aSkipItem = true ; +#endif // 0 + break ; + case nsIAbBooleanConditionTypes::LessThan : + aRestriction.rt = RES_PROPERTY ; + aRestriction.res.resProperty.relop = RELOP_LT ; + aRestriction.res.resProperty.ulPropTag = propertyTag ; + aRestriction.res.resProperty.lpProp = new SPropValue ; + aRestriction.res.resProperty.lpProp->ulPropTag = propertyTag ; + aRestriction.res.resProperty.lpProp->Value.lpszA = strdup(valueAscii.get()) ; + break ; + case nsIAbBooleanConditionTypes::GreaterThan : + aRestriction.rt = RES_PROPERTY ; + aRestriction.res.resProperty.relop = RELOP_GT ; + aRestriction.res.resProperty.ulPropTag = propertyTag ; + aRestriction.res.resProperty.lpProp = new SPropValue ; + aRestriction.res.resProperty.lpProp->ulPropTag = propertyTag ; + aRestriction.res.resProperty.lpProp->Value.lpszA = strdup(valueAscii.get()) ; + break ; + default : + aSkipItem = true ; + break ; + } + return retCode ; +} + +static nsresult BuildRestriction(nsIAbBooleanExpression *aLevel, + SRestriction& aRestriction) +{ + if (!aLevel) { return NS_ERROR_NULL_POINTER ; } + aRestriction.rt = RES_COMMENT ; + nsresult retCode = NS_OK ; + nsAbBooleanOperationType operationType = 0 ; + uint32_t nbExpressions = 0 ; + nsCOMPtr<nsIArray> expressions; + + retCode = aLevel->GetOperation(&operationType); + NS_ENSURE_SUCCESS(retCode, retCode); + retCode = aLevel->GetExpressions(getter_AddRefs(expressions)); + NS_ENSURE_SUCCESS(retCode, retCode); + retCode = expressions->GetLength(&nbExpressions); + NS_ENSURE_SUCCESS(retCode, retCode); + if (nbExpressions == 0) { + PRINTF(("Error, no expressions.\n")) ; + return NS_OK ; + } + if (operationType == nsIAbBooleanOperationTypes::NOT && nbExpressions != 1) { + PRINTF(("Error, unary operation NOT with multiple operands.\n")) ; + return NS_OK ; + } + LPSRestriction restrictionArray = new SRestriction [nbExpressions] ; + uint32_t realNbExpressions = 0 ; + bool skipItem = false ; + uint32_t i = 0 ; + + nsCOMPtr<nsIAbBooleanConditionString> condition; + nsCOMPtr<nsIAbBooleanExpression> subExpression; + + for (i = 0; i < nbExpressions; ++i) { + condition = do_QueryElementAt(expressions, i, &retCode); + + if (NS_SUCCEEDED(retCode)) { + retCode = BuildRestriction(condition, *restrictionArray, skipItem); + if (NS_SUCCEEDED(retCode)) { + if (!skipItem) { + ++restrictionArray; + ++realNbExpressions; + } + } + else + PRINTF(("Cannot build restriction for item %d %08x.\n", i, retCode)); + } + else { + subExpression = do_QueryElementAt(expressions, i, &retCode); + + if (NS_SUCCEEDED(retCode)) { + retCode = BuildRestriction(subExpression, *restrictionArray); + if (NS_SUCCEEDED(retCode)) { + if (restrictionArray->rt != RES_COMMENT) { + ++restrictionArray; + ++realNbExpressions; + } + } + } + else + PRINTF(("Cannot get interface for item %d %08x.\n", i, retCode)); + } + } + + restrictionArray -= realNbExpressions ; + if (realNbExpressions > 1) { + if (operationType == nsIAbBooleanOperationTypes::OR) { + aRestriction.rt = RES_OR ; + aRestriction.res.resOr.lpRes = restrictionArray ; + aRestriction.res.resOr.cRes = realNbExpressions ; + } + else if (operationType == nsIAbBooleanOperationTypes::AND) { + aRestriction.rt = RES_AND ; + aRestriction.res.resAnd.lpRes = restrictionArray ; + aRestriction.res.resAnd.cRes = realNbExpressions ; + } + else { + PRINTF(("Unsupported operation %d.\n", operationType)) ; + } + } + else if (realNbExpressions == 1) { + if (operationType == nsIAbBooleanOperationTypes::NOT) { + aRestriction.rt = RES_NOT ; + // This copy is to ensure that every NOT restriction is being + // allocated by new and not new[] (see destruction of restriction) + aRestriction.res.resNot.lpRes = new SRestriction ; + memcpy(aRestriction.res.resNot.lpRes, restrictionArray, sizeof(SRestriction)) ; + } + else { + // Case where the restriction array is redundant, + // we need to fill the restriction directly. + memcpy(&aRestriction, restrictionArray, sizeof(SRestriction)) ; + } + delete [] restrictionArray ; + } + if (aRestriction.rt == RES_COMMENT) { + // This means we haven't really built any useful expression + delete [] restrictionArray ; + } + return NS_OK ; +} + +static nsresult BuildRestriction(nsIAbDirectoryQueryArguments *aArguments, + SRestriction& aRestriction) +{ + if (!aArguments) { return NS_ERROR_NULL_POINTER ; } + nsresult retCode = NS_OK ; + + nsCOMPtr<nsISupports> supports ; + retCode = aArguments->GetExpression(getter_AddRefs(supports)) ; + NS_ENSURE_SUCCESS(retCode, retCode) ; + nsCOMPtr<nsIAbBooleanExpression> booleanQuery = + do_QueryInterface(supports, &retCode) ; + NS_ENSURE_SUCCESS(retCode, retCode) ; + retCode = BuildRestriction(booleanQuery, aRestriction) ; + return retCode ; +} + +static void DestroyRestriction(SRestriction& aRestriction) +{ + switch(aRestriction.rt) { + case RES_AND : + case RES_OR : + { + for (ULONG i = 0 ; i < aRestriction.res.resAnd.cRes ; ++ i) { + DestroyRestriction(aRestriction.res.resAnd.lpRes [i]) ; + } + delete [] aRestriction.res.resAnd.lpRes ; + } + break ; + case RES_COMMENT : + break ; + case RES_CONTENT : + if (PROP_TYPE(aRestriction.res.resContent.ulPropTag) == PT_UNICODE) { + NS_Free(aRestriction.res.resContent.lpProp->Value.lpszW) ; + } + else if (PROP_TYPE(aRestriction.res.resContent.ulPropTag) == PT_STRING8) { + NS_Free(aRestriction.res.resContent.lpProp->Value.lpszA) ; + } + delete aRestriction.res.resContent.lpProp ; + break ; + case RES_EXIST : + break ; + case RES_NOT : + DestroyRestriction(*aRestriction.res.resNot.lpRes) ; + delete aRestriction.res.resNot.lpRes ; + break ; + case RES_BITMASK : + case RES_COMPAREPROPS : + break ; + case RES_PROPERTY : + if (PROP_TYPE(aRestriction.res.resProperty.ulPropTag) == PT_UNICODE) { + NS_Free(aRestriction.res.resProperty.lpProp->Value.lpszW) ; + } + else if (PROP_TYPE(aRestriction.res.resProperty.ulPropTag) == PT_STRING8) { + NS_Free(aRestriction.res.resProperty.lpProp->Value.lpszA) ; + } + delete aRestriction.res.resProperty.lpProp ; + case RES_SIZE : + case RES_SUBRESTRICTION : + break ; + } +} + +struct QueryThreadArgs +{ + nsAbOutlookDirectory *mThis ; + SRestriction mRestriction ; + nsCOMPtr<nsIAbDirSearchListener> mListener ; + int32_t mResultLimit ; + int32_t mTimeout ; + int32_t mThreadId ; +} ; + +static void QueryThreadFunc(void *aArguments) +{ + QueryThreadArgs *arguments = reinterpret_cast<QueryThreadArgs *>(aArguments) ; + + if (!aArguments) { return ; } + arguments->mThis->ExecuteQuery(arguments->mRestriction, arguments->mListener, + arguments->mResultLimit, arguments->mTimeout, + arguments->mThreadId) ; + DestroyRestriction(arguments->mRestriction) ; + delete arguments ; +} + +NS_IMETHODIMP nsAbOutlookDirectory::DoQuery(nsIAbDirectory *aDirectory, + nsIAbDirectoryQueryArguments *aArguments, + nsIAbDirSearchListener *aListener, + int32_t aResultLimit, int32_t aTimeout, + int32_t *aReturnValue) +{ + if (!aArguments || !aListener || !aReturnValue) { + return NS_ERROR_NULL_POINTER; + } + *aReturnValue = -1; + + QueryThreadArgs *threadArgs = new QueryThreadArgs; + PRThread *newThread = nullptr; + + if (!threadArgs) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv = BuildRestriction(aArguments, threadArgs->mRestriction); + NS_ENSURE_SUCCESS(rv, rv); + + threadArgs->mThis = this; + threadArgs->mListener = aListener; + threadArgs->mResultLimit = aResultLimit; + threadArgs->mTimeout = aTimeout; + + PR_Lock(mProtector); + *aReturnValue = ++mCurrentQueryId; + PR_Unlock(mProtector); + + threadArgs->mThreadId = *aReturnValue; + newThread = PR_CreateThread(PR_USER_THREAD, + QueryThreadFunc, + threadArgs, + PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, + PR_UNJOINABLE_THREAD, + 0); + + if (!newThread ) { + DestroyRestriction(threadArgs->mRestriction); + delete threadArgs; + return NS_ERROR_OUT_OF_MEMORY; + } + + mQueryThreads.Put(*aReturnValue, newThread); + return NS_OK; +} + +NS_IMETHODIMP nsAbOutlookDirectory::StopQuery(int32_t aContext) +{ + PRThread *queryThread; + if (mQueryThreads.Get(aContext, &queryThread)) { + PR_Interrupt(queryThread); + mQueryThreads.Remove(aContext); + } + return NS_OK; +} + +// nsIAbDirectorySearch methods +NS_IMETHODIMP nsAbOutlookDirectory::StartSearch(void) +{ + if (!mIsQueryURI) { return NS_ERROR_NOT_IMPLEMENTED ; } + nsresult retCode = NS_OK ; + + retCode = StopSearch() ; + NS_ENSURE_SUCCESS(retCode, retCode) ; + mCardList.Clear(); + + nsCOMPtr<nsIAbBooleanExpression> expression ; + + nsCOMPtr<nsIAbDirectoryQueryArguments> arguments = do_CreateInstance(NS_ABDIRECTORYQUERYARGUMENTS_CONTRACTID,&retCode); + NS_ENSURE_SUCCESS(retCode, retCode); + + retCode = nsAbQueryStringToExpression::Convert(mQueryString, getter_AddRefs(expression)) ; + NS_ENSURE_SUCCESS(retCode, retCode) ; + retCode = arguments->SetExpression(expression) ; + NS_ENSURE_SUCCESS(retCode, retCode) ; + + retCode = arguments->SetQuerySubDirectories(true) ; + NS_ENSURE_SUCCESS(retCode, retCode) ; + + return DoQuery(this, arguments, this, -1, 0, &mSearchContext); +} + +NS_IMETHODIMP nsAbOutlookDirectory::StopSearch(void) +{ + if (!mIsQueryURI) { return NS_ERROR_NOT_IMPLEMENTED ; } + return StopQuery(mSearchContext) ; +} + +// nsIAbDirSearchListener +NS_IMETHODIMP nsAbOutlookDirectory::OnSearchFinished(int32_t aResult, + const nsAString &aErrorMsg) +{ + return NS_OK; +} + +NS_IMETHODIMP nsAbOutlookDirectory::OnSearchFoundCard(nsIAbCard *aCard) +{ + mCardList.Put(aCard, aCard); + nsresult rv; + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) + rv = abManager->NotifyDirectoryItemAdded(this, aCard); + + return rv; +} + +nsresult nsAbOutlookDirectory::ExecuteQuery(SRestriction &aRestriction, + nsIAbDirSearchListener *aListener, + int32_t aResultLimit, int32_t aTimeout, + int32_t aThreadId) + +{ + if (!aListener) + return NS_ERROR_NULL_POINTER; + + nsresult retCode = NS_OK; + + nsCOMPtr<nsIMutableArray> resultsArray(do_CreateInstance(NS_ARRAY_CONTRACTID, + &retCode)); + NS_ENSURE_SUCCESS(retCode, retCode); + + retCode = GetChildCards(resultsArray, + aRestriction.rt == RES_COMMENT ? nullptr : &aRestriction); + NS_ENSURE_SUCCESS(retCode, retCode); + + uint32_t nbResults = 0; + retCode = resultsArray->GetLength(&nbResults); + NS_ENSURE_SUCCESS(retCode, retCode); + + if (aResultLimit > 0 && nbResults > static_cast<uint32_t>(aResultLimit)) { + nbResults = static_cast<uint32_t>(aResultLimit) ; + } + + uint32_t i = 0; + nsCOMPtr<nsIAbCard> card; + + for (i = 0 ; i < nbResults ; ++ i) { + card = do_QueryElementAt(resultsArray, i, &retCode); + NS_ENSURE_SUCCESS(retCode, retCode); + + aListener->OnSearchFoundCard(card); + } + + mQueryThreads.Remove(aThreadId); + + aListener->OnSearchFinished(nsIAbDirectoryQueryResultListener::queryResultComplete, + EmptyString()); + return retCode; +} + +// This function expects the aCards array to already be created. +nsresult nsAbOutlookDirectory::GetChildCards(nsIMutableArray *aCards, + void *aRestriction) +{ + nsAbWinHelperGuard mapiAddBook(mAbWinType); + + if (!mapiAddBook->IsOK()) + return NS_ERROR_FAILURE; + + nsMapiEntryArray cardEntries; + LPSRestriction restriction = (LPSRestriction) aRestriction; + + if (!mapiAddBook->GetCards(*mMapiData, restriction, cardEntries)) { + PRINTF(("Cannot get cards.\n")); + return NS_ERROR_FAILURE; + } + + nsAutoCString ourUuid; + GetUuid(ourUuid); + + nsAutoCString entryId; + nsAutoCString uriName; + nsCOMPtr<nsIAbCard> childCard; + nsresult rv; + + for (ULONG card = 0; card < cardEntries.mNbEntries; ++card) { + cardEntries.mEntries[card].ToString(entryId); + buildAbWinUri(kOutlookCardScheme, mAbWinType, uriName); + uriName.Append(entryId); + + rv = OutlookCardForURI(uriName, getter_AddRefs(childCard)); + NS_ENSURE_SUCCESS(rv, rv); + childCard->SetDirectoryId(ourUuid); + + aCards->AppendElement(childCard, false); + } + return rv; +} + +nsresult nsAbOutlookDirectory::GetChildNodes(nsIMutableArray* aNodes) +{ + NS_ENSURE_ARG_POINTER(aNodes); + + aNodes->Clear(); + + nsAbWinHelperGuard mapiAddBook(mAbWinType); + nsMapiEntryArray nodeEntries; + + if (!mapiAddBook->IsOK()) + return NS_ERROR_FAILURE; + + if (!mapiAddBook->GetNodes(*mMapiData, nodeEntries)) + { + PRINTF(("Cannot get nodes.\n")); + return NS_ERROR_FAILURE; + } + + nsAutoCString entryId; + nsAutoCString uriName; + nsresult rv = NS_OK; + + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + for (ULONG node = 0; node < nodeEntries.mNbEntries; ++node) + { + nodeEntries.mEntries[node].ToString(entryId); + buildAbWinUri(kOutlookDirectoryScheme, mAbWinType, uriName); + uriName.Append(entryId); + + nsCOMPtr <nsIAbDirectory> directory; + rv = abManager->GetDirectory(uriName, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + aNodes->AppendElement(directory, false); + } + return rv; +} + +nsresult nsAbOutlookDirectory::NotifyItemDeletion(nsISupports *aItem) +{ + nsresult rv; + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv)); + + if (NS_SUCCEEDED(rv)) + rv = abManager->NotifyDirectoryItemDeleted(this, aItem); + + return rv; +} + +nsresult nsAbOutlookDirectory::NotifyItemAddition(nsISupports *aItem) +{ + nsresult rv; + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + rv = abManager->NotifyDirectoryItemAdded(this, aItem); + + return rv; +} + +// This is called from EditMailListToDatabase. +// We got m_AddressList containing the list of cards the mailing +// list is supposed to contain at the end. +nsresult nsAbOutlookDirectory::CommitAddressList(void) +{ + if (!m_IsMailList) { + PRINTF(("We are not in a mailing list, no commit can be done.\n")); + return NS_ERROR_UNEXPECTED; + } + + nsresult rv; + uint32_t i = 0; + nsCOMPtr<nsIMutableArray> oldList(do_CreateInstance(NS_ARRAY_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetChildCards(oldList, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + if (!m_AddressList) + return NS_ERROR_NULL_POINTER; + + uint32_t nbCards = 0; + rv = m_AddressList->GetLength(&nbCards); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupports> element; + nsCOMPtr<nsIAbCard> newCard; + uint32_t pos; + + for (i = 0; i < nbCards; ++i) { + element = do_QueryElementAt(m_AddressList, i, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (NS_SUCCEEDED(oldList->IndexOf(0, element, &pos))) { + rv = oldList->RemoveElementAt(pos); + NS_ENSURE_SUCCESS(rv, rv); + + // The entry was not already there + nsCOMPtr<nsIAbCard> card(do_QueryInterface(element, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = CreateCard(card, getter_AddRefs(newCard)); + NS_ENSURE_SUCCESS(rv, rv); + m_AddressList->ReplaceElementAt(newCard, i, false); + } + } + return DeleteCards(oldList); +} + +nsresult nsAbOutlookDirectory::UpdateAddressList(void) +{ + if (!m_AddressList) + { + nsresult rv; + m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } + + return m_IsMailList ? GetChildCards(m_AddressList, nullptr) : + GetChildNodes(m_AddressList); +} + +nsresult nsAbOutlookDirectory::CreateCard(nsIAbCard *aData, nsIAbCard **aNewCard) +{ + if (!aData || !aNewCard) { return NS_ERROR_NULL_POINTER ; } + *aNewCard = nullptr ; + nsresult retCode = NS_OK ; + nsAbWinHelperGuard mapiAddBook (mAbWinType) ; + nsMapiEntry newEntry ; + nsAutoCString entryString ; + bool didCopy = false ; + + if (!mapiAddBook->IsOK()) { return NS_ERROR_FAILURE ; } + // If we get an nsIAbCard that maps onto an Outlook card uri + // we simply copy the contents of the Outlook card. + retCode = ExtractCardEntry(aData, entryString) ; + if (NS_SUCCEEDED(retCode) && !entryString.IsEmpty()) { + nsMapiEntry sourceEntry ; + + + sourceEntry.Assign(entryString) ; + if (m_IsMailList) { + // In the case of a mailing list, we can use the address + // as a direct template to build the new one (which is done + // by CopyEntry). + mapiAddBook->CopyEntry(*mMapiData, sourceEntry, newEntry) ; + didCopy = true ; + } + else { + // Else, we have to create a temporary address and copy the + // source into it. Yes it's silly. + mapiAddBook->CreateEntry(*mMapiData, newEntry) ; + } + } + // If this approach doesn't work, well we're back to creating and copying. + if (newEntry.mByteCount == 0) { + // In the case of a mailing list, we cannot directly create a new card, + // we have to create a temporary one in a real folder (to be able to use + // templates) and then copy it to the mailing list. + if (m_IsMailList) { + nsMapiEntry parentEntry ; + nsMapiEntry temporaryEntry ; + + if (!mapiAddBook->GetDefaultContainer(parentEntry)) { + return NS_ERROR_FAILURE ; + } + if (!mapiAddBook->CreateEntry(parentEntry, temporaryEntry)) { + return NS_ERROR_FAILURE ; + } + if (!mapiAddBook->CopyEntry(*mMapiData, temporaryEntry, newEntry)) { + return NS_ERROR_FAILURE ; + } + if (!mapiAddBook->DeleteEntry(parentEntry, temporaryEntry)) { + return NS_ERROR_FAILURE ; + } + } + // If we're on a real address book folder, we can directly create an + // empty card. + else if (!mapiAddBook->CreateEntry(*mMapiData, newEntry)) { + return NS_ERROR_FAILURE ; + } + } + newEntry.ToString(entryString) ; + nsAutoCString uri ; + + buildAbWinUri(kOutlookCardScheme, mAbWinType, uri) ; + uri.Append(entryString) ; + + nsCOMPtr<nsIAbCard> newCard; + retCode = OutlookCardForURI(uri, getter_AddRefs(newCard)); + NS_ENSURE_SUCCESS(retCode, retCode); + + nsAutoCString ourUuid; + GetUuid(ourUuid); + newCard->SetDirectoryId(ourUuid); + + if (!didCopy) { + retCode = newCard->Copy(aData) ; + NS_ENSURE_SUCCESS(retCode, retCode) ; + retCode = ModifyCard(newCard) ; + NS_ENSURE_SUCCESS(retCode, retCode) ; + } + *aNewCard = newCard ; + NS_ADDREF(*aNewCard) ; + return retCode ; +} + +static void UnicodeToWord(const char16_t *aUnicode, WORD& aWord) +{ + aWord = 0 ; + if (aUnicode == nullptr || *aUnicode == 0) { return ; } + nsresult errorCode = NS_OK; + nsAutoString unichar (aUnicode) ; + + aWord = static_cast<WORD>(unichar.ToInteger(&errorCode)); + if (NS_FAILED(errorCode)) { + PRINTF(("Error conversion string %S: %08x.\n", unichar.get(), errorCode)) ; + } +} + +#define PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST "mail.addr_book.lastnamefirst" + + +NS_IMETHODIMP nsAbOutlookDirectory::ModifyCard(nsIAbCard *aModifiedCard) +{ + NS_ENSURE_ARG_POINTER(aModifiedCard); + + nsString *properties = nullptr; + nsAutoString utility; + nsAbWinHelperGuard mapiAddBook(mAbWinType); + + if (!mapiAddBook->IsOK()) + return NS_ERROR_FAILURE; + + nsCString entry; + nsresult retCode = ExtractCardEntry(aModifiedCard, entry); + NS_ENSURE_SUCCESS(retCode, retCode); + // If we don't have the card entry, we can't work. + if (entry.IsEmpty()) + return NS_ERROR_FAILURE; + + nsMapiEntry mapiData; + mapiData.Assign(entry); + + // First, all the standard properties in one go + properties = new nsString[index_LastProp]; + if (!properties) { + return NS_ERROR_OUT_OF_MEMORY; + } + aModifiedCard->GetFirstName(properties[index_FirstName]); + aModifiedCard->GetLastName(properties[index_LastName]); + // This triple search for something to put in the name + // is because in the case of a mailing list edition in + // Mozilla, the display name will not be provided, and + // MAPI doesn't allow that, so we fall back on an optional + // name, and when all fails, on the email address. + aModifiedCard->GetDisplayName(properties[index_DisplayName]); + if (properties[index_DisplayName].IsEmpty()) { + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranch = + do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + int32_t format; + rv = prefBranch->GetIntPref(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, &format); + NS_ENSURE_SUCCESS(rv,rv); + + rv = aModifiedCard->GenerateName(format, nullptr, + properties[index_DisplayName]); + NS_ENSURE_SUCCESS(rv,rv); + + if (properties[index_DisplayName].IsEmpty()) { + aModifiedCard->GetPrimaryEmail(properties[index_DisplayName]); + } + } + aModifiedCard->SetDisplayName(properties[index_DisplayName]); + aModifiedCard->GetPrimaryEmail(properties[index_EmailAddress]); + aModifiedCard->GetPropertyAsAString(kNicknameProperty, properties[index_NickName]); + aModifiedCard->GetPropertyAsAString(kWorkPhoneProperty, properties[index_WorkPhoneNumber]); + aModifiedCard->GetPropertyAsAString(kHomePhoneProperty, properties[index_HomePhoneNumber]); + aModifiedCard->GetPropertyAsAString(kFaxProperty, properties[index_WorkFaxNumber]); + aModifiedCard->GetPropertyAsAString(kPagerProperty, properties[index_PagerNumber]); + aModifiedCard->GetPropertyAsAString(kCellularProperty, properties[index_MobileNumber]); + aModifiedCard->GetPropertyAsAString(kHomeCityProperty, properties[index_HomeCity]); + aModifiedCard->GetPropertyAsAString(kHomeStateProperty, properties[index_HomeState]); + aModifiedCard->GetPropertyAsAString(kHomeZipCodeProperty, properties[index_HomeZip]); + aModifiedCard->GetPropertyAsAString(kHomeCountryProperty, properties[index_HomeCountry]); + aModifiedCard->GetPropertyAsAString(kWorkCityProperty, properties[index_WorkCity]); + aModifiedCard->GetPropertyAsAString(kWorkStateProperty, properties[index_WorkState]); + aModifiedCard->GetPropertyAsAString(kWorkZipCodeProperty, properties[index_WorkZip]); + aModifiedCard->GetPropertyAsAString(kWorkCountryProperty, properties[index_WorkCountry]); + aModifiedCard->GetPropertyAsAString(kJobTitleProperty, properties[index_JobTitle]); + aModifiedCard->GetPropertyAsAString(kDepartmentProperty, properties[index_Department]); + aModifiedCard->GetPropertyAsAString(kCompanyProperty, properties[index_Company]); + aModifiedCard->GetPropertyAsAString(kWorkWebPageProperty, properties[index_WorkWebPage]); + aModifiedCard->GetPropertyAsAString(kHomeWebPageProperty, properties[index_HomeWebPage]); + aModifiedCard->GetPropertyAsAString(kNotesProperty, properties[index_Comments]); + if (!mapiAddBook->SetPropertiesUString(mapiData, OutlookCardMAPIProps, + index_LastProp, properties)) { + PRINTF(("Cannot set general properties.\n")) ; + } + + delete [] properties; + nsString unichar; + nsString unichar2; + WORD year = 0; + WORD month = 0; + WORD day = 0; + + aModifiedCard->GetPropertyAsAString(kHomeAddressProperty, unichar); + aModifiedCard->GetPropertyAsAString(kHomeAddress2Property, unichar2); + + utility.Assign(unichar.get()); + if (!utility.IsEmpty()) + utility.AppendLiteral("\r\n"); + + utility.Append(unichar2.get()); + if (!mapiAddBook->SetPropertyUString(mapiData, PR_HOME_ADDRESS_STREET_W, utility.get())) { + PRINTF(("Cannot set home address.\n")) ; + } + + unichar.Truncate(); + aModifiedCard->GetPropertyAsAString(kWorkAddressProperty, unichar); + unichar2.Truncate(); + aModifiedCard->GetPropertyAsAString(kWorkAddress2Property, unichar2); + + utility.Assign(unichar.get()); + if (!utility.IsEmpty()) + utility.AppendLiteral("\r\n"); + + utility.Append(unichar2.get()); + if (!mapiAddBook->SetPropertyUString(mapiData, PR_BUSINESS_ADDRESS_STREET_W, utility.get())) { + PRINTF(("Cannot set work address.\n")) ; + } + + unichar.Truncate(); + aModifiedCard->GetPropertyAsAString(kBirthYearProperty, unichar); + UnicodeToWord(unichar.get(), year); + unichar.Truncate(); + aModifiedCard->GetPropertyAsAString(kBirthMonthProperty, unichar); + UnicodeToWord(unichar.get(), month); + unichar.Truncate(); + aModifiedCard->GetPropertyAsAString(kBirthDayProperty, unichar); + UnicodeToWord(unichar.get(), day); + if (!mapiAddBook->SetPropertyDate(mapiData, PR_BIRTHDAY, year, month, day)) { + PRINTF(("Cannot set date.\n")) ; + } + + return retCode; +} + +NS_IMETHODIMP nsAbOutlookDirectory::OnQueryFoundCard(nsIAbCard *aCard) +{ + return OnSearchFoundCard(aCard); +} + +NS_IMETHODIMP nsAbOutlookDirectory::OnQueryResult(int32_t aResult, + int32_t aErrorCode) +{ + return OnSearchFinished(aResult, EmptyString()); +} + +NS_IMETHODIMP nsAbOutlookDirectory::UseForAutocomplete(const nsACString &aIdentityKey, bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = false; + return NS_OK; +} + +static void splitString(nsString& aSource, nsString& aTarget) +{ + aTarget.Truncate(); + int32_t offset = aSource.FindChar('\n'); + + if (offset >= 0) + { + const char16_t *source = aSource.get() + offset + 1; + while (*source) + { + if (*source == '\n' || *source == '\r') + aTarget.Append(char16_t(' ')); + else + aTarget.Append(*source); + ++source; + } + aSource.SetLength(offset); + } +} + +nsresult OutlookCardForURI(const nsACString &aUri, nsIAbCard **newCard) +{ + NS_ENSURE_ARG_POINTER(newCard); + + nsAutoCString entry; + nsAutoCString stub; + uint32_t abWinType = getAbWinType(kOutlookCardScheme, + PromiseFlatCString(aUri).get(), stub, entry); + if (abWinType == nsAbWinType_Unknown) + { + PRINTF(("Huge problem URI=%s.\n", PromiseFlatCString(aUri).get())); + return NS_ERROR_INVALID_ARG; + } + + nsAbWinHelperGuard mapiAddBook(abWinType); + if (!mapiAddBook->IsOK()) + return NS_ERROR_FAILURE; + + nsresult rv; + nsCOMPtr<nsIAbCard> card = do_CreateInstance(NS_ABCARDPROPERTY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + card->SetPropertyAsAUTF8String("OutlookEntryURI", aUri); + card->SetLocalId(aUri); + + nsMapiEntry mapiData; + mapiData.Assign(entry); + + nsString unichars[index_LastProp]; + + if (mapiAddBook->GetPropertiesUString(mapiData, OutlookCardMAPIProps, + index_LastProp, unichars)) + { + card->SetFirstName(unichars[index_FirstName]); + card->SetLastName(unichars[index_LastName]); + card->SetDisplayName(unichars[index_DisplayName]); + card->SetPrimaryEmail(unichars[index_EmailAddress]); + card->SetPropertyAsAString(kNicknameProperty, unichars[index_NickName]); + card->SetPropertyAsAString(kWorkPhoneProperty, unichars[index_WorkPhoneNumber]); + card->SetPropertyAsAString(kHomePhoneProperty, unichars[index_HomePhoneNumber]); + card->SetPropertyAsAString(kFaxProperty, unichars[index_WorkFaxNumber]); + card->SetPropertyAsAString(kPagerProperty, unichars[index_PagerNumber]); + card->SetPropertyAsAString(kCellularProperty, unichars[index_MobileNumber]); + card->SetPropertyAsAString(kHomeCityProperty, unichars[index_HomeCity]); + card->SetPropertyAsAString(kHomeStateProperty, unichars[index_HomeState]); + card->SetPropertyAsAString(kHomeZipCodeProperty, unichars[index_HomeZip]); + card->SetPropertyAsAString(kHomeCountryProperty, unichars[index_HomeCountry]); + card->SetPropertyAsAString(kWorkCityProperty, unichars[index_WorkCity]); + card->SetPropertyAsAString(kWorkStateProperty, unichars[index_WorkState]); + card->SetPropertyAsAString(kWorkZipCodeProperty, unichars[index_WorkZip]); + card->SetPropertyAsAString(kWorkCountryProperty, unichars[index_WorkCountry]); + card->SetPropertyAsAString(kJobTitleProperty, unichars[index_JobTitle]); + card->SetPropertyAsAString(kDepartmentProperty, unichars[index_Department]); + card->SetPropertyAsAString(kCompanyProperty, unichars[index_Company]); + card->SetPropertyAsAString(kWorkWebPageProperty, unichars[index_WorkWebPage]); + card->SetPropertyAsAString(kHomeWebPageProperty, unichars[index_HomeWebPage]); + card->SetPropertyAsAString(kNotesProperty, unichars[index_Comments]); + } + + ULONG cardType = 0; + if (mapiAddBook->GetPropertyLong(mapiData, PR_OBJECT_TYPE, cardType)) + { + card->SetIsMailList(cardType == MAPI_DISTLIST); + if (cardType == MAPI_DISTLIST) + { + nsAutoCString normalChars; + buildAbWinUri(kOutlookDirectoryScheme, abWinType, normalChars); + normalChars.Append(entry); + card->SetMailListURI(normalChars.get()); + } + } + + nsAutoString unichar; + nsAutoString unicharBis; + if (mapiAddBook->GetPropertyUString(mapiData, PR_HOME_ADDRESS_STREET_W, unichar)) + { + splitString(unichar, unicharBis); + card->SetPropertyAsAString(kHomeAddressProperty, unichar); + card->SetPropertyAsAString(kHomeAddress2Property, unicharBis); + } + if (mapiAddBook->GetPropertyUString(mapiData, PR_BUSINESS_ADDRESS_STREET_W, + unichar)) + { + splitString(unichar, unicharBis); + card->SetPropertyAsAString(kWorkAddressProperty, unichar); + card->SetPropertyAsAString(kWorkAddress2Property, unicharBis); + } + + WORD year = 0, month = 0, day = 0; + if (mapiAddBook->GetPropertyDate(mapiData, PR_BIRTHDAY, year, month, day)) + { + card->SetPropertyAsUint32(kBirthYearProperty, year); + card->SetPropertyAsUint32(kBirthMonthProperty, month); + card->SetPropertyAsUint32(kBirthDayProperty, day); + } + + card.swap(*newCard); + return NS_OK; +} diff --git a/mailnews/addrbook/src/nsAbOutlookDirectory.h b/mailnews/addrbook/src/nsAbOutlookDirectory.h new file mode 100644 index 000000000..116966ce3 --- /dev/null +++ b/mailnews/addrbook/src/nsAbOutlookDirectory.h @@ -0,0 +1,152 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ +#ifndef nsAbOutlookDirectory_h___ +#define nsAbOutlookDirectory_h___ + +#include "mozilla/Attributes.h" +#include "nsAbDirProperty.h" +#include "nsIAbDirectoryQuery.h" +#include "nsIAbDirectorySearch.h" +#include "nsIAbDirSearchListener.h" +#include "nsDataHashtable.h" +#include "nsInterfaceHashtable.h" +#include "nsIMutableArray.h" +#include "nsAbWinHelper.h" + +struct nsMapiEntry ; + +class nsAbOutlookDirectory : public nsAbDirProperty, // nsIAbDirectory + public nsIAbDirectoryQuery, + public nsIAbDirectorySearch, + public nsIAbDirSearchListener, + public nsIAbDirectoryQueryResultListener +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIABDIRSEARCHLISTENER + NS_DECL_NSIABDIRECTORYQUERYRESULTLISTENER + + nsAbOutlookDirectory(void); + + // nsAbDirProperty methods + NS_IMETHOD GetDirType(int32_t *aDirType) override; + NS_IMETHOD GetURI(nsACString &aURI) override; + NS_IMETHOD GetChildCards(nsISimpleEnumerator **aCards) override; + NS_IMETHOD GetChildNodes(nsISimpleEnumerator **aNodes) override; + NS_IMETHOD GetIsQuery(bool *aResult) override; + NS_IMETHOD HasCard(nsIAbCard *aCard, bool *aHasCard) override; + NS_IMETHOD HasDirectory(nsIAbDirectory *aDirectory, bool *aHasDirectory) override; + NS_IMETHOD DeleteCards(nsIArray *aCardList) override; + NS_IMETHOD DeleteDirectory(nsIAbDirectory *aDirectory) override; + NS_IMETHOD UseForAutocomplete(const nsACString &aIdentityKey, bool *aResult) override; + NS_IMETHOD AddCard(nsIAbCard *aData, nsIAbCard **addedCard) override; + NS_IMETHOD ModifyCard(nsIAbCard *aModifiedCard) override; + NS_IMETHOD DropCard(nsIAbCard *aData, bool needToCopyCard) override; + NS_IMETHOD AddMailList(nsIAbDirectory *aMailList, nsIAbDirectory **addedList) override; + NS_IMETHOD EditMailListToDatabase(nsIAbCard *listCard) override; + + // nsAbDirProperty method + NS_IMETHOD Init(const char *aUri) override; + // nsIAbDirectoryQuery methods + NS_DECL_NSIABDIRECTORYQUERY + // nsIAbDirectorySearch methods + NS_DECL_NSIABDIRECTORYSEARCH + // Perform a MAPI query (function executed in a separate thread) + nsresult ExecuteQuery(SRestriction &aRestriction, + nsIAbDirSearchListener *aListener, + int32_t aResultLimit, int32_t aTimeout, + int32_t aThreadId); + +protected: + // Retrieve hierarchy as cards, with an optional restriction + nsresult GetChildCards(nsIMutableArray *aCards, void *aRestriction); + // Retrieve hierarchy as directories + nsresult GetChildNodes(nsIMutableArray *aNodes); + // Create a new card + nsresult CreateCard(nsIAbCard *aData, nsIAbCard **aNewCard); + // Notification for the UI + nsresult NotifyItemDeletion(nsISupports *aItem); + nsresult NotifyItemAddition(nsISupports *aItem); + // Force update of MAPI repository for mailing list + nsresult CommitAddressList(void); + // Read MAPI repository + nsresult UpdateAddressList(void); + + nsMapiEntry *mMapiData; + // Container for the query threads + nsDataHashtable<nsUint32HashKey, PRThread*> mQueryThreads; + int32_t mCurrentQueryId; + PRLock *mProtector; + // Data for the search interfaces + nsInterfaceHashtable<nsISupportsHashKey, nsIAbCard> mCardList; + int32_t mSearchContext; + // Windows AB type + uint32_t mAbWinType; + +private: + virtual ~nsAbOutlookDirectory(void); + +}; + +enum +{ + index_DisplayName = 0, + index_EmailAddress, + index_FirstName, + index_LastName, + index_NickName, + index_WorkPhoneNumber, + index_HomePhoneNumber, + index_WorkFaxNumber, + index_PagerNumber, + index_MobileNumber, + index_HomeCity, + index_HomeState, + index_HomeZip, + index_HomeCountry, + index_WorkCity, + index_WorkState, + index_WorkZip, + index_WorkCountry, + index_JobTitle, + index_Department, + index_Company, + index_WorkWebPage, + index_HomeWebPage, + index_Comments, + index_LastProp +}; + +static const ULONG OutlookCardMAPIProps[] = +{ + PR_DISPLAY_NAME_W, + PR_EMAIL_ADDRESS_W, + PR_GIVEN_NAME_W, + PR_SURNAME_W, + PR_NICKNAME_W, + PR_BUSINESS_TELEPHONE_NUMBER_W, + PR_HOME_TELEPHONE_NUMBER_W, + PR_BUSINESS_FAX_NUMBER_W, + PR_PAGER_TELEPHONE_NUMBER_W, + PR_MOBILE_TELEPHONE_NUMBER_W, + PR_HOME_ADDRESS_CITY_W, + PR_HOME_ADDRESS_STATE_OR_PROVINCE_W, + PR_HOME_ADDRESS_POSTAL_CODE_W, + PR_HOME_ADDRESS_COUNTRY_W, + PR_BUSINESS_ADDRESS_CITY_W, + PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_W, + PR_BUSINESS_ADDRESS_POSTAL_CODE_W, + PR_BUSINESS_ADDRESS_COUNTRY_W, + PR_TITLE_W, + PR_DEPARTMENT_NAME_W, + PR_COMPANY_NAME_W, + PR_BUSINESS_HOME_PAGE_W, + PR_PERSONAL_HOME_PAGE_W, + PR_COMMENT_W +}; + +nsresult OutlookCardForURI(const nsACString &aUri, nsIAbCard **card); + +#endif // nsAbOutlookDirectory_h___ diff --git a/mailnews/addrbook/src/nsAbQueryStringToExpression.cpp b/mailnews/addrbook/src/nsAbQueryStringToExpression.cpp new file mode 100644 index 000000000..fe1f22e00 --- /dev/null +++ b/mailnews/addrbook/src/nsAbQueryStringToExpression.cpp @@ -0,0 +1,337 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#include "nsAbQueryStringToExpression.h" + +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsStringGlue.h" +#include "nsITextToSubURI.h" +#include "nsAbBooleanExpression.h" +#include "nsAbBaseCID.h" +#include "plstr.h" +#include "nsIMutableArray.h" + +/** + * This code parses the query expression passed in as an addressbook URI. + * The expression takes the form: + * (BOOL1(FIELD1,OP1,VALUE1)..(FIELDn,OPn,VALUEn)(BOOL2(FIELD1,OP1,VALUE1)...)...) + * + * BOOLn A boolean operator joining subsequent terms delimited by (). + * For possible values see CreateBooleanExpression(). + * FIELDn An addressbook card data field. + * OPn An operator for the search term. + * For possible values see CreateBooleanConditionString(). + * VALUEn The value to be matched in the FIELDn via the OPn operator. + * The value must be URL encoded by the caller, if it contains any special + * characters including '(' and ')'. + */ +nsresult nsAbQueryStringToExpression::Convert ( + const nsACString &aQueryString, + nsIAbBooleanExpression** expression) +{ + nsresult rv; + + nsAutoCString q(aQueryString); + q.StripWhitespace(); + const char *queryChars = q.get(); + + nsCOMPtr<nsISupports> s; + rv = ParseExpression(&queryChars, getter_AddRefs(s)); + NS_ENSURE_SUCCESS(rv, rv); + + // Case: Not end of string + if (*queryChars != 0) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIAbBooleanExpression> e(do_QueryInterface(s, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*expression = e); + return rv; +} + +nsresult nsAbQueryStringToExpression::ParseExpression ( + const char** index, + nsISupports** expression) +{ + nsresult rv; + + if (**index != '(') + return NS_ERROR_FAILURE; + + const char* indexBracket = *index + 1; + while (*indexBracket && + *indexBracket != '(' && *indexBracket != ')') + indexBracket++; + + // Case: End of string + if (*indexBracket == 0) + return NS_ERROR_FAILURE; + + // Case: "((" or "()" + if (indexBracket == *index + 1) + { + return NS_ERROR_FAILURE; + } + // Case: "(*(" + else if (*indexBracket == '(') + { + // printf ("Case: (*(: %s\n", *index); + + nsCString operation; + rv = ParseOperationEntry ( + *index, indexBracket, + getter_Copies (operation)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbBooleanExpression> e; + rv = CreateBooleanExpression(operation.get(), + getter_AddRefs(e)); + NS_ENSURE_SUCCESS(rv, rv); + + // Case: "(*)(*)....(*))" + *index = indexBracket; + rv = ParseExpressions (index, e); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*expression = e); + } + // Case" "(*)" + else if (*indexBracket == ')') + { + // printf ("Case: (*): %s\n", *index); + + nsCOMPtr<nsIAbBooleanConditionString> conditionString; + rv = ParseCondition (index, indexBracket, + getter_AddRefs(conditionString)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*expression = conditionString); + } + + if (**index != ')') + return NS_ERROR_FAILURE; + + (*index)++; + + return NS_OK; +} + + +nsresult nsAbQueryStringToExpression::ParseExpressions ( + const char** index, + nsIAbBooleanExpression* expression) +{ + nsresult rv; + nsCOMPtr<nsIMutableArray> expressions(do_CreateInstance(NS_ARRAY_CONTRACTID, + &rv)); + if (NS_FAILED(rv)) + return NS_ERROR_OUT_OF_MEMORY; + + // Case: ")(*)(*)....(*))" + // printf ("Case: )(*)(*)....(*)): %s\n", *index); + while (**index == '(') + { + nsCOMPtr<nsISupports> childExpression; + rv = ParseExpression(index, getter_AddRefs (childExpression)); + NS_ENSURE_SUCCESS(rv, rv); + + expressions->AppendElement(childExpression, false); + } + + if (**index == 0) + return NS_ERROR_FAILURE; + + // Case: "))" + // printf ("Case: )): %s\n", *index); + + if (**index != ')') + return NS_ERROR_FAILURE; + + expression->SetExpressions (expressions); + + return NS_OK; +} + +nsresult nsAbQueryStringToExpression::ParseCondition ( + const char** index, + const char* indexBracketClose, + nsIAbBooleanConditionString** conditionString) +{ + nsresult rv; + + (*index)++; + + nsCString entries[3]; + for (int i = 0; i < 3; i++) + { + rv = ParseConditionEntry (index, indexBracketClose, + getter_Copies (entries[i])); + NS_ENSURE_SUCCESS(rv, rv); + + if (*index == indexBracketClose) + break; + } + + if (*index != indexBracketClose) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIAbBooleanConditionString> c; + rv = CreateBooleanConditionString ( + entries[0].get(), + entries[1].get(), + entries[2].get(), + getter_AddRefs (c)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*conditionString = c); + return NS_OK; +} + +nsresult nsAbQueryStringToExpression::ParseConditionEntry ( + const char** index, + const char* indexBracketClose, + char** entry) +{ + const char* indexDeliminator = *index; + while (indexDeliminator != indexBracketClose && + *indexDeliminator != ',') + indexDeliminator++; + + int entryLength = indexDeliminator - *index; + if (entryLength) + *entry = PL_strndup (*index, entryLength); + else + *entry = 0; + + if (indexDeliminator != indexBracketClose) + *index = indexDeliminator + 1; + else + *index = indexDeliminator; + + return NS_OK; +} + +nsresult nsAbQueryStringToExpression::ParseOperationEntry ( + const char* indexBracketOpen1, + const char* indexBracketOpen2, + char** operation) +{ + int operationLength = indexBracketOpen2 - indexBracketOpen1 - 1; + if (operationLength) + *operation = PL_strndup (indexBracketOpen1 + 1, + operationLength); + else + *operation = 0; + + return NS_OK; +} + +nsresult nsAbQueryStringToExpression::CreateBooleanExpression( + const char* operation, + nsIAbBooleanExpression** expression) +{ + nsAbBooleanOperationType op; + if (PL_strcasecmp (operation, "and") == 0) + op = nsIAbBooleanOperationTypes::AND; + else if (PL_strcasecmp (operation, "or") == 0) + op = nsIAbBooleanOperationTypes::OR; + else if (PL_strcasecmp (operation, "not") == 0) + op = nsIAbBooleanOperationTypes::NOT; + else + return NS_ERROR_FAILURE; + + nsresult rv; + + nsCOMPtr <nsIAbBooleanExpression> expr = do_CreateInstance(NS_BOOLEANEXPRESSION_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + NS_IF_ADDREF(*expression = expr); + + rv = expr->SetOperation (op); + return rv; +} + +nsresult nsAbQueryStringToExpression::CreateBooleanConditionString ( + const char* attribute, + const char* condition, + const char* value, + nsIAbBooleanConditionString** conditionString) +{ + if (attribute == 0 || condition == 0 || value == 0) + return NS_ERROR_FAILURE; + + nsAbBooleanConditionType c; + + if (PL_strcasecmp (condition, "=") == 0) + c = nsIAbBooleanConditionTypes::Is; + else if (PL_strcasecmp (condition, "!=") == 0) + c = nsIAbBooleanConditionTypes::IsNot; + else if (PL_strcasecmp (condition, "lt") == 0) + c = nsIAbBooleanConditionTypes::LessThan; + else if (PL_strcasecmp (condition, "gt") == 0) + c = nsIAbBooleanConditionTypes::GreaterThan; + else if (PL_strcasecmp (condition, "bw") == 0) + c = nsIAbBooleanConditionTypes::BeginsWith; + else if (PL_strcasecmp (condition, "ew") == 0) + c = nsIAbBooleanConditionTypes::EndsWith; + else if (PL_strcasecmp (condition, "c")== 0) + c = nsIAbBooleanConditionTypes::Contains; + else if (PL_strcasecmp (condition, "!c") == 0) + c = nsIAbBooleanConditionTypes::DoesNotContain; + else if (PL_strcasecmp (condition, "~=") == 0) + c = nsIAbBooleanConditionTypes::SoundsLike; + else if (PL_strcasecmp (condition, "regex") == 0) + c = nsIAbBooleanConditionTypes::RegExp; + else + return NS_ERROR_FAILURE; + + nsresult rv; + + nsCOMPtr<nsIAbBooleanConditionString> cs = do_CreateInstance(NS_BOOLEANCONDITIONSTRING_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = cs->SetCondition (c); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsITextToSubURI> textToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID,&rv); + if (NS_SUCCEEDED(rv)) + { + nsString attributeUCS2; + nsString valueUCS2; + + rv = textToSubURI->UnEscapeAndConvert("UTF-8", + attribute, getter_Copies(attributeUCS2)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = textToSubURI->UnEscapeAndConvert("UTF-8", + value, getter_Copies(valueUCS2)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ConvertUTF16toUTF8 attributeUTF8(attributeUCS2); + + rv = cs->SetName (attributeUTF8.get ()); + NS_ENSURE_SUCCESS(rv, rv); + rv = cs->SetValue(valueUCS2.get()); + NS_ENSURE_SUCCESS(rv, rv); + } + else + { + NS_ConvertUTF8toUTF16 valueUCS2(value); + + rv = cs->SetName (attribute); + NS_ENSURE_SUCCESS(rv, rv); + rv = cs->SetValue (valueUCS2.get ()); + NS_ENSURE_SUCCESS(rv, rv); + } + + + NS_IF_ADDREF(*conditionString = cs); + return NS_OK; +} + + diff --git a/mailnews/addrbook/src/nsAbQueryStringToExpression.h b/mailnews/addrbook/src/nsAbQueryStringToExpression.h new file mode 100644 index 000000000..dfd8da0c5 --- /dev/null +++ b/mailnews/addrbook/src/nsAbQueryStringToExpression.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsAbQueryStringToExpression_h__ +#define nsAbQueryStringToExpression_h__ + +#include "nsIAbBooleanExpression.h" + +class nsAbQueryStringToExpression +{ +public: + static nsresult Convert ( + const nsACString &aQueryString, + nsIAbBooleanExpression** expression); + +protected: + static nsresult ParseExpression ( + const char** index, + nsISupports** expression); + static nsresult ParseExpressions ( + const char** index, + nsIAbBooleanExpression* expression); + static nsresult ParseCondition ( + const char** index, + const char* indexBracketClose, + nsIAbBooleanConditionString** conditionString); + + static nsresult ParseConditionEntry ( + const char** index, + const char* indexBracketClose, + char** entry); + static nsresult ParseOperationEntry ( + const char* indexBracketOpen1, + const char* indexBracketOpen2, + char** operation); + + static nsresult CreateBooleanExpression( + const char* operation, + nsIAbBooleanExpression** expression); + static nsresult CreateBooleanConditionString ( + const char* attribute, + const char* condition, + const char* value, + nsIAbBooleanConditionString** conditionString); +}; + +#endif diff --git a/mailnews/addrbook/src/nsAbUtils.h b/mailnews/addrbook/src/nsAbUtils.h new file mode 100644 index 000000000..d6b8915e0 --- /dev/null +++ b/mailnews/addrbook/src/nsAbUtils.h @@ -0,0 +1,140 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsAbUtils_h__ +#define nsAbUtils_h__ + +#include "nsMemory.h" + +/* + * Wrapper class to automatically free an array of + * char* when class goes out of scope + */ +class CharPtrArrayGuard +{ +public: + CharPtrArrayGuard (bool freeElements = true) : + mFreeElements (freeElements), + mArray (0), + mSize (0) + { + } + + ~CharPtrArrayGuard () + { + Free (); + } + + char* operator[](int i) + { + return mArray[i]; + } + + uint32_t* GetSizeAddr(void) + { + return &mSize; + } + + uint32_t GetSize(void) + { + return mSize; + } + + char*** GetArrayAddr(void) + { + return &mArray; + } + + const char** GetArray(void) + { + return (const char** ) mArray; + } + +public: + +private: + bool mFreeElements; + char **mArray; + uint32_t mSize; + + void Free () + { + if (!mArray) + return; + + if (mFreeElements) + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mSize, mArray); + else + { + free(mArray); + } + } +}; + +/* + * Wrapper class to automatically free an array of + * char16_t* when class goes out of scope + */ +class PRUnicharPtrArrayGuard +{ +public: + PRUnicharPtrArrayGuard (bool freeElements = true) : + mFreeElements (freeElements), + mArray (0), + mSize (0) + { + } + + ~PRUnicharPtrArrayGuard () + { + Free (); + } + + char16_t* operator[](int i) + { + return mArray[i]; + } + + uint32_t* GetSizeAddr(void) + { + return &mSize; + } + + uint32_t GetSize(void) + { + return mSize; + } + + char16_t*** GetArrayAddr(void) + { + return &mArray; + } + + const char16_t** GetArray(void) + { + return (const char16_t** ) mArray; + } + +public: + +private: + bool mFreeElements; + char16_t **mArray; + uint32_t mSize; + void Free () + { + if (!mArray) + return; + + if (mFreeElements) + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mSize, mArray); + else + { + free(mArray); + } + } +}; + +#endif /* nsAbUtils_h__ */ diff --git a/mailnews/addrbook/src/nsAbView.cpp b/mailnews/addrbook/src/nsAbView.cpp new file mode 100644 index 000000000..77f3122df --- /dev/null +++ b/mailnews/addrbook/src/nsAbView.cpp @@ -0,0 +1,1451 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "mozilla/DebugOnly.h" + +#include "nsAbView.h" +#include "nsISupports.h" +#include "nsCOMPtr.h" +#include "nsIServiceManager.h" +#include "nsIAbCard.h" +#include "nsILocale.h" +#include "nsILocaleService.h" +#include "prmem.h" +#include "nsCollationCID.h" +#include "nsIAbManager.h" +#include "nsAbBaseCID.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsITreeColumns.h" +#include "nsCRTGlue.h" +#include "nsIMutableArray.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIStringBundle.h" +#include "nsIPrefLocalizedString.h" +#include "nsArrayUtils.h" +#include "nsIAddrDatabase.h" // for kPriEmailColumn +#include "nsMsgUtils.h" +#include "mozilla/Services.h" + +using namespace mozilla; + +#define CARD_NOT_FOUND -1 +#define ALL_ROWS -1 + +#define PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST "mail.addr_book.lastnamefirst" +#define PREF_MAIL_ADDR_BOOK_DISPLAYNAME_AUTOGENERATION "mail.addr_book.displayName.autoGeneration" +#define PREF_MAIL_ADDR_BOOK_DISPLAYNAME_LASTNAMEFIRST "mail.addr_book.displayName.lastnamefirst" + +// Also, our default primary sort +#define GENERATED_NAME_COLUMN_ID "GeneratedName" + +NS_IMPL_ISUPPORTS(nsAbView, nsIAbView, nsITreeView, nsIAbListener, nsIObserver) + +nsAbView::nsAbView() : mInitialized(false), + mIsAllDirectoryRootView(false), + mSuppressSelectionChange(false), + mSuppressCountChange(false), + mGeneratedNameFormat(0) +{ +} + +nsAbView::~nsAbView() +{ + if (mInitialized) { + NS_ASSERTION(NS_SUCCEEDED(ClearView()), "failed to close view"); + } +} + +NS_IMETHODIMP nsAbView::ClearView() +{ + mDirectory = nullptr; + mAbViewListener = nullptr; + if (mTree) + mTree->SetView(nullptr); + mTree = nullptr; + mTreeSelection = nullptr; + + if (mInitialized) + { + nsresult rv; + mInitialized = false; + nsCOMPtr<nsIPrefBranch> pbi(do_GetService(NS_PREFSERVICE_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = pbi->RemoveObserver(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, this); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = abManager->RemoveAddressBookListener(this); + NS_ENSURE_SUCCESS(rv, rv); + } + + int32_t i = mCards.Length(); + while(i-- > 0) + NS_ASSERTION(NS_SUCCEEDED(RemoveCardAt(i)), "remove card failed\n"); + + return NS_OK; +} + +nsresult nsAbView::RemoveCardAt(int32_t row) +{ + nsresult rv; + + AbCard *abcard = mCards.ElementAt(row); + NS_IF_RELEASE(abcard->card); + mCards.RemoveElementAt(row); + PR_FREEIF(abcard->primaryCollationKey); + PR_FREEIF(abcard->secondaryCollationKey); + PR_FREEIF(abcard); + + + // This needs to happen after we remove the card, as RowCountChanged() will call GetRowCount() + if (mTree) { + rv = mTree->RowCountChanged(row, -1); + NS_ENSURE_SUCCESS(rv,rv); + } + + if (mAbViewListener && !mSuppressCountChange) { + rv = mAbViewListener->OnCountChanged(mCards.Length()); + NS_ENSURE_SUCCESS(rv,rv); + } + return NS_OK; +} + +nsresult nsAbView::SetGeneratedNameFormatFromPrefs() +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefBranchInt(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv,rv); + + return prefBranchInt->GetIntPref(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, &mGeneratedNameFormat); +} + +nsresult nsAbView::Initialize() +{ + if (mInitialized) + return NS_OK; + + mInitialized = true; + + nsresult rv; + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = abManager->AddAddressBookListener(this, nsIAbListener::all); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrefBranch> pbi(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = pbi->AddObserver(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST, this, false); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mABBundle) + { + nsCOMPtr<nsIStringBundleService> stringBundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(stringBundleService, NS_ERROR_UNEXPECTED); + + rv = stringBundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", getter_AddRefs(mABBundle)); + NS_ENSURE_SUCCESS(rv, rv); + } + + return SetGeneratedNameFormatFromPrefs(); +} + +NS_IMETHODIMP nsAbView::SetView(nsIAbDirectory *aAddressBook, + nsIAbViewListener *aAbViewListener, + const nsAString &aSortColumn, + const nsAString &aSortDirection, + nsAString &aResult) +{ + // Ensure we are initialized + nsresult rv = Initialize(); + + mAbViewListener = nullptr; + if (mTree) + { + // Try and speed deletion of old cards by disconnecting the tree from us. + mTreeSelection->ClearSelection(); + mTree->SetView(nullptr); + } + + // Clear out old cards + int32_t i = mCards.Length(); + while(i-- > 0) + { + rv = RemoveCardAt(i); + NS_ASSERTION(NS_SUCCEEDED(rv), "remove card failed\n"); + } + + // We replace all cards so any sorting is no longer valid. + mSortColumn.AssignLiteral(""); + mSortDirection.AssignLiteral(""); + + nsCString uri; + aAddressBook->GetURI(uri); + int32_t searchBegin = uri.FindChar('?'); + nsCString searchQuery(Substring(uri, searchBegin)); + // This is a special case, a workaround basically, to just have all ABs. + if (searchQuery.EqualsLiteral("?")) { + searchQuery.AssignLiteral(""); + } + + if (Substring(uri, 0, searchBegin).EqualsLiteral(kAllDirectoryRoot)) { + mIsAllDirectoryRootView = true; + // We have special request case to search all addressbooks, so we need + // to iterate over all addressbooks. + // Since the request is for all addressbooks, the URI must have been + // passed with an extra '?'. We still check it for sanity and trim it here. + if (searchQuery.Find("??") == 0) + searchQuery = Substring(searchQuery, 1); + + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = abManager->GetDirectories(getter_AddRefs(enumerator)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore = false; + nsCOMPtr<nsISupports> support; + nsCOMPtr<nsIAbDirectory> directory; + while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) { + rv = enumerator->GetNext(getter_AddRefs(support)); + NS_ENSURE_SUCCESS(rv, rv); + directory = do_QueryInterface(support, &rv); + + // If, for some reason, we are unable to get a directory, we continue. + if (NS_FAILED(rv)) + continue; + + // Get appropriate directory with search query. + nsCString uri; + directory->GetURI(uri); + rv = abManager->GetDirectory(uri + searchQuery, getter_AddRefs(directory)); + mDirectory = directory; + rv = EnumerateCards(); + NS_ENSURE_SUCCESS(rv, rv); + } + } else { + mIsAllDirectoryRootView = false; + mDirectory = aAddressBook; + rv = EnumerateCards(); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_NAMED_LITERAL_STRING(generatedNameColumnId, GENERATED_NAME_COLUMN_ID); + + // See if the persisted sortColumn is valid. + // It may not be, if you migrated from older versions, or switched between + // a mozilla build and a commercial build, which have different columns. + nsAutoString actualSortColumn; + if (!generatedNameColumnId.Equals(aSortColumn) && mCards.Length()) { + nsIAbCard *card = mCards.ElementAt(0)->card; + nsString value; + // XXX todo + // Need to check if _Generic is valid. GetCardValue() will always return NS_OK for _Generic + // We're going to have to ask mDirectory if it is. + // It might not be. example: _ScreenName is valid in Netscape, but not Mozilla. + rv = GetCardValue(card, PromiseFlatString(aSortColumn).get(), value); + if (NS_FAILED(rv)) + actualSortColumn = generatedNameColumnId; + else + actualSortColumn = aSortColumn; + } + else + actualSortColumn = aSortColumn; + + rv = SortBy(actualSortColumn.get(), PromiseFlatString(aSortDirection).get(), false); + NS_ENSURE_SUCCESS(rv, rv); + + mAbViewListener = aAbViewListener; + if (mAbViewListener && !mSuppressCountChange) { + rv = mAbViewListener->OnCountChanged(mCards.Length()); + NS_ENSURE_SUCCESS(rv, rv); + } + + aResult = actualSortColumn; + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetDirectory(nsIAbDirectory **aDirectory) +{ + NS_ENSURE_ARG_POINTER(aDirectory); + NS_IF_ADDREF(*aDirectory = mDirectory); + return NS_OK; +} + +nsresult nsAbView::EnumerateCards() +{ + nsresult rv; + nsCOMPtr<nsISimpleEnumerator> cardsEnumerator; + nsCOMPtr<nsIAbCard> card; + + if (!mDirectory) + return NS_ERROR_UNEXPECTED; + + rv = mDirectory->GetChildCards(getter_AddRefs(cardsEnumerator)); + if (NS_SUCCEEDED(rv) && cardsEnumerator) + { + nsCOMPtr<nsISupports> item; + bool more; + while (NS_SUCCEEDED(cardsEnumerator->HasMoreElements(&more)) && more) + { + rv = cardsEnumerator->GetNext(getter_AddRefs(item)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr <nsIAbCard> card = do_QueryInterface(item); + // Malloc these from an arena + AbCard *abcard = (AbCard *) PR_Calloc(1, sizeof(struct AbCard)); + if (!abcard) + return NS_ERROR_OUT_OF_MEMORY; + + abcard->card = card; + NS_IF_ADDREF(abcard->card); + + // XXX todo + // Would it be better to do an insertion sort, than append and sort? + // XXX todo + // If we knew how many cards there was going to be + // we could allocate an array of the size, + // instead of growing and copying as we append. + DebugOnly<bool> didAppend = mCards.AppendElement(abcard); + NS_ASSERTION(didAppend, "failed to append card"); + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetRowCount(int32_t *aRowCount) +{ + *aRowCount = mCards.Length(); + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetSelection(nsITreeSelection * *aSelection) +{ + NS_IF_ADDREF(*aSelection = mTreeSelection); + return NS_OK; +} + +NS_IMETHODIMP nsAbView::SetSelection(nsITreeSelection * aSelection) +{ + mTreeSelection = aSelection; + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetRowProperties(int32_t index, nsAString& properties) +{ + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetCellProperties(int32_t row, nsITreeColumn* col, nsAString& properties) +{ + NS_ENSURE_TRUE(row >= 0, NS_ERROR_UNEXPECTED); + + if (mCards.Length() <= (size_t)row) + return NS_OK; + + const char16_t* colID; + col->GetIdConst(&colID); + // "G" == "GeneratedName" + if (colID[0] != char16_t('G')) + return NS_OK; + + nsIAbCard *card = mCards.ElementAt(row)->card; + + bool isMailList; + nsresult rv = card->GetIsMailList(&isMailList); + NS_ENSURE_SUCCESS(rv,rv); + + if (isMailList) + properties.AssignLiteral("MailList"); + + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetColumnProperties(nsITreeColumn* col, nsAString& properties) +{ + return NS_OK; +} + +NS_IMETHODIMP nsAbView::IsContainer(int32_t index, bool *_retval) +{ + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP nsAbView::IsContainerOpen(int32_t index, bool *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::IsContainerEmpty(int32_t index, bool *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::IsSeparator(int32_t index, bool *_retval) +{ + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP nsAbView::IsSorted(bool *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::CanDrop(int32_t index, + int32_t orientation, + nsIDOMDataTransfer *dataTransfer, + bool *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::Drop(int32_t row, + int32_t orientation, + nsIDOMDataTransfer *dataTransfer) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::GetParentIndex(int32_t rowIndex, int32_t *_retval) +{ + *_retval = -1; + return NS_OK; +} + +NS_IMETHODIMP nsAbView::HasNextSibling(int32_t rowIndex, int32_t afterIndex, bool *_retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::GetLevel(int32_t index, int32_t *_retval) +{ + *_retval = 0; + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetImageSrc(int32_t row, nsITreeColumn* col, nsAString& _retval) +{ + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetProgressMode(int32_t row, nsITreeColumn* col, int32_t* _retval) +{ + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetCellValue(int32_t row, nsITreeColumn* col, nsAString& _retval) +{ + return NS_OK; +} + +nsresult nsAbView::GetCardValue(nsIAbCard *card, const char16_t *colID, + nsAString &_retval) +{ + if (nsString(colID).EqualsLiteral("addrbook")) { + nsCString dirID; + nsresult rv = card->GetDirectoryId(dirID); + if (NS_SUCCEEDED(rv)) + CopyUTF8toUTF16(Substring(dirID, dirID.FindChar('&') + 1), _retval); + + return rv; + } + + // "G" == "GeneratedName", "_P" == "_PhoneticName" + // else, standard column (like PrimaryEmail and _AimScreenName) + if (colID[0] == char16_t('G')) + return card->GenerateName(mGeneratedNameFormat, mABBundle, _retval); + + if (colID[0] == char16_t('_') && colID[1] == char16_t('P')) + // Use LN/FN order for the phonetic name + return card->GeneratePhoneticName(true, _retval); + + if (!NS_strcmp(colID, u"ChatName")) + return card->GenerateChatName(_retval); + + nsresult rv = card->GetPropertyAsAString(NS_ConvertUTF16toUTF8(colID).get(), _retval); + if (rv == NS_ERROR_NOT_AVAILABLE) { + rv = NS_OK; + _retval.Truncate(); + } + return rv; +} + +nsresult nsAbView::RefreshTree() +{ + nsresult rv; + + // The PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST pref affects how the GeneratedName column looks. + // so if the GeneratedName is our primary or secondary sort, + // we need to resort. + // the same applies for kPhoneticNameColumn + // + // XXX optimize me + // PrimaryEmail is always the secondary sort, unless it is currently the + // primary sort. So, if PrimaryEmail is the primary sort, + // GeneratedName might be the secondary sort. + // + // One day, we can get fancy and remember what the secondary sort is. + // We do that, we can fix this code. At best, it will turn a sort into a invalidate. + // + // If neither the primary nor the secondary sorts are GeneratedName (or kPhoneticNameColumn), + // all we have to do is invalidate (to show the new GeneratedNames), + // but the sort will not change. + if (mSortColumn.EqualsLiteral(GENERATED_NAME_COLUMN_ID) || + mSortColumn.EqualsLiteral(kPriEmailProperty) || + mSortColumn.EqualsLiteral(kPhoneticNameColumn)) { + rv = SortBy(mSortColumn.get(), mSortDirection.get(), true); + } + else { + rv = InvalidateTree(ALL_ROWS); + + // Although the selection hasn't changed, the card that is selected may need + // to be displayed differently, therefore pretend that the selection has + // changed to force that update. + SelectionChanged(); + } + + return rv; +} + +NS_IMETHODIMP nsAbView::GetCellText(int32_t row, nsITreeColumn* col, nsAString& _retval) +{ + NS_ENSURE_TRUE(row >= 0 && (size_t)row < mCards.Length(), NS_ERROR_UNEXPECTED); + + nsIAbCard *card = mCards.ElementAt(row)->card; + const char16_t* colID; + col->GetIdConst(&colID); + return GetCardValue(card, colID, _retval); +} + +NS_IMETHODIMP nsAbView::SetTree(nsITreeBoxObject *tree) +{ + mTree = tree; + return NS_OK; +} + +NS_IMETHODIMP nsAbView::ToggleOpenState(int32_t index) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::CycleHeader(nsITreeColumn* col) +{ + return NS_OK; +} + +nsresult nsAbView::InvalidateTree(int32_t row) +{ + if (!mTree) + return NS_OK; + + if (row == ALL_ROWS) + return mTree->Invalidate(); + else + return mTree->InvalidateRow(row); +} + +NS_IMETHODIMP nsAbView::SelectionChanged() +{ + if (mAbViewListener && !mSuppressSelectionChange) { + nsresult rv = mAbViewListener->OnSelectionChanged(); + NS_ENSURE_SUCCESS(rv,rv); + } + return NS_OK; +} + +NS_IMETHODIMP nsAbView::CycleCell(int32_t row, nsITreeColumn* col) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::IsEditable(int32_t row, nsITreeColumn* col, bool* _retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::IsSelectable(int32_t row, nsITreeColumn* col, bool* _retval) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::SetCellValue(int32_t row, nsITreeColumn* col, const nsAString& value) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::SetCellText(int32_t row, nsITreeColumn* col, const nsAString& value) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::PerformAction(const char16_t *action) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::PerformActionOnRow(const char16_t *action, int32_t row) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::PerformActionOnCell(const char16_t *action, int32_t row, nsITreeColumn* col) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP nsAbView::GetCardFromRow(int32_t row, nsIAbCard **aCard) +{ + *aCard = nullptr; + NS_ENSURE_TRUE(row >= 0, NS_ERROR_UNEXPECTED); + if (mCards.Length() <= (size_t)row) { + return NS_OK; + } + + AbCard *a = mCards.ElementAt(row); + if (!a) + return NS_OK; + + NS_IF_ADDREF(*aCard = a->card); + return NS_OK; +} + +#define DESCENDING_SORT_FACTOR -1 +#define ASCENDING_SORT_FACTOR 1 + +typedef struct SortClosure +{ + const char16_t *colID; + int32_t factor; + nsAbView *abView; +} SortClosure; + +static int +inplaceSortCallback(const AbCard *card1, const AbCard *card2, SortClosure *closure) +{ + int32_t sortValue; + + // If we are sorting the "PrimaryEmail", swap the collation keys, as the secondary is always the + // PrimaryEmail. Use the last primary key as the secondary key. + // + // "Pr" to distinguish "PrimaryEmail" from "PagerNumber" + if (closure->colID[0] == char16_t('P') && closure->colID[1] == char16_t('r')) { + sortValue = closure->abView->CompareCollationKeys(card1->secondaryCollationKey,card1->secondaryCollationKeyLen,card2->secondaryCollationKey,card2->secondaryCollationKeyLen); + if (sortValue) + return sortValue * closure->factor; + else + return closure->abView->CompareCollationKeys(card1->primaryCollationKey,card1->primaryCollationKeyLen,card2->primaryCollationKey,card2->primaryCollationKeyLen) * (closure->factor); + } + else { + sortValue = closure->abView->CompareCollationKeys(card1->primaryCollationKey,card1->primaryCollationKeyLen,card2->primaryCollationKey,card2->primaryCollationKeyLen); + if (sortValue) + return sortValue * (closure->factor); + else + return closure->abView->CompareCollationKeys(card1->secondaryCollationKey,card1->secondaryCollationKeyLen,card2->secondaryCollationKey,card2->secondaryCollationKeyLen) * (closure->factor); + } +} + +static void SetSortClosure(const char16_t *sortColumn, const char16_t *sortDirection, nsAbView *abView, SortClosure *closure) +{ + closure->colID = sortColumn; + + if (sortDirection && !NS_strcmp(sortDirection, u"descending")) + closure->factor = DESCENDING_SORT_FACTOR; + else + closure->factor = ASCENDING_SORT_FACTOR; + + closure->abView = abView; + return; +} + +class CardComparator +{ +public: + void SetClosure(SortClosure *closure) { m_closure = closure; }; + + bool Equals(const AbCard *a, const AbCard *b) const { + return inplaceSortCallback(a, b, m_closure) == 0; + } + bool LessThan(const AbCard *a, const AbCard *b) const{ + return inplaceSortCallback(a, b, m_closure) < 0; + } + +private: + SortClosure *m_closure; +}; + +NS_IMETHODIMP nsAbView::SortBy(const char16_t *colID, const char16_t *sortDir, bool aResort = false) +{ + nsresult rv; + + int32_t count = mCards.Length(); + + nsAutoString sortColumn; + if (!colID) + sortColumn = NS_LITERAL_STRING(GENERATED_NAME_COLUMN_ID); // default sort column + else + sortColumn = colID; + + nsAutoString sortDirection; + if (!sortDir) + sortDirection = NS_LITERAL_STRING("ascending"); // default direction + else + sortDirection = sortDir; + + if (mSortColumn.Equals(sortColumn) && !aResort) { + if (mSortDirection.Equals(sortDir)) { + // If sortColumn and sortDirection are identical since the last call, do nothing. + return NS_OK; + } else { + // If we are sorting by how we are already sorted, + // and just the sort direction changes, just reverse. + int32_t halfPoint = count / 2; + for (int32_t i = 0; i < halfPoint; i++) { + // Swap the elements. + AbCard *ptr1 = mCards.ElementAt(i); + AbCard *ptr2 = mCards.ElementAt(count - i - 1); + mCards.ReplaceElementAt(i, ptr2); + mCards.ReplaceElementAt(count - i - 1, ptr1); + } + mSortDirection = sortDir; + } + } + else { + // Generate collation keys + for (int32_t i = 0; i < count; i++) { + AbCard *abcard = mCards.ElementAt(i); + + rv = GenerateCollationKeysForCard(sortColumn.get(), abcard); + NS_ENSURE_SUCCESS(rv,rv); + } + + // We need to do full sort. + SortClosure closure; + SetSortClosure(sortColumn.get(), sortDirection.get(), this, &closure); + + nsCOMPtr<nsIMutableArray> selectedCards = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetSelectedCards(selectedCards); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbCard> indexCard; + + if (mTreeSelection) { + int32_t currentIndex = -1; + + rv = mTreeSelection->GetCurrentIndex(¤tIndex); + NS_ENSURE_SUCCESS(rv,rv); + + if (currentIndex != -1) { + rv = GetCardFromRow(currentIndex, getter_AddRefs(indexCard)); + NS_ENSURE_SUCCESS(rv,rv); + } + } + + CardComparator cardComparator; + cardComparator.SetClosure(&closure); + mCards.Sort(cardComparator); + + rv = ReselectCards(selectedCards, indexCard); + NS_ENSURE_SUCCESS(rv, rv); + + mSortColumn = sortColumn; + mSortDirection = sortDirection; + } + + rv = InvalidateTree(ALL_ROWS); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +int32_t nsAbView::CompareCollationKeys(uint8_t *key1, uint32_t len1, uint8_t *key2, uint32_t len2) +{ + NS_ASSERTION(mCollationKeyGenerator, "no key generator"); + if (!mCollationKeyGenerator) + return 0; + + int32_t result; + + nsresult rv = mCollationKeyGenerator->CompareRawSortKey(key1,len1,key2,len2,&result); + NS_ASSERTION(NS_SUCCEEDED(rv), "key compare failed"); + if (NS_FAILED(rv)) + result = 0; + return result; +} + +nsresult nsAbView::GenerateCollationKeysForCard(const char16_t *colID, AbCard *abcard) +{ + nsresult rv; + nsString value; + + if (!mCollationKeyGenerator) + { + nsCOMPtr<nsILocaleService> localeSvc = do_GetService(NS_LOCALESERVICE_CONTRACTID,&rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILocale> locale; + rv = localeSvc->GetApplicationLocale(getter_AddRefs(locale)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr <nsICollationFactory> factory = do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = factory->CreateCollation(locale, getter_AddRefs(mCollationKeyGenerator)); + NS_ENSURE_SUCCESS(rv, rv); + } + + rv = GetCardValue(abcard->card, colID, value); + NS_ENSURE_SUCCESS(rv,rv); + + PR_FREEIF(abcard->primaryCollationKey); + rv = mCollationKeyGenerator->AllocateRawSortKey(nsICollation::kCollationCaseInSensitive, + value, &(abcard->primaryCollationKey), &(abcard->primaryCollationKeyLen)); + NS_ENSURE_SUCCESS(rv,rv); + + // Hardcode email to be our secondary key. As we are doing this, just call + // the card's GetCardValue direct, rather than our own function which will + // end up doing the same as then we can save a bit of time. + rv = abcard->card->GetPrimaryEmail(value); + NS_ENSURE_SUCCESS(rv,rv); + + PR_FREEIF(abcard->secondaryCollationKey); + rv = mCollationKeyGenerator->AllocateRawSortKey(nsICollation::kCollationCaseInSensitive, + value, &(abcard->secondaryCollationKey), &(abcard->secondaryCollationKeyLen)); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +// A helper method currently returns true if the directory is an LDAP. +// We can tweak this to return true for all Remote Address Books where the +// search is asynchronous. +bool isDirectoryRemote(nsCOMPtr<nsIAbDirectory> aDir) +{ + nsCString uri; + aDir->GetURI(uri); + return (uri.Find("moz-abldapdirectory") != kNotFound); +} + +// A helper method to get the query string for nsIAbDirectory. +nsCString getQuery(nsCOMPtr<nsIAbDirectory> aDir) +{ + nsCString uri; + aDir->GetURI(uri); + int32_t searchBegin = uri.FindChar('?'); + if (searchBegin == kNotFound) + return EmptyCString(); + + return nsCString(Substring(uri, searchBegin)); +} + +NS_IMETHODIMP nsAbView::OnItemAdded(nsISupports *parentDir, nsISupports *item) +{ + nsresult rv; + nsCOMPtr <nsIAbDirectory> directory = do_QueryInterface(parentDir, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + bool isRemote = isDirectoryRemote(directory); + // If the search is performed on All Address books, its possible that the LDAP + // results start coming when mDirectory has changed (LDAP search works in an + // asynchronous manner). + // Since the listeners are being added to all nsAbView instances, we need to + // make sure that all the views aren't updated by the listeners. + bool isDirectoryQuery = false; + bool isMDirectoryQuery = false; + // See if current parent directory to which the item is added is a query + // directory. + directory->GetIsQuery(&isDirectoryQuery); + // Get the query string for the directory in Advanced AB Search window. + nsCString directoryQuery(getQuery(directory)); + // See if the selected directory in Address book main window is a query + // directory. + mDirectory->GetIsQuery(&isMDirectoryQuery); + // Get the query string for the selected directory in the main AB window. + nsCString mDirectoryQuery(getQuery(mDirectory)); + if ((mIsAllDirectoryRootView && isRemote && + isDirectoryQuery && isMDirectoryQuery && + directoryQuery.Equals(mDirectoryQuery)) || + directory.get() == mDirectory.get()) { + nsCOMPtr <nsIAbCard> addedCard = do_QueryInterface(item); + if (addedCard) { + // Malloc these from an arena + AbCard *abcard = (AbCard *) PR_Calloc(1, sizeof(struct AbCard)); + if (!abcard) + return NS_ERROR_OUT_OF_MEMORY; + + abcard->card = addedCard; + NS_IF_ADDREF(abcard->card); + + rv = GenerateCollationKeysForCard(mSortColumn.get(), abcard); + NS_ENSURE_SUCCESS(rv,rv); + + int32_t index; + rv = AddCard(abcard, false /* select card */, &index); + NS_ENSURE_SUCCESS(rv,rv); + } + } + return rv; +} + +NS_IMETHODIMP nsAbView::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData) +{ + if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + if (nsDependentString(someData).EqualsLiteral(PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST)) { + nsresult rv = SetGeneratedNameFormatFromPrefs(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = RefreshTree(); + NS_ENSURE_SUCCESS(rv, rv); + } + } + return NS_OK; +} + +nsresult nsAbView::AddCard(AbCard *abcard, bool selectCardAfterAdding, int32_t *index) +{ + nsresult rv = NS_OK; + NS_ENSURE_ARG_POINTER(abcard); + + *index = FindIndexForInsert(abcard); + mCards.InsertElementAt(*index, abcard); + + // This needs to happen after we insert the card, as RowCountChanged() will call GetRowCount() + if (mTree) + rv = mTree->RowCountChanged(*index, 1); + + // Checking for mTree here works around core bug 399227 + if (selectCardAfterAdding && mTreeSelection && mTree) { + mTreeSelection->SetCurrentIndex(*index); + mTreeSelection->RangedSelect(*index, *index, false /* augment */); + } + + if (mAbViewListener && !mSuppressCountChange) { + rv = mAbViewListener->OnCountChanged(mCards.Length()); + NS_ENSURE_SUCCESS(rv,rv); + } + + return rv; +} + +int32_t nsAbView::FindIndexForInsert(AbCard *abcard) +{ + int32_t count = mCards.Length(); + int32_t i; + + SortClosure closure; + SetSortClosure(mSortColumn.get(), mSortDirection.get(), this, &closure); + + // XXX todo + // Make this a binary search + for (i=0; i < count; i++) { + int32_t value = inplaceSortCallback(abcard, mCards.ElementAt(i), &closure); + // XXX Fix me, this is not right for both ascending and descending + if (value <= 0) + break; + } + return i; +} + +NS_IMETHODIMP nsAbView::OnItemRemoved(nsISupports *parentDir, nsISupports *item) +{ + nsresult rv; + + nsCOMPtr <nsIAbDirectory> directory = do_QueryInterface(parentDir,&rv); + NS_ENSURE_SUCCESS(rv,rv); + + if (directory.get() == mDirectory.get()) + return RemoveCardAndSelectNextCard(item); + + // The pointers aren't the same, are the URI strings similar? This is most + // likely the case if the current directory is a search on a directory. + nsCString currentURI; + rv = mDirectory->GetURI(currentURI); + NS_ENSURE_SUCCESS(rv, rv); + + // If it is a search, it will have something like ?(or(PrimaryEmail... + // on the end of the string, so remove that before comparing + int32_t pos = currentURI.FindChar('?'); + if (pos != -1) + currentURI.SetLength(pos); + + nsCString notifiedURI; + rv = directory->GetURI(notifiedURI); + NS_ENSURE_SUCCESS(rv, rv); + + if (currentURI.Equals(notifiedURI)) + return RemoveCardAndSelectNextCard(item); + + return NS_OK; +} + +nsresult nsAbView::RemoveCardAndSelectNextCard(nsISupports *item) +{ + nsresult rv = NS_OK; + nsCOMPtr <nsIAbCard> card = do_QueryInterface(item); + if (card) { + int32_t index = FindIndexForCard(card); + if (index != CARD_NOT_FOUND) { + bool selectNextCard = false; + if (mTreeSelection) { + int32_t selectedIndex; + // XXX todo + // Make sure it works if nothing selected + mTreeSelection->GetCurrentIndex(&selectedIndex); + if (index == selectedIndex) + selectNextCard = true; + } + + rv = RemoveCardAt(index); + NS_ENSURE_SUCCESS(rv,rv); + + if (selectNextCard) { + int32_t count = mCards.Length(); + if (count && mTreeSelection) { + // If we deleted the last card, adjust so we select the new "last" card + if (index >= (count - 1)) { + index = count -1; + } + mTreeSelection->SetCurrentIndex(index); + mTreeSelection->RangedSelect(index, index, false /* augment */); + } + } + } + } + return rv; +} + +int32_t nsAbView::FindIndexForCard(nsIAbCard *card) +{ + int32_t count = mCards.Length(); + int32_t i; + + // You can't implement the binary search here, as all you have is the nsIAbCard + // you might be here because one of the card properties has changed, and that property + // could be the collation key. + for (i=0; i < count; i++) { + AbCard *abcard = mCards.ElementAt(i); + bool equals; + nsresult rv = card->Equals(abcard->card, &equals); + if (NS_SUCCEEDED(rv) && equals) { + return i; + } + } + return CARD_NOT_FOUND; +} + +NS_IMETHODIMP nsAbView::OnItemPropertyChanged(nsISupports *item, const char *property, const char16_t *oldValue, const char16_t *newValue) +{ + nsresult rv; + + nsCOMPtr <nsIAbCard> card = do_QueryInterface(item); + if (!card) + return NS_OK; + + int32_t index = FindIndexForCard(card); + if (index == -1) + return NS_OK; + + AbCard *oldCard = mCards.ElementAt(index); + + // Malloc these from an arena + AbCard *newCard = (AbCard *) PR_Calloc(1, sizeof(struct AbCard)); + if (!newCard) + return NS_ERROR_OUT_OF_MEMORY; + + newCard->card = card; + NS_IF_ADDREF(newCard->card); + + rv = GenerateCollationKeysForCard(mSortColumn.get(), newCard); + NS_ENSURE_SUCCESS(rv,rv); + + bool cardWasSelected = false; + + if (mTreeSelection) { + rv = mTreeSelection->IsSelected(index, &cardWasSelected); + NS_ENSURE_SUCCESS(rv,rv); + } + + if (!CompareCollationKeys(newCard->primaryCollationKey,newCard->primaryCollationKeyLen,oldCard->primaryCollationKey,oldCard->primaryCollationKeyLen) + && CompareCollationKeys(newCard->secondaryCollationKey,newCard->secondaryCollationKeyLen,oldCard->secondaryCollationKey,oldCard->secondaryCollationKeyLen)) { + // No need to remove and add, since the collation keys haven't changed. + // Since they haven't changed, the card will sort to the same place. + // We just need to clean up what we allocated. + NS_IF_RELEASE(newCard->card); + if (newCard->primaryCollationKey) + free(newCard->primaryCollationKey); + if (newCard->secondaryCollationKey) + free(newCard->secondaryCollationKey); + PR_FREEIF(newCard); + + // Still need to invalidate, as the other columns may have changed. + rv = InvalidateTree(index); + NS_ENSURE_SUCCESS(rv,rv); + } + else { + mSuppressSelectionChange = true; + mSuppressCountChange = true; + + // Remove the old card. + rv = RemoveCardAt(index); + NS_ASSERTION(NS_SUCCEEDED(rv), "remove card failed\n"); + + // Add the card we created, and select it (to restore selection) if it was selected. + rv = AddCard(newCard, cardWasSelected /* select card */, &index); + NS_ASSERTION(NS_SUCCEEDED(rv), "add card failed\n"); + + mSuppressSelectionChange = false; + mSuppressCountChange = false; + + // Ensure restored selection is visible + if (cardWasSelected && mTree) + mTree->EnsureRowIsVisible(index); + } + + // Although the selection hasn't changed, the card that is selected may need + // to be displayed differently, therefore pretend that the selection has + // changed to force that update. + if (cardWasSelected) + SelectionChanged(); + + return NS_OK; +} + +NS_IMETHODIMP nsAbView::SelectAll() +{ + if (mTreeSelection && mTree) { + mTreeSelection->SelectAll(); + mTree->Invalidate(); + } + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetSortDirection(nsAString & aDirection) +{ + aDirection = mSortDirection; + return NS_OK; +} + +NS_IMETHODIMP nsAbView::GetSortColumn(nsAString & aColumn) +{ + aColumn = mSortColumn; + return NS_OK; +} + +nsresult nsAbView::ReselectCards(nsIArray *aCards, nsIAbCard *aIndexCard) +{ + uint32_t count; + uint32_t i; + + if (!mTreeSelection || !aCards) + return NS_OK; + + nsresult rv = mTreeSelection->ClearSelection(); + NS_ENSURE_SUCCESS(rv,rv); + + rv = aCards->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + + // If we don't have any cards selected, nothing else to do. + if (!count) + return NS_OK; + + for (i = 0; i < count; i++) { + nsCOMPtr<nsIAbCard> card = do_QueryElementAt(aCards, i); + if (card) { + int32_t index = FindIndexForCard(card); + if (index != CARD_NOT_FOUND) { + mTreeSelection->RangedSelect(index, index, true /* augment */); + } + } + } + + // Reset the index card, and ensure it is visible. + if (aIndexCard) { + int32_t currentIndex = FindIndexForCard(aIndexCard); + rv = mTreeSelection->SetCurrentIndex(currentIndex); + NS_ENSURE_SUCCESS(rv, rv); + + if (mTree) { + rv = mTree->EnsureRowIsVisible(currentIndex); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsAbView::DeleteSelectedCards() +{ + nsresult rv; + nsCOMPtr<nsIMutableArray> cardsToDelete = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetSelectedCards(cardsToDelete); + NS_ENSURE_SUCCESS(rv, rv); + + // mDirectory should not be null + // Bullet proof (and assert) to help figure out bug #127748 + NS_ENSURE_TRUE(mDirectory, NS_ERROR_UNEXPECTED); + + rv = mDirectory->DeleteCards(cardsToDelete); + NS_ENSURE_SUCCESS(rv, rv); + return rv; +} + +nsresult nsAbView::GetSelectedCards(nsCOMPtr<nsIMutableArray> &aSelectedCards) +{ + if (!mTreeSelection) + return NS_OK; + + int32_t selectionCount; + nsresult rv = mTreeSelection->GetRangeCount(&selectionCount); + NS_ENSURE_SUCCESS(rv, rv); + + if (!selectionCount) + return NS_OK; + + for (int32_t i = 0; i < selectionCount; i++) + { + int32_t startRange; + int32_t endRange; + rv = mTreeSelection->GetRangeAt(i, &startRange, &endRange); + NS_ENSURE_SUCCESS(rv, NS_OK); + int32_t totalCards = mCards.Length(); + if (startRange >= 0 && startRange < totalCards) + { + for (int32_t rangeIndex = startRange; rangeIndex <= endRange && rangeIndex < totalCards; rangeIndex++) { + nsCOMPtr<nsIAbCard> abCard; + rv = GetCardFromRow(rangeIndex, getter_AddRefs(abCard)); + NS_ENSURE_SUCCESS(rv,rv); + + rv = aSelectedCards->AppendElement(abCard, false); + NS_ENSURE_SUCCESS(rv, rv); + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsAbView::SwapFirstNameLastName() +{ + if (!mTreeSelection) + return NS_OK; + + int32_t selectionCount; + nsresult rv = mTreeSelection->GetRangeCount(&selectionCount); + NS_ENSURE_SUCCESS(rv, rv); + + if (!selectionCount) + return NS_OK; + + // Prepare for displayname generation + // No cache for pref and bundle since the swap operation is not executed frequently + bool displayNameAutoGeneration; + bool displayNameLastnamefirst = false; + + nsCOMPtr<nsIPrefBranch> pPrefBranchInt(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = pPrefBranchInt->GetBoolPref(PREF_MAIL_ADDR_BOOK_DISPLAYNAME_AUTOGENERATION, &displayNameAutoGeneration); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIStringBundle> bundle; + if (displayNameAutoGeneration) + { + nsCOMPtr<nsIPrefLocalizedString> pls; + rv = pPrefBranchInt->GetComplexValue(PREF_MAIL_ADDR_BOOK_DISPLAYNAME_LASTNAMEFIRST, + NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(pls)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString str; + pls->ToString(getter_Copies(str)); + displayNameLastnamefirst = str.EqualsLiteral("true"); + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + + rv = bundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", + getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + } + + for (int32_t i = 0; i < selectionCount; i++) + { + int32_t startRange; + int32_t endRange; + rv = mTreeSelection->GetRangeAt(i, &startRange, &endRange); + NS_ENSURE_SUCCESS(rv, NS_OK); + int32_t totalCards = mCards.Length(); + if (startRange >= 0 && startRange < totalCards) + { + for (int32_t rangeIndex = startRange; rangeIndex <= endRange && rangeIndex < totalCards; rangeIndex++) { + nsCOMPtr<nsIAbCard> abCard; + rv = GetCardFromRow(rangeIndex, getter_AddRefs(abCard)); + NS_ENSURE_SUCCESS(rv, rv); + + // Swap FN/LN + nsAutoString fn, ln; + abCard->GetFirstName(fn); + abCard->GetLastName(ln); + if (!fn.IsEmpty() || !ln.IsEmpty()) + { + abCard->SetFirstName(ln); + abCard->SetLastName(fn); + + // Generate display name using the new order + if (displayNameAutoGeneration && + !fn.IsEmpty() && !ln.IsEmpty()) + { + nsString dnLnFn; + nsString dnFnLn; + const char16_t *nameString[2]; + const char16_t *formatString; + + // The format should stays the same before/after we swap the names + formatString = displayNameLastnamefirst ? + u"lastFirstFormat" : + u"firstLastFormat"; + + // Generate both ln/fn and fn/ln combination since we need both later + // to check to see if the current display name was edited + // note that fn/ln still hold the values before the swap + nameString[0] = ln.get(); + nameString[1] = fn.get(); + rv = bundle->FormatStringFromName(formatString, + nameString, 2, getter_Copies(dnLnFn)); + NS_ENSURE_SUCCESS(rv, rv); + nameString[0] = fn.get(); + nameString[1] = ln.get(); + rv = bundle->FormatStringFromName(formatString, + nameString, 2, getter_Copies(dnFnLn)); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the current display name + nsAutoString dn; + rv = abCard->GetDisplayName(dn); + NS_ENSURE_SUCCESS(rv, rv); + + // Swap the display name if not edited + if (displayNameLastnamefirst) + { + if (dn.Equals(dnLnFn)) + abCard->SetDisplayName(dnFnLn); + } + else + { + if (dn.Equals(dnFnLn)) + abCard->SetDisplayName(dnLnFn); + } + } + + // Swap phonetic names + rv = abCard->GetPropertyAsAString(kPhoneticFirstNameProperty, fn); + NS_ENSURE_SUCCESS(rv, rv); + rv = abCard->GetPropertyAsAString(kPhoneticLastNameProperty, ln); + NS_ENSURE_SUCCESS(rv, rv); + if (!fn.IsEmpty() || !ln.IsEmpty()) + { + abCard->SetPropertyAsAString(kPhoneticFirstNameProperty, ln); + abCard->SetPropertyAsAString(kPhoneticLastNameProperty, fn); + } + } + } + } + } + // Update the tree + // Re-sort if either generated or phonetic name is primary or secondary sort, + // otherwise invalidate to reflect the change + rv = RefreshTree(); + + return rv; +} + +NS_IMETHODIMP nsAbView::GetSelectedAddresses(nsIArray **_retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + nsresult rv; + nsCOMPtr<nsIMutableArray> selectedCards = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetSelectedCards(selectedCards); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> addresses = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + uint32_t count; + selectedCards->GetLength(&count); + + for (uint32_t i = 0; i < count; i++) { + nsCOMPtr<nsIAbCard> card(do_QueryElementAt(selectedCards, i, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + bool isMailList; + card->GetIsMailList(&isMailList); + nsAutoString primaryEmail; + if (isMailList) { + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, + &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString mailListURI; + rv = card->GetMailListURI(getter_Copies(mailListURI)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> mailList; + rv = abManager->GetDirectory(mailListURI, getter_AddRefs(mailList)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIMutableArray> mailListAddresses; + rv = mailList->GetAddressLists(getter_AddRefs(mailListAddresses)); + NS_ENSURE_SUCCESS(rv,rv); + + uint32_t mailListCount = 0; + mailListAddresses->GetLength(&mailListCount); + + for (uint32_t j = 0; j < mailListCount; j++) { + nsCOMPtr<nsIAbCard> mailListCard = do_QueryElementAt(mailListAddresses, j, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = mailListCard->GetPrimaryEmail(primaryEmail); + NS_ENSURE_SUCCESS(rv,rv); + + if (!primaryEmail.IsEmpty()) { + nsCOMPtr<nsISupportsString> supportsEmail(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID)); + supportsEmail->SetData(primaryEmail); + addresses->AppendElement(supportsEmail, false); + } + } + } + else { + rv = card->GetPrimaryEmail(primaryEmail); + NS_ENSURE_SUCCESS(rv,rv); + + if (!primaryEmail.IsEmpty()) { + nsCOMPtr<nsISupportsString> supportsEmail(do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID)); + supportsEmail->SetData(primaryEmail); + addresses->AppendElement(supportsEmail, false); + } + } + } + + NS_IF_ADDREF(*_retval = addresses); + + return NS_OK; +} diff --git a/mailnews/addrbook/src/nsAbView.h b/mailnews/addrbook/src/nsAbView.h new file mode 100644 index 000000000..f0a913082 --- /dev/null +++ b/mailnews/addrbook/src/nsAbView.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef _nsAbView_H_ +#define _nsAbView_H_ + +#include "nsISupports.h" +#include "nsStringGlue.h" +#include "nsIAbView.h" +#include "nsITreeView.h" +#include "nsITreeBoxObject.h" +#include "nsITreeSelection.h" +#include "nsTArray.h" +#include "nsIAbDirectory.h" +#include "nsIAtom.h" +#include "nsICollation.h" +#include "nsIAbListener.h" +#include "nsIObserver.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsMemory.h" +#include "nsIStringBundle.h" + +typedef struct AbCard +{ + nsIAbCard *card; + uint32_t primaryCollationKeyLen; + uint32_t secondaryCollationKeyLen; + uint8_t *primaryCollationKey; + uint8_t *secondaryCollationKey; +} AbCard; + + +class nsAbView : public nsIAbView, public nsITreeView, public nsIAbListener, public nsIObserver +{ +public: + nsAbView(); + + NS_DECL_ISUPPORTS + NS_DECL_NSIABVIEW + NS_DECL_NSITREEVIEW + NS_DECL_NSIABLISTENER + NS_DECL_NSIOBSERVER + + int32_t CompareCollationKeys(uint8_t *key1, uint32_t len1, uint8_t *key2, uint32_t len2); + +private: + virtual ~nsAbView(); + nsresult Initialize(); + int32_t FindIndexForInsert(AbCard *abcard); + int32_t FindIndexForCard(nsIAbCard *card); + nsresult GenerateCollationKeysForCard(const char16_t *colID, AbCard *abcard); + nsresult InvalidateTree(int32_t row); + nsresult RemoveCardAt(int32_t row); + nsresult AddCard(AbCard *abcard, bool selectCardAfterAdding, int32_t *index); + nsresult RemoveCardAndSelectNextCard(nsISupports *item); + nsresult EnumerateCards(); + nsresult SetGeneratedNameFormatFromPrefs(); + nsresult GetSelectedCards(nsCOMPtr<nsIMutableArray> &aSelectedCards); + nsresult ReselectCards(nsIArray *aCards, nsIAbCard *aIndexCard); + nsresult GetCardValue(nsIAbCard *card, const char16_t *colID, nsAString &_retval); + nsresult RefreshTree(); + + nsCOMPtr<nsITreeBoxObject> mTree; + nsCOMPtr<nsITreeSelection> mTreeSelection; + nsCOMPtr <nsIAbDirectory> mDirectory; + nsTArray<AbCard*> mCards; + nsString mSortColumn; + nsString mSortDirection; + nsCOMPtr<nsICollation> mCollationKeyGenerator; + nsCOMPtr<nsIAbViewListener> mAbViewListener; + nsCOMPtr<nsIStringBundle> mABBundle; + + bool mInitialized; + bool mIsAllDirectoryRootView; + bool mSuppressSelectionChange; + bool mSuppressCountChange; + int32_t mGeneratedNameFormat; +}; + +#endif /* _nsAbView_H_ */ diff --git a/mailnews/addrbook/src/nsAbWinHelper.cpp b/mailnews/addrbook/src/nsAbWinHelper.cpp new file mode 100644 index 000000000..c5b9d131f --- /dev/null +++ b/mailnews/addrbook/src/nsAbWinHelper.cpp @@ -0,0 +1,1003 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ +#define INITGUID +#define USES_IID_IMAPIProp +#define USES_IID_IMAPIContainer +#define USES_IID_IABContainer +#define USES_IID_IMAPITable +#define USES_IID_IDistList + +#include "nsAbWinHelper.h" +#include "nsMapiAddressBook.h" +#include "nsWabAddressBook.h" + +#include <mapiguid.h> + +#include "mozilla/Logging.h" + +#ifdef PR_LOGGING +static PRLogModuleInfo* gAbWinHelperLog + = PR_NewLogModule("nsAbWinHelperLog"); +#endif + +#define PRINTF(args) MOZ_LOG(gAbWinHelperLog, mozilla::LogLevel::Debug, args) + +// Small utility to ensure release of all MAPI interfaces +template <class tInterface> struct nsMapiInterfaceWrapper +{ + tInterface mInterface ; + + nsMapiInterfaceWrapper(void) : mInterface(NULL) {} + ~nsMapiInterfaceWrapper(void) { + if (mInterface != NULL) { mInterface->Release() ; } + } + operator LPUNKNOWN *(void) { return reinterpret_cast<LPUNKNOWN *>(&mInterface) ; } + tInterface operator -> (void) const { return mInterface ; } + operator tInterface *(void) { return &mInterface ; } +} ; + +static void assignEntryID(LPENTRYID& aTarget, LPENTRYID aSource, ULONG aByteCount) +{ + if (aTarget != NULL) { + delete [] (reinterpret_cast<LPBYTE>(aTarget)) ; + aTarget = NULL ; + } + if (aSource != NULL) { + aTarget = reinterpret_cast<LPENTRYID>(new BYTE [aByteCount]) ; + memcpy(aTarget, aSource, aByteCount) ; + } +} + +nsMapiEntry::nsMapiEntry(void) +: mByteCount(0), mEntryId(NULL) +{ + MOZ_COUNT_CTOR(nsMapiEntry) ; +} + +nsMapiEntry::nsMapiEntry(ULONG aByteCount, LPENTRYID aEntryId) +: mByteCount(0), mEntryId(NULL) +{ + Assign(aByteCount, aEntryId) ; + MOZ_COUNT_CTOR(nsMapiEntry) ; +} + +nsMapiEntry::~nsMapiEntry(void) +{ + Assign(0, NULL) ; + MOZ_COUNT_DTOR(nsMapiEntry) ; +} + +void nsMapiEntry::Assign(ULONG aByteCount, LPENTRYID aEntryId) +{ + assignEntryID(mEntryId, aEntryId, aByteCount) ; + mByteCount = aByteCount ; +} + +static char UnsignedToChar(unsigned char aUnsigned) +{ + if (aUnsigned < 0xA) { return '0' + aUnsigned ; } + return 'A' + aUnsigned - 0xA ; +} + +static char kBase64Encoding [] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-." ; +static const int kARank = 0 ; +static const int kaRank = 26 ; +static const int k0Rank = 52 ; +static const unsigned char kMinusRank = 62 ; +static const unsigned char kDotRank = 63 ; + +static void UnsignedToBase64(unsigned char *& aUnsigned, + ULONG aNbUnsigned, nsCString& aString) +{ + if (aNbUnsigned > 0) { + unsigned char remain0 = (*aUnsigned & 0x03) << 4 ; + + aString.Append(kBase64Encoding [(*aUnsigned >> 2) & 0x3F]) ; + ++ aUnsigned ; + if (aNbUnsigned > 1) { + unsigned char remain1 = (*aUnsigned & 0x0F) << 2 ; + + aString.Append(kBase64Encoding [remain0 | ((*aUnsigned >> 4) & 0x0F)]) ; + ++ aUnsigned ; + if (aNbUnsigned > 2) { + aString.Append(kBase64Encoding [remain1 | ((*aUnsigned >> 6) & 0x03)]) ; + aString.Append(kBase64Encoding [*aUnsigned & 0x3F]) ; + ++ aUnsigned ; + } + else { + aString.Append(kBase64Encoding [remain1]) ; + } + } + else { + aString.Append(kBase64Encoding [remain0]) ; + } + } +} + +static unsigned char CharToUnsigned(char aChar) +{ + if (aChar >= '0' && aChar <= '9') { return static_cast<unsigned char>(aChar) - '0' ; } + return static_cast<unsigned char>(aChar) - 'A' + 0xA ; +} + +// This function must return the rank in kBase64Encoding of the +// character provided. +static unsigned char Base64To6Bits(char aBase64) +{ + if (aBase64 >= 'A' && aBase64 <= 'Z') { + return static_cast<unsigned char>(aBase64 - 'A' + kARank) ; + } + if (aBase64 >= 'a' && aBase64 <= 'z') { + return static_cast<unsigned char>(aBase64 - 'a' + kaRank) ; + } + if (aBase64 >= '0' && aBase64 <= '9') { + return static_cast<unsigned char>(aBase64 - '0' + k0Rank) ; + } + if (aBase64 == '-') { return kMinusRank ; } + if (aBase64 == '.') { return kDotRank ; } + return 0 ; +} + +static void Base64ToUnsigned(const char *& aBase64, uint32_t aNbBase64, + unsigned char *&aUnsigned) +{ + // By design of the encoding, we must have at least two characters to use + if (aNbBase64 > 1) { + unsigned char first6Bits = Base64To6Bits(*aBase64 ++) ; + unsigned char second6Bits = Base64To6Bits(*aBase64 ++) ; + + *aUnsigned = first6Bits << 2 ; + *aUnsigned |= second6Bits >> 4 ; + ++ aUnsigned ; + if (aNbBase64 > 2) { + unsigned char third6Bits = Base64To6Bits(*aBase64 ++) ; + + *aUnsigned = second6Bits << 4 ; + *aUnsigned |= third6Bits >> 2 ; + ++ aUnsigned ; + if (aNbBase64 > 3) { + unsigned char fourth6Bits = Base64To6Bits(*aBase64 ++) ; + + *aUnsigned = third6Bits << 6 ; + *aUnsigned |= fourth6Bits ; + ++ aUnsigned ; + } + } + } +} + +void nsMapiEntry::Assign(const nsCString& aString) +{ + Assign(0, NULL) ; + ULONG byteCount = aString.Length() / 4 * 3 ; + + if ((aString.Length() & 0x03) != 0) { + byteCount += (aString.Length() & 0x03) - 1 ; + } + const char *currentSource = aString.get() ; + unsigned char *currentTarget = new unsigned char [byteCount] ; + uint32_t i = 0 ; + + mByteCount = byteCount ; + mEntryId = reinterpret_cast<LPENTRYID>(currentTarget) ; + for (i = aString.Length() ; i >= 4 ; i -= 4) { + Base64ToUnsigned(currentSource, 4, currentTarget) ; + } + Base64ToUnsigned(currentSource, i, currentTarget) ; +} + +void nsMapiEntry::ToString(nsCString& aString) const +{ + aString.Truncate() ; + ULONG i = 0 ; + unsigned char *currentSource = reinterpret_cast<unsigned char *>(mEntryId) ; + + for (i = mByteCount ; i >= 3 ; i -= 3) { + UnsignedToBase64(currentSource, 3, aString) ; + } + UnsignedToBase64(currentSource, i, aString) ; +} + +void nsMapiEntry::Dump(void) const +{ + PRINTF(("%d\n", mByteCount)) ; + for (ULONG i = 0 ; i < mByteCount ; ++ i) { + PRINTF(("%02X", (reinterpret_cast<unsigned char *>(mEntryId)) [i])) ; + } + PRINTF(("\n")) ; +} + +nsMapiEntryArray::nsMapiEntryArray(void) +: mEntries(NULL), mNbEntries(0) +{ + MOZ_COUNT_CTOR(nsMapiEntryArray) ; +} + +nsMapiEntryArray::~nsMapiEntryArray(void) +{ + if (mEntries) { delete [] mEntries ; } + MOZ_COUNT_DTOR(nsMapiEntryArray) ; +} + +void nsMapiEntryArray::CleanUp(void) +{ + if (mEntries != NULL) { + delete [] mEntries ; + mEntries = NULL ; + mNbEntries = 0 ; + } +} + +using namespace mozilla; + +uint32_t nsAbWinHelper::mEntryCounter = 0; +nsAutoPtr<mozilla::Mutex> nsAbWinHelper::mMutex; +uint32_t nsAbWinHelper::mUseCount = 0; +// There seems to be a deadlock/auto-destruction issue +// in MAPI when multiple threads perform init/release +// operations at the same time. So I've put a mutex +// around both the initialize process and the destruction +// one. I just hope the rest of the calls don't need the +// same protection (MAPI is supposed to be thread-safe). + +nsAbWinHelper::nsAbWinHelper(void) +: mAddressBook(NULL), mLastError(S_OK) +{ + if (!mUseCount++) + mMutex = new mozilla::Mutex("nsAbWinHelper.mMutex"); + + MOZ_COUNT_CTOR(nsAbWinHelper); +} + +nsAbWinHelper::~nsAbWinHelper(void) +{ + if (!--mUseCount) + mMutex = nullptr; + MOZ_COUNT_DTOR(nsAbWinHelper) ; +} + +BOOL nsAbWinHelper::GetFolders(nsMapiEntryArray& aFolders) +{ + aFolders.CleanUp() ; + nsMapiInterfaceWrapper<LPABCONT> rootFolder ; + nsMapiInterfaceWrapper<LPMAPITABLE> folders ; + ULONG objType = 0 ; + ULONG rowCount = 0 ; + SRestriction restriction ; + SPropTagArray folderColumns ; + + mLastError = mAddressBook->OpenEntry(0, NULL, NULL, 0, &objType, + rootFolder) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open root %08x.\n", mLastError)) ; + return FALSE ; + } + mLastError = rootFolder->GetHierarchyTable(0, folders) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot get hierarchy %08x.\n", mLastError)) ; + return FALSE ; + } + // We only take into account modifiable containers, + // otherwise, we end up with all the directory services... + restriction.rt = RES_BITMASK ; + restriction.res.resBitMask.ulPropTag = PR_CONTAINER_FLAGS ; + restriction.res.resBitMask.relBMR = BMR_NEZ ; + restriction.res.resBitMask.ulMask = AB_MODIFIABLE ; + mLastError = folders->Restrict(&restriction, 0) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot restrict table %08x.\n", mLastError)) ; + } + folderColumns.cValues = 1 ; + folderColumns.aulPropTag [0] = PR_ENTRYID ; + mLastError = folders->SetColumns(&folderColumns, 0) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot set columns %08x.\n", mLastError)) ; + return FALSE ; + } + mLastError = folders->GetRowCount(0, &rowCount) ; + if (HR_SUCCEEDED(mLastError)) { + aFolders.mEntries = new nsMapiEntry [rowCount] ; + aFolders.mNbEntries = 0 ; + do { + LPSRowSet rowSet = NULL ; + + rowCount = 0 ; + mLastError = folders->QueryRows(1, 0, &rowSet) ; + if (HR_SUCCEEDED(mLastError)) { + rowCount = rowSet->cRows ; + if (rowCount > 0) { + nsMapiEntry& current = aFolders.mEntries [aFolders.mNbEntries ++] ; + SPropValue& currentValue = rowSet->aRow->lpProps [0] ; + + current.Assign(currentValue.Value.bin.cb, + reinterpret_cast<LPENTRYID>(currentValue.Value.bin.lpb)) ; + } + MyFreeProws(rowSet) ; + } + else { + PRINTF(("Cannot query rows %08x.\n", mLastError)) ; + } + } while (rowCount > 0) ; + } + return HR_SUCCEEDED(mLastError) ; +} + +BOOL nsAbWinHelper::GetCards(const nsMapiEntry& aParent, LPSRestriction aRestriction, + nsMapiEntryArray& aCards) +{ + aCards.CleanUp() ; + return GetContents(aParent, aRestriction, &aCards.mEntries, aCards.mNbEntries, 0) ; +} + +BOOL nsAbWinHelper::GetNodes(const nsMapiEntry& aParent, nsMapiEntryArray& aNodes) +{ + aNodes.CleanUp() ; + return GetContents(aParent, NULL, &aNodes.mEntries, aNodes.mNbEntries, MAPI_DISTLIST) ; +} + +BOOL nsAbWinHelper::GetCardsCount(const nsMapiEntry& aParent, ULONG& aNbCards) +{ + aNbCards = 0 ; + return GetContents(aParent, NULL, NULL, aNbCards, 0) ; +} + +BOOL nsAbWinHelper::GetPropertyString(const nsMapiEntry& aObject, + ULONG aPropertyTag, + nsCString& aName) +{ + aName.Truncate() ; + LPSPropValue values = NULL ; + ULONG valueCount = 0 ; + + if (!GetMAPIProperties(aObject, &aPropertyTag, 1, values, valueCount)) { return FALSE ; } + if (valueCount == 1 && values != NULL) { + if (PROP_TYPE(values->ulPropTag) == PT_STRING8) + aName = values->Value.lpszA ; + else if (PROP_TYPE(values->ulPropTag) == PT_UNICODE) + aName = NS_LossyConvertUTF16toASCII(values->Value.lpszW); + } + FreeBuffer(values) ; + return TRUE ; +} + +BOOL nsAbWinHelper::GetPropertyUString(const nsMapiEntry& aObject, ULONG aPropertyTag, + nsString& aName) +{ + aName.Truncate() ; + LPSPropValue values = NULL ; + ULONG valueCount = 0 ; + + if (!GetMAPIProperties(aObject, &aPropertyTag, 1, values, valueCount)) { return FALSE ; } + if (valueCount == 1 && values != NULL) { + if (PROP_TYPE(values->ulPropTag) == PT_UNICODE) + aName = values->Value.lpszW ; + else if (PROP_TYPE(values->ulPropTag) == PT_STRING8) + aName.AssignASCII(values->Value.lpszA); + } + FreeBuffer(values) ; + return TRUE ; +} + +BOOL nsAbWinHelper::GetPropertiesUString(const nsMapiEntry& aObject, const ULONG *aPropertyTags, + ULONG aNbProperties, nsString *aNames) +{ + LPSPropValue values = NULL; + ULONG valueCount = 0; + + if (!GetMAPIProperties(aObject, aPropertyTags, aNbProperties, values, + valueCount)) + return FALSE; + + if (valueCount == aNbProperties && values != NULL) + { + for (ULONG i = 0 ; i < valueCount ; ++ i) + { + if (PROP_ID(values[i].ulPropTag) == PROP_ID(aPropertyTags[i])) + { + if (PROP_TYPE(values[i].ulPropTag) == PT_STRING8) + aNames[i].AssignASCII(values[i].Value.lpszA); + else if (PROP_TYPE(values[i].ulPropTag) == PT_UNICODE) + aNames[i] = values[i].Value.lpszW; + } + } + FreeBuffer(values); + } + return TRUE; +} + +BOOL nsAbWinHelper::GetPropertyDate(const nsMapiEntry& aObject, ULONG aPropertyTag, + WORD& aYear, WORD& aMonth, WORD& aDay) +{ + aYear = 0 ; + aMonth = 0 ; + aDay = 0 ; + LPSPropValue values = NULL ; + ULONG valueCount = 0 ; + + if (!GetMAPIProperties(aObject, &aPropertyTag, 1, values, valueCount)) { return FALSE ; } + if (valueCount == 1 && values != NULL && PROP_TYPE(values->ulPropTag) == PT_SYSTIME) { + SYSTEMTIME readableTime ; + + if (FileTimeToSystemTime(&values->Value.ft, &readableTime)) { + aYear = readableTime.wYear ; + aMonth = readableTime.wMonth ; + aDay = readableTime.wDay ; + } + } + FreeBuffer(values) ; + return TRUE ; +} + +BOOL nsAbWinHelper::GetPropertyLong(const nsMapiEntry& aObject, + ULONG aPropertyTag, + ULONG& aValue) +{ + aValue = 0 ; + LPSPropValue values = NULL ; + ULONG valueCount = 0 ; + + if (!GetMAPIProperties(aObject, &aPropertyTag, 1, values, valueCount)) { return FALSE ; } + if (valueCount == 1 && values != NULL && PROP_TYPE(values->ulPropTag) == PT_LONG) { + aValue = values->Value.ul ; + } + FreeBuffer(values) ; + return TRUE ; +} + +BOOL nsAbWinHelper::GetPropertyBin(const nsMapiEntry& aObject, ULONG aPropertyTag, + nsMapiEntry& aValue) +{ + aValue.Assign(0, NULL) ; + LPSPropValue values = NULL ; + ULONG valueCount = 0 ; + + if (!GetMAPIProperties(aObject, &aPropertyTag, 1, values, valueCount)) { return FALSE ; } + if (valueCount == 1 && values != NULL && PROP_TYPE(values->ulPropTag) == PT_BINARY) { + aValue.Assign(values->Value.bin.cb, + reinterpret_cast<LPENTRYID>(values->Value.bin.lpb)) ; + } + FreeBuffer(values) ; + return TRUE ; +} + +// This function, supposedly indicating whether a particular entry was +// in a particular container, doesn't seem to work very well (has +// a tendency to return TRUE even if we're talking to different containers...). +BOOL nsAbWinHelper::TestOpenEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aEntry) +{ + nsMapiInterfaceWrapper<LPMAPICONTAINER> container ; + nsMapiInterfaceWrapper<LPMAPIPROP> subObject ; + ULONG objType = 0 ; + + mLastError = mAddressBook->OpenEntry(aContainer.mByteCount, aContainer.mEntryId, + &IID_IMAPIContainer, 0, &objType, + container) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open container %08x.\n", mLastError)) ; + return FALSE ; + } + mLastError = container->OpenEntry(aEntry.mByteCount, aEntry.mEntryId, + NULL, 0, &objType, subObject) ; + return HR_SUCCEEDED(mLastError) ; +} + +BOOL nsAbWinHelper::DeleteEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aEntry) +{ + nsMapiInterfaceWrapper<LPABCONT> container ; + ULONG objType = 0 ; + SBinary entry ; + SBinaryArray entryArray ; + + mLastError = mAddressBook->OpenEntry(aContainer.mByteCount, aContainer.mEntryId, + &IID_IABContainer, MAPI_MODIFY, &objType, + container) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open container %08x.\n", mLastError)) ; + return FALSE ; + } + entry.cb = aEntry.mByteCount ; + entry.lpb = reinterpret_cast<LPBYTE>(aEntry.mEntryId) ; + entryArray.cValues = 1 ; + entryArray.lpbin = &entry ; + mLastError = container->DeleteEntries(&entryArray, 0) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot delete entry %08x.\n", mLastError)) ; + return FALSE ; + } + return TRUE ; +} + +BOOL nsAbWinHelper::SetPropertyUString(const nsMapiEntry& aObject, ULONG aPropertyTag, + const char16_t *aValue) +{ + SPropValue value ; + nsAutoCString alternativeValue ; + + value.ulPropTag = aPropertyTag ; + if (PROP_TYPE(aPropertyTag) == PT_UNICODE) { + value.Value.lpszW = wwc(const_cast<char16_t *>(aValue)) ; + } + else if (PROP_TYPE(aPropertyTag) == PT_STRING8) { + alternativeValue = NS_LossyConvertUTF16toASCII(aValue); + value.Value.lpszA = const_cast<char *>(alternativeValue.get()) ; + } + else { + PRINTF(("Property %08x is not a string.\n", aPropertyTag)) ; + return TRUE ; + } + return SetMAPIProperties(aObject, 1, &value) ; +} + +BOOL nsAbWinHelper::SetPropertiesUString(const nsMapiEntry& aObject, const ULONG *aPropertiesTag, + ULONG aNbProperties, nsString *aValues) +{ + LPSPropValue values = new SPropValue [aNbProperties] ; + if (!values) + return FALSE ; + + ULONG i = 0 ; + ULONG currentValue = 0 ; + nsAutoCString alternativeValue ; + BOOL retCode = TRUE ; + + for (i = 0 ; i < aNbProperties ; ++ i) { + values [currentValue].ulPropTag = aPropertiesTag [i] ; + if (PROP_TYPE(aPropertiesTag [i]) == PT_UNICODE) { + const wchar_t *value = aValues [i].get() ; + values [currentValue ++].Value.lpszW = const_cast<wchar_t *>(value) ; + } + else if (PROP_TYPE(aPropertiesTag [i]) == PT_STRING8) { + LossyCopyUTF16toASCII(aValues [i], alternativeValue); + char *av = strdup(alternativeValue.get()) ; + if (!av) { + retCode = FALSE ; + break ; + } + values [currentValue ++].Value.lpszA = av ; + } + } + if (retCode) + retCode = SetMAPIProperties(aObject, currentValue, values) ; + for (i = 0 ; i < currentValue ; ++ i) { + if (PROP_TYPE(aPropertiesTag [i]) == PT_STRING8) { + NS_Free(values [i].Value.lpszA) ; + } + } + delete [] values ; + return retCode ; +} + +BOOL nsAbWinHelper::SetPropertyDate(const nsMapiEntry& aObject, ULONG aPropertyTag, + WORD aYear, WORD aMonth, WORD aDay) +{ + SPropValue value ; + + value.ulPropTag = aPropertyTag ; + if (PROP_TYPE(aPropertyTag) == PT_SYSTIME) { + SYSTEMTIME readableTime ; + + readableTime.wYear = aYear ; + readableTime.wMonth = aMonth ; + readableTime.wDay = aDay ; + readableTime.wDayOfWeek = 0 ; + readableTime.wHour = 0 ; + readableTime.wMinute = 0 ; + readableTime.wSecond = 0 ; + readableTime.wMilliseconds = 0 ; + if (SystemTimeToFileTime(&readableTime, &value.Value.ft)) { + return SetMAPIProperties(aObject, 1, &value) ; + } + return TRUE ; + } + return FALSE ; +} + +BOOL nsAbWinHelper::CreateEntry(const nsMapiEntry& aParent, nsMapiEntry& aNewEntry) +{ + nsMapiInterfaceWrapper<LPABCONT> container ; + ULONG objType = 0 ; + + mLastError = mAddressBook->OpenEntry(aParent.mByteCount, aParent.mEntryId, + &IID_IABContainer, MAPI_MODIFY, &objType, + container) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open container %08x.\n", mLastError)) ; + return FALSE ; + } + SPropTagArray property ; + LPSPropValue value = NULL ; + ULONG valueCount = 0 ; + + property.cValues = 1 ; + property.aulPropTag [0] = PR_DEF_CREATE_MAILUSER ; + mLastError = container->GetProps(&property, 0, &valueCount, &value) ; + if (HR_FAILED(mLastError) || valueCount != 1) { + PRINTF(("Cannot obtain template %08x.\n", mLastError)) ; + return FALSE ; + } + nsMapiInterfaceWrapper<LPMAPIPROP> newEntry ; + + mLastError = container->CreateEntry(value->Value.bin.cb, + reinterpret_cast<LPENTRYID>(value->Value.bin.lpb), + CREATE_CHECK_DUP_LOOSE, + newEntry) ; + FreeBuffer(value) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot create new entry %08x.\n", mLastError)) ; + return FALSE ; + } + SPropValue displayName ; + LPSPropProblemArray problems = NULL ; + nsAutoString tempName ; + + displayName.ulPropTag = PR_DISPLAY_NAME_W ; + tempName.AssignLiteral("__MailUser__") ; + tempName.AppendInt(mEntryCounter ++) ; + const wchar_t *tempNameValue = tempName.get(); + displayName.Value.lpszW = const_cast<wchar_t *>(tempNameValue) ; + mLastError = newEntry->SetProps(1, &displayName, &problems) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot set temporary name %08x.\n", mLastError)) ; + return FALSE ; + } + mLastError = newEntry->SaveChanges(KEEP_OPEN_READONLY) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot commit new entry %08x.\n", mLastError)) ; + return FALSE ; + } + property.aulPropTag [0] = PR_ENTRYID ; + mLastError = newEntry->GetProps(&property, 0, &valueCount, &value) ; + if (HR_FAILED(mLastError) || valueCount != 1) { + PRINTF(("Cannot get entry id %08x.\n", mLastError)) ; + return FALSE ; + } + aNewEntry.Assign(value->Value.bin.cb, reinterpret_cast<LPENTRYID>(value->Value.bin.lpb)) ; + FreeBuffer(value) ; + return TRUE ; +} + +BOOL nsAbWinHelper::CreateDistList(const nsMapiEntry& aParent, nsMapiEntry& aNewEntry) +{ + nsMapiInterfaceWrapper<LPABCONT> container ; + ULONG objType = 0 ; + + mLastError = mAddressBook->OpenEntry(aParent.mByteCount, aParent.mEntryId, + &IID_IABContainer, MAPI_MODIFY, &objType, + container) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open container %08x.\n", mLastError)) ; + return FALSE ; + } + SPropTagArray property ; + LPSPropValue value = NULL ; + ULONG valueCount = 0 ; + + property.cValues = 1 ; + property.aulPropTag [0] = PR_DEF_CREATE_DL ; + mLastError = container->GetProps(&property, 0, &valueCount, &value) ; + if (HR_FAILED(mLastError) || valueCount != 1) { + PRINTF(("Cannot obtain template %08x.\n", mLastError)) ; + return FALSE ; + } + nsMapiInterfaceWrapper<LPMAPIPROP> newEntry ; + + mLastError = container->CreateEntry(value->Value.bin.cb, + reinterpret_cast<LPENTRYID>(value->Value.bin.lpb), + CREATE_CHECK_DUP_LOOSE, + newEntry) ; + FreeBuffer(value) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot create new entry %08x.\n", mLastError)) ; + return FALSE ; + } + SPropValue displayName ; + LPSPropProblemArray problems = NULL ; + nsAutoString tempName ; + + displayName.ulPropTag = PR_DISPLAY_NAME_W ; + tempName.AssignLiteral("__MailList__") ; + tempName.AppendInt(mEntryCounter ++) ; + const wchar_t *tempNameValue = tempName.get() ; + displayName.Value.lpszW = const_cast<wchar_t *>(tempNameValue) ; + mLastError = newEntry->SetProps(1, &displayName, &problems) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot set temporary name %08x.\n", mLastError)) ; + return FALSE ; + } + mLastError = newEntry->SaveChanges(KEEP_OPEN_READONLY) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot commit new entry %08x.\n", mLastError)) ; + return FALSE ; + } + property.aulPropTag [0] = PR_ENTRYID ; + mLastError = newEntry->GetProps(&property, 0, &valueCount, &value) ; + if (HR_FAILED(mLastError) || valueCount != 1) { + PRINTF(("Cannot get entry id %08x.\n", mLastError)) ; + return FALSE ; + } + aNewEntry.Assign(value->Value.bin.cb, + reinterpret_cast<LPENTRYID>(value->Value.bin.lpb)) ; + FreeBuffer(value) ; + return TRUE ; +} + +BOOL nsAbWinHelper::CopyEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aSource, + nsMapiEntry& aTarget) +{ + nsMapiInterfaceWrapper<LPABCONT> container ; + ULONG objType = 0 ; + + mLastError = mAddressBook->OpenEntry(aContainer.mByteCount, aContainer.mEntryId, + &IID_IABContainer, MAPI_MODIFY, &objType, + container) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open container %08x.\n", mLastError)) ; + return FALSE ; + } + nsMapiInterfaceWrapper<LPMAPIPROP> newEntry ; + + mLastError = container->CreateEntry(aSource.mByteCount, aSource.mEntryId, + CREATE_CHECK_DUP_LOOSE, newEntry) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot create new entry %08x.\n", mLastError)) ; + return FALSE ; + } + mLastError = newEntry->SaveChanges(KEEP_OPEN_READONLY) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot commit new entry %08x.\n", mLastError)) ; + return FALSE ; + } + SPropTagArray property ; + LPSPropValue value = NULL ; + ULONG valueCount = 0 ; + + property.cValues = 1 ; + property.aulPropTag [0] = PR_ENTRYID ; + mLastError = newEntry->GetProps(&property, 0, &valueCount, &value) ; + if (HR_FAILED(mLastError) || valueCount != 1) { + PRINTF(("Cannot get entry id %08x.\n", mLastError)) ; + return FALSE ; + } + aTarget.Assign(value->Value.bin.cb, + reinterpret_cast<LPENTRYID>(value->Value.bin.lpb)) ; + FreeBuffer(value) ; + return TRUE ; +} + +BOOL nsAbWinHelper::GetDefaultContainer(nsMapiEntry& aContainer) +{ + LPENTRYID entryId = NULL ; + ULONG byteCount = 0 ; + + mLastError = mAddressBook->GetPAB(&byteCount, &entryId) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot get PAB %08x.\n", mLastError)) ; + return FALSE ; + } + aContainer.Assign(byteCount, entryId) ; + FreeBuffer(entryId) ; + return TRUE ; +} + +enum +{ + ContentsColumnEntryId = 0, + ContentsColumnObjectType, + ContentsColumnsSize +} ; + +static const SizedSPropTagArray(ContentsColumnsSize, ContentsColumns) = +{ + ContentsColumnsSize, + { + PR_ENTRYID, + PR_OBJECT_TYPE + } +} ; + +BOOL nsAbWinHelper::GetContents(const nsMapiEntry& aParent, LPSRestriction aRestriction, + nsMapiEntry **aList, ULONG& aNbElements, ULONG aMapiType) +{ + if (aList != NULL) { *aList = NULL ; } + aNbElements = 0 ; + nsMapiInterfaceWrapper<LPMAPICONTAINER> parent ; + nsMapiInterfaceWrapper<LPMAPITABLE> contents ; + ULONG objType = 0 ; + ULONG rowCount = 0 ; + + mLastError = mAddressBook->OpenEntry(aParent.mByteCount, aParent.mEntryId, + &IID_IMAPIContainer, 0, &objType, + parent) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open parent %08x.\n", mLastError)) ; + return FALSE ; + } + // Here, flags for WAB and MAPI could be different, so this works + // only as long as we don't want to use any flag in GetContentsTable + mLastError = parent->GetContentsTable(0, contents) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot get contents %08x.\n", mLastError)) ; + return FALSE ; + } + if (aRestriction != NULL) { + mLastError = contents->Restrict(aRestriction, 0) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot set restriction %08x.\n", mLastError)) ; + return FALSE ; + } + } + mLastError = contents->SetColumns((LPSPropTagArray) &ContentsColumns, 0) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot set columns %08x.\n", mLastError)) ; + return FALSE ; + } + mLastError = contents->GetRowCount(0, &rowCount) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot get result count %08x.\n", mLastError)) ; + return FALSE ; + } + if (aList != NULL) { *aList = new nsMapiEntry [rowCount] ; } + aNbElements = 0 ; + do { + LPSRowSet rowSet = NULL ; + + rowCount = 0 ; + mLastError = contents->QueryRows(1, 0, &rowSet) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot query rows %08x.\n", mLastError)) ; + return FALSE ; + } + rowCount = rowSet->cRows ; + if (rowCount > 0 && + (aMapiType == 0 || + rowSet->aRow->lpProps[ContentsColumnObjectType].Value.ul == aMapiType)) { + if (aList != NULL) { + nsMapiEntry& current = (*aList) [aNbElements] ; + SPropValue& currentValue = rowSet->aRow->lpProps[ContentsColumnEntryId] ; + + current.Assign(currentValue.Value.bin.cb, + reinterpret_cast<LPENTRYID>(currentValue.Value.bin.lpb)) ; + } + ++ aNbElements ; + } + MyFreeProws(rowSet) ; + } while (rowCount > 0) ; + return TRUE ; +} + +BOOL nsAbWinHelper::GetMAPIProperties(const nsMapiEntry& aObject, const ULONG *aPropertyTags, + ULONG aNbProperties, LPSPropValue& aValue, + ULONG& aValueCount) +{ + nsMapiInterfaceWrapper<LPMAPIPROP> object ; + ULONG objType = 0 ; + LPSPropTagArray properties = NULL ; + ULONG i = 0 ; + + mLastError = mAddressBook->OpenEntry(aObject.mByteCount, aObject.mEntryId, + &IID_IMAPIProp, 0, &objType, + object) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open entry %08x.\n", mLastError)) ; + return FALSE ; + } + AllocateBuffer(CbNewSPropTagArray(aNbProperties), + reinterpret_cast<void **>(&properties)) ; + properties->cValues = aNbProperties ; + for (i = 0 ; i < aNbProperties ; ++ i) { + properties->aulPropTag [i] = aPropertyTags [i] ; + } + mLastError = object->GetProps(properties, 0, &aValueCount, &aValue) ; + FreeBuffer(properties) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot get props %08x.\n", mLastError)) ; + } + return HR_SUCCEEDED(mLastError) ; +} + +BOOL nsAbWinHelper::SetMAPIProperties(const nsMapiEntry& aObject, ULONG aNbProperties, + const LPSPropValue& aValues) +{ + nsMapiInterfaceWrapper<LPMAPIPROP> object ; + ULONG objType = 0 ; + LPSPropProblemArray problems = NULL ; + + mLastError = mAddressBook->OpenEntry(aObject.mByteCount, aObject.mEntryId, + &IID_IMAPIProp, MAPI_MODIFY, &objType, + object) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot open entry %08x.\n", mLastError)) ; + return FALSE ; + } + mLastError = object->SetProps(aNbProperties, aValues, &problems) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot update the object %08x.\n", mLastError)) ; + return FALSE ; + } + if (problems != NULL) { + for (ULONG i = 0 ; i < problems->cProblem ; ++ i) { + PRINTF(("Problem %d: index %d code %08x.\n", i, + problems->aProblem [i].ulIndex, + problems->aProblem [i].scode)) ; + } + } + mLastError = object->SaveChanges(0) ; + if (HR_FAILED(mLastError)) { + PRINTF(("Cannot commit changes %08x.\n", mLastError)) ; + } + return HR_SUCCEEDED(mLastError) ; +} + +void nsAbWinHelper::MyFreeProws(LPSRowSet aRowset) +{ + if (aRowset == NULL) { return ; } + ULONG i = 0 ; + + for (i = 0 ; i < aRowset->cRows ; ++ i) { + FreeBuffer(aRowset->aRow [i].lpProps) ; + } + FreeBuffer(aRowset) ; +} + +nsAbWinHelperGuard::nsAbWinHelperGuard(uint32_t aType) +: mHelper(NULL) +{ + switch(aType) { + case nsAbWinType_Outlook: mHelper = new nsMapiAddressBook ; break ; + case nsAbWinType_OutlookExp: mHelper = new nsWabAddressBook ; break ; + default: break ; + } +} + +nsAbWinHelperGuard::~nsAbWinHelperGuard(void) +{ + delete mHelper ; +} + +const char *kOutlookDirectoryScheme = "moz-aboutlookdirectory://" ; +const int kOutlookDirSchemeLength = 21 ; +const char *kOutlookStub = "op/" ; +const int kOutlookStubLength = 3 ; +const char *kOutlookExpStub = "oe/" ; +const int kOutlookExpStubLength = 3 ; +const char *kOutlookCardScheme = "moz-aboutlookcard://" ; +const int kOutlookCardSchemeLength = 16 ; + +nsAbWinType getAbWinType(const char *aScheme, const char *aUri, nsCString& aStub, nsCString& aEntry) +{ + aStub.Truncate() ; + aEntry.Truncate() ; + uint32_t schemeLength = strlen(aScheme) ; + + if (strncmp(aUri, aScheme, schemeLength) == 0) { + if (strncmp(aUri + schemeLength, kOutlookStub, kOutlookStubLength) == 0) { + aEntry = aUri + schemeLength + kOutlookStubLength ; + aStub = kOutlookStub ; + return nsAbWinType_Outlook ; + } + if (strncmp(aUri + schemeLength, kOutlookExpStub, kOutlookExpStubLength) == 0) { + aEntry = aUri + schemeLength + kOutlookExpStubLength ; + aStub = kOutlookExpStub ; + return nsAbWinType_OutlookExp ; + } + } + return nsAbWinType_Unknown ; +} + +void buildAbWinUri(const char *aScheme, uint32_t aType, nsCString& aUri) +{ + aUri.Assign(aScheme) ; + switch(aType) { + case nsAbWinType_Outlook: aUri.Append(kOutlookStub) ; break ; + case nsAbWinType_OutlookExp: aUri.Append(kOutlookExpStub) ; break ; + default: aUri.Assign("") ; + } +} + + + + + + diff --git a/mailnews/addrbook/src/nsAbWinHelper.h b/mailnews/addrbook/src/nsAbWinHelper.h new file mode 100644 index 000000000..fb0ad7e44 --- /dev/null +++ b/mailnews/addrbook/src/nsAbWinHelper.h @@ -0,0 +1,156 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ +#ifndef nsAbWinHelper_h___ +#define nsAbWinHelper_h___ + +#include <windows.h> +#include <mapix.h> + +#include "nsStringGlue.h" +#include "mozilla/Mutex.h" +#include "nsAutoPtr.h" + +struct nsMapiEntry +{ + ULONG mByteCount ; + LPENTRYID mEntryId ; + + nsMapiEntry(void) ; + ~nsMapiEntry(void) ; + nsMapiEntry(ULONG aByteCount, LPENTRYID aEntryId) ; + + void Assign(ULONG aByteCount, LPENTRYID aEntryId) ; + void Assign(const nsCString& aString) ; + void ToString(nsCString& aString) const ; + void Dump(void) const ; +} ; + +struct nsMapiEntryArray +{ + nsMapiEntry *mEntries ; + ULONG mNbEntries ; + + nsMapiEntryArray(void) ; + ~nsMapiEntryArray(void) ; + + const nsMapiEntry& operator [] (int aIndex) const { return mEntries [aIndex] ; } + void CleanUp(void) ; +} ; + +class nsAbWinHelper +{ +public: + nsAbWinHelper(void) ; + virtual ~nsAbWinHelper(void) ; + + // Get the top address books + BOOL GetFolders(nsMapiEntryArray& aFolders) ; + // Get a list of entries for cards/mailing lists in a folder/mailing list + BOOL GetCards(const nsMapiEntry& aParent, LPSRestriction aRestriction, + nsMapiEntryArray& aCards) ; + // Get a list of mailing lists in a folder + BOOL GetNodes(const nsMapiEntry& aParent, nsMapiEntryArray& aNodes) ; + // Get the number of cards/mailing lists in a folder/mailing list + BOOL GetCardsCount(const nsMapiEntry& aParent, ULONG& aNbCards) ; + // Access last MAPI error + HRESULT LastError(void) const { return mLastError ; } + // Get the value of a MAPI property of type string + BOOL GetPropertyString(const nsMapiEntry& aObject, ULONG aPropertyTag, nsCString& aValue) ; + // Same as previous, but string is returned as unicode + BOOL GetPropertyUString(const nsMapiEntry& aObject, ULONG aPropertyTag, nsString& aValue) ; + // Get multiple string MAPI properties in one call. + BOOL GetPropertiesUString(const nsMapiEntry& aObject, const ULONG *aPropertiesTag, + ULONG aNbProperties, nsString *aValues); + // Get the value of a MAPI property of type SYSTIME + BOOL GetPropertyDate(const nsMapiEntry& aObject, ULONG aPropertyTag, + WORD& aYear, WORD& aMonth, WORD& aDay) ; + // Get the value of a MAPI property of type LONG + BOOL GetPropertyLong(const nsMapiEntry& aObject, ULONG aPropertyTag, ULONG& aValue) ; + // Get the value of a MAPI property of type BIN + BOOL GetPropertyBin(const nsMapiEntry& aObject, ULONG aPropertyTag, nsMapiEntry& aValue) ; + // Tests if a container contains an entry + BOOL TestOpenEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aEntry) ; + // Delete an entry in the address book + BOOL DeleteEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aEntry) ; + // Set the value of a MAPI property of type string in unicode + BOOL SetPropertyUString (const nsMapiEntry& aObject, ULONG aPropertyTag, + const char16_t *aValue) ; + // Same as previous, but with a bunch of properties in one call + BOOL SetPropertiesUString(const nsMapiEntry& aObject, const ULONG *aPropertiesTag, + ULONG aNbProperties, nsString *aValues) ; + // Set the value of a MAPI property of type SYSTIME + BOOL SetPropertyDate(const nsMapiEntry& aObject, ULONG aPropertyTag, + WORD aYear, WORD aMonth, WORD aDay) ; + // Create entry in the address book + BOOL CreateEntry(const nsMapiEntry& aParent, nsMapiEntry& aNewEntry) ; + // Create a distribution list in the address book + BOOL CreateDistList(const nsMapiEntry& aParent, nsMapiEntry& aNewEntry) ; + // Copy an existing entry in the address book + BOOL CopyEntry(const nsMapiEntry& aContainer, const nsMapiEntry& aSource, nsMapiEntry& aTarget) ; + // Get a default address book container + BOOL GetDefaultContainer(nsMapiEntry& aContainer) ; + // Is the helper correctly initialised? + BOOL IsOK(void) const { return mAddressBook != NULL ; } + +protected: + HRESULT mLastError ; + LPADRBOOK mAddressBook ; + static uint32_t mEntryCounter ; + static uint32_t mUseCount ; + static nsAutoPtr<mozilla::Mutex> mMutex ; + + // Retrieve the contents of a container, with an optional restriction + BOOL GetContents(const nsMapiEntry& aParent, LPSRestriction aRestriction, + nsMapiEntry **aList, ULONG &aNbElements, ULONG aMapiType) ; + // Retrieve the values of a set of properties on a MAPI object + BOOL GetMAPIProperties(const nsMapiEntry& aObject, const ULONG *aPropertyTags, + ULONG aNbProperties, + LPSPropValue& aValues, ULONG& aValueCount) ; + // Set the values of a set of properties on a MAPI object + BOOL SetMAPIProperties(const nsMapiEntry& aObject, ULONG aNbProperties, + const LPSPropValue& aValues) ; + // Clean-up a rowset returned by QueryRows + void MyFreeProws(LPSRowSet aSet) ; + // Allocation of a buffer for transmission to interfaces + virtual void AllocateBuffer(ULONG aByteCount, LPVOID *aBuffer) = 0 ; + // Destruction of a buffer provided by the interfaces + virtual void FreeBuffer(LPVOID aBuffer) = 0 ; + +private: +} ; + +enum nsAbWinType +{ + nsAbWinType_Unknown, + nsAbWinType_Outlook, + nsAbWinType_OutlookExp +} ; + +class nsAbWinHelperGuard +{ +public : + nsAbWinHelperGuard(uint32_t aType) ; + ~nsAbWinHelperGuard(void) ; + + nsAbWinHelper *operator ->(void) { return mHelper ; } + +private: + nsAbWinHelper *mHelper ; +} ; + +extern const char *kOutlookDirectoryScheme ; +extern const int kOutlookDirSchemeLength ; +extern const char *kOutlookStub ; +extern const char *kOutlookExpStub ; +extern const char *kOutlookCardScheme ; + +nsAbWinType getAbWinType(const char *aScheme, const char *aUri, + nsCString& aStub, nsCString& aEntry) ; +void buildAbWinUri(const char *aScheme, uint32_t aType, nsCString& aUri) ; + +#endif // nsAbWinHelper_h___ + + + diff --git a/mailnews/addrbook/src/nsAddbookProtocolHandler.cpp b/mailnews/addrbook/src/nsAddbookProtocolHandler.cpp new file mode 100644 index 000000000..2d1fab36e --- /dev/null +++ b/mailnews/addrbook/src/nsAddbookProtocolHandler.cpp @@ -0,0 +1,323 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ +#include "msgCore.h" // precompiled header... +#include "nsStringGlue.h" +#include "nsIIOService.h" + +#include "nsIStreamListener.h" +#include "nsAddbookProtocolHandler.h" + +#include "nsAddbookUrl.h" +#include "nsAddbookProtocolHandler.h" +#include "nsCOMPtr.h" +#include "nsAbBaseCID.h" +#include "nsNetUtil.h" +#include "nsStringStream.h" +#include "nsIAbDirectory.h" +#include "nsIAbManager.h" +#include "prmem.h" +#include "nsIAbView.h" +#include "nsITreeView.h" +#include "nsIStringBundle.h" +#include "nsIServiceManager.h" +#include "mozilla/Services.h" +#include "nsIAsyncInputStream.h" +#include "nsIAsyncOutputStream.h" +#include "nsIPipe.h" +#include "nsIPrincipal.h" + +nsAddbookProtocolHandler::nsAddbookProtocolHandler() +{ + mAddbookOperation = nsIAddbookUrlOperation::InvalidUrl; +} + +nsAddbookProtocolHandler::~nsAddbookProtocolHandler() +{ +} + +NS_IMPL_ISUPPORTS(nsAddbookProtocolHandler, nsIProtocolHandler) + +NS_IMETHODIMP nsAddbookProtocolHandler::GetScheme(nsACString &aScheme) +{ + aScheme = "addbook"; + return NS_OK; +} + +NS_IMETHODIMP nsAddbookProtocolHandler::GetDefaultPort(int32_t *aDefaultPort) +{ + return NS_OK; +} + +NS_IMETHODIMP nsAddbookProtocolHandler::GetProtocolFlags(uint32_t *aUritype) +{ + *aUritype = URI_STD | URI_LOADABLE_BY_ANYONE | URI_FORBIDS_COOKIE_ACCESS; + return NS_OK; +} + +NS_IMETHODIMP nsAddbookProtocolHandler::NewURI(const nsACString &aSpec, + const char *aOriginCharset, // ignored + nsIURI *aBaseURI, + nsIURI **_retval) +{ + nsresult rv; + nsCOMPtr <nsIAddbookUrl> addbookUrl = do_CreateInstance(NS_ADDBOOKURL_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = addbookUrl->SetSpec(aSpec); + NS_ENSURE_SUCCESS(rv,rv); + + nsCOMPtr <nsIURI> uri = do_QueryInterface(addbookUrl, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + NS_ADDREF(*_retval = uri); + return NS_OK; +} + +NS_IMETHODIMP +nsAddbookProtocolHandler::AllowPort(int32_t port, const char *scheme, bool *_retval) +{ + // don't override anything. + *_retval = false; + return NS_OK; +} + +nsresult +nsAddbookProtocolHandler::GenerateXMLOutputChannel( nsString &aOutput, + nsIAddbookUrl *addbookUrl, + nsIURI *aURI, + nsILoadInfo *aLoadInfo, + nsIChannel **_retval) +{ + nsresult rv; + nsCOMPtr<nsIStringInputStream> inStr(do_CreateInstance("@mozilla.org/io/string-input-stream;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ConvertUTF16toUTF8 utf8String(aOutput.get()); + + rv = inStr->SetData(utf8String.get(), utf8String.Length()); + NS_ENSURE_SUCCESS(rv, rv); + + if (aLoadInfo) { + return NS_NewInputStreamChannelInternal(_retval, + aURI, + inStr, + NS_LITERAL_CSTRING("text/xml"), + EmptyCString(), + aLoadInfo); + } + + nsCOMPtr<nsIPrincipal> nullPrincipal = + do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); + NS_ASSERTION(NS_SUCCEEDED(rv), "CreateInstance of nullprincipalfailed."); + if (NS_FAILED(rv)) + return rv; + + return NS_NewInputStreamChannel(_retval, aURI, inStr, + nullPrincipal, nsILoadInfo::SEC_NORMAL, + nsIContentPolicy::TYPE_OTHER, + NS_LITERAL_CSTRING("text/xml")); +} + +NS_IMETHODIMP +nsAddbookProtocolHandler::NewChannel(nsIURI *aURI, nsIChannel **_retval) +{ + return NewChannel2(aURI, nullptr, _retval); +} + +NS_IMETHODIMP +nsAddbookProtocolHandler::NewChannel2(nsIURI *aURI, + nsILoadInfo* aLoadInfo, + nsIChannel **_retval) +{ + nsresult rv; + nsCOMPtr <nsIAddbookUrl> addbookUrl = do_QueryInterface(aURI, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + rv = addbookUrl->GetAddbookOperation(&mAddbookOperation); + NS_ENSURE_SUCCESS(rv,rv); + + if (mAddbookOperation == nsIAddbookUrlOperation::InvalidUrl) { + nsAutoString errorString; + errorString.AssignLiteral("Unsupported format/operation requested for "); + nsAutoCString spec; + rv = aURI->GetSpec(spec); + NS_ENSURE_SUCCESS(rv,rv); + + errorString.Append(NS_ConvertUTF8toUTF16(spec)); + rv = GenerateXMLOutputChannel(errorString, addbookUrl, aURI, aLoadInfo, _retval); + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; + } + + if (mAddbookOperation == nsIAddbookUrlOperation::AddVCard) { + // create an empty pipe for use with the input stream channel. + nsCOMPtr<nsIAsyncInputStream> pipeIn; + nsCOMPtr<nsIAsyncOutputStream> pipeOut; + nsCOMPtr<nsIPipe> pipe = do_CreateInstance("@mozilla.org/pipe;1"); + + rv = pipe->Init(false, false, 0, 0); + NS_ENSURE_SUCCESS(rv, rv); + + // These always succeed because the pipe is initialized above. + MOZ_ALWAYS_SUCCEEDS(pipe->GetInputStream(getter_AddRefs(pipeIn))); + MOZ_ALWAYS_SUCCEEDS(pipe->GetOutputStream(getter_AddRefs(pipeOut))); + + pipeOut->Close(); + if (aLoadInfo) { + return NS_NewInputStreamChannelInternal(_retval, + aURI, + pipeIn, + NS_LITERAL_CSTRING("application/x-addvcard"), + EmptyCString(), + aLoadInfo); + } + + nsCOMPtr<nsIPrincipal> nullPrincipal = + do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); + NS_ASSERTION(NS_SUCCEEDED(rv), "CreateInstance of nullprincipal failed."); + if (NS_FAILED(rv)) + return rv; + + return NS_NewInputStreamChannel(_retval, aURI, pipeIn, + nullPrincipal, nsILoadInfo::SEC_NORMAL, nsIContentPolicy::TYPE_OTHER, + NS_LITERAL_CSTRING("application/x-addvcard")); + } + + nsString output; + rv = GeneratePrintOutput(addbookUrl, output); + if (NS_FAILED(rv)) { + output.AssignLiteral("failed to print. url="); + nsAutoCString spec; + rv = aURI->GetSpec(spec); + NS_ENSURE_SUCCESS(rv,rv); + output.Append(NS_ConvertUTF8toUTF16(spec)); + } + + rv = GenerateXMLOutputChannel(output, addbookUrl, aURI, aLoadInfo, _retval); + NS_ENSURE_SUCCESS(rv,rv); + return NS_OK; +} + +nsresult +nsAddbookProtocolHandler::GeneratePrintOutput(nsIAddbookUrl *addbookUrl, + nsString &aOutput) +{ + NS_ENSURE_ARG_POINTER(addbookUrl); + + nsAutoCString uri; + nsresult rv = addbookUrl->GetPath(uri); + NS_ENSURE_SUCCESS(rv,rv); + + /* turn + "//moz-abmdbdirectory/abook.mab?action=print" + into "moz-abmdbdirectory://abook.mab" + */ + + /* step 1: + turn "//moz-abmdbdirectory/abook.mab?action=print" + into "moz-abmdbdirectory/abook.mab?action=print" + */ + if (uri[0] != '/' && uri[1] != '/') + return NS_ERROR_UNEXPECTED; + + uri.Cut(0,2); + + /* step 2: + turn "moz-abmdbdirectory/abook.mab?action=print" + into "moz-abmdbdirectory/abook.mab" + */ + int32_t pos = uri.Find("?action=print"); + if (pos == -1) + return NS_ERROR_UNEXPECTED; + + uri.SetLength(pos); + + /* step 2: + turn "moz-abmdbdirectory/abook.mab" + into "moz-abmdbdirectory://abook.mab" + */ + pos = uri.FindChar('/'); + if (pos == -1) + return NS_ERROR_UNEXPECTED; + + uri.Insert('/', pos); + uri.Insert(':', pos); + + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> directory; + rv = abManager->GetDirectory(uri, getter_AddRefs(directory)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = BuildDirectoryXML(directory, aOutput); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +nsAddbookProtocolHandler::BuildDirectoryXML(nsIAbDirectory *aDirectory, + nsString &aOutput) +{ + NS_ENSURE_ARG_POINTER(aDirectory); + + nsresult rv; + nsCOMPtr<nsISimpleEnumerator> cardsEnumerator; + nsCOMPtr<nsIAbCard> card; + + aOutput.AppendLiteral("<?xml version=\"1.0\"?>\n" + "<?xml-stylesheet type=\"text/css\" href=\"chrome://messagebody/content/addressbook/print.css\"?>\n" + "<directory>\n"); + + // Get Address Book string and set it as title of XML document + nsCOMPtr<nsIStringBundle> bundle; + nsCOMPtr<nsIStringBundleService> stringBundleService = + mozilla::services::GetStringBundleService(); + if (stringBundleService) { + rv = stringBundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", getter_AddRefs(bundle)); + if (NS_SUCCEEDED(rv)) { + nsString addrBook; + rv = bundle->GetStringFromName(u"addressBook", getter_Copies(addrBook)); + if (NS_SUCCEEDED(rv)) { + aOutput.AppendLiteral("<title xmlns=\"http://www.w3.org/1999/xhtml\">"); + aOutput.Append(addrBook); + aOutput.AppendLiteral("</title>\n"); + } + } + } + + // create a view and init it with the generated name sort order. Then, iterate + // over the view, getting the card for each row, and printing them. + nsString sortColumn; + nsCOMPtr <nsIAbView> view = do_CreateInstance("@mozilla.org/addressbook/abview;1", &rv); + + view->SetView(aDirectory, nullptr, NS_LITERAL_STRING("GeneratedName"), + NS_LITERAL_STRING("ascending"), sortColumn); + + int32_t numRows; + nsCOMPtr <nsITreeView> treeView = do_QueryInterface(view, &rv); + NS_ENSURE_SUCCESS(rv, rv); + treeView->GetRowCount(&numRows); + + for (int32_t row = 0; row < numRows; row++) + { + + nsCOMPtr <nsIAbCard> card; + view->GetCardFromRow(row, getter_AddRefs(card)); + nsCString xmlSubstr; + + rv = card->TranslateTo(NS_LITERAL_CSTRING("xml"), xmlSubstr); + NS_ENSURE_SUCCESS(rv,rv); + + aOutput.AppendLiteral("<separator/>"); + aOutput.Append(NS_ConvertUTF8toUTF16(xmlSubstr)); + aOutput.AppendLiteral("<separator/>"); + } + + aOutput.AppendLiteral("</directory>\n"); + + return NS_OK; +} diff --git a/mailnews/addrbook/src/nsAddbookProtocolHandler.h b/mailnews/addrbook/src/nsAddbookProtocolHandler.h new file mode 100644 index 000000000..1eb07a4ff --- /dev/null +++ b/mailnews/addrbook/src/nsAddbookProtocolHandler.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsAddbookProtocolHandler_h___ +#define nsAddbookProtocolHandler_h___ + +#include "nscore.h" +#include "nsCOMPtr.h" +#include "nsAddbookProtocolHandler.h" +#include "nsIProtocolHandler.h" +#include "nsIAddbookUrl.h" +#include "nsIAddrDatabase.h" + +class nsAddbookProtocolHandler : public nsIProtocolHandler +{ +public: + nsAddbookProtocolHandler(); + + NS_DECL_ISUPPORTS + + ////////////////////////////////////////////////////////////////////////// + // We support the nsIProtocolHandler interface. + ////////////////////////////////////////////////////////////////////////// + NS_DECL_NSIPROTOCOLHANDLER + +private: + virtual ~nsAddbookProtocolHandler(); + nsresult GenerateXMLOutputChannel(nsString &aOutput, + nsIAddbookUrl *addbookUrl, + nsIURI *aURI, + nsILoadInfo *aLoadInfo, + nsIChannel **_retval); + + nsresult GeneratePrintOutput(nsIAddbookUrl *addbookUrl, + nsString &aOutput); + + nsresult BuildDirectoryXML(nsIAbDirectory *aDirectory, + nsString &aOutput); + + int32_t mAddbookOperation; +}; + +#endif /* nsAddbookProtocolHandler_h___ */ diff --git a/mailnews/addrbook/src/nsAddbookUrl.cpp b/mailnews/addrbook/src/nsAddbookUrl.cpp new file mode 100644 index 000000000..2084ca467 --- /dev/null +++ b/mailnews/addrbook/src/nsAddbookUrl.cpp @@ -0,0 +1,282 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#include "nsIURI.h" +#include "nsNetCID.h" +#include "nsAddbookUrl.h" +#include "nsStringGlue.h" +#include "nsAbBaseCID.h" +#include "nsComponentManagerUtils.h" +#include "nsAutoPtr.h" + +///////////////////////////////////////////////////////////////////////////////////// +// addbook url definition +///////////////////////////////////////////////////////////////////////////////////// +nsAddbookUrl::nsAddbookUrl() +{ + m_baseURL = do_CreateInstance(NS_SIMPLEURI_CONTRACTID); + + mOperationType = nsIAddbookUrlOperation::InvalidUrl; +} + +nsAddbookUrl::~nsAddbookUrl() +{ +} + +NS_IMPL_ISUPPORTS(nsAddbookUrl, nsIAddbookUrl, nsIURI) + +NS_IMETHODIMP +nsAddbookUrl::SetSpec(const nsACString &aSpec) +{ + nsresult rv = m_baseURL->SetSpec(aSpec); + NS_ENSURE_SUCCESS(rv, rv); + return ParseUrl(); +} + +nsresult nsAddbookUrl::ParseUrl() +{ + nsAutoCString pathStr; + + nsresult rv = m_baseURL->GetPath(pathStr); + NS_ENSURE_SUCCESS(rv,rv); + + if (strstr(pathStr.get(), "?action=print")) + mOperationType = nsIAddbookUrlOperation::PrintAddressBook; + else if (strstr(pathStr.get(), "?action=add")) + mOperationType = nsIAddbookUrlOperation::AddVCard; + else + mOperationType = nsIAddbookUrlOperation::InvalidUrl; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////////////////// +// Begin nsIURI support +//////////////////////////////////////////////////////////////////////////////////// + + +NS_IMETHODIMP nsAddbookUrl::GetSpec(nsACString &aSpec) +{ + return m_baseURL->GetSpec(aSpec); +} + +NS_IMETHODIMP nsAddbookUrl::GetPrePath(nsACString &aPrePath) +{ + return m_baseURL->GetPrePath(aPrePath); +} + +NS_IMETHODIMP nsAddbookUrl::GetScheme(nsACString &aScheme) +{ + return m_baseURL->GetScheme(aScheme); +} + +NS_IMETHODIMP nsAddbookUrl::SetScheme(const nsACString &aScheme) +{ + return m_baseURL->SetScheme(aScheme); +} + +NS_IMETHODIMP nsAddbookUrl::GetUserPass(nsACString &aUserPass) +{ + return m_baseURL->GetUserPass(aUserPass); +} + +NS_IMETHODIMP nsAddbookUrl::SetUserPass(const nsACString &aUserPass) +{ + return m_baseURL->SetUserPass(aUserPass); +} + +NS_IMETHODIMP nsAddbookUrl::GetUsername(nsACString &aUsername) +{ + return m_baseURL->GetUsername(aUsername); +} + +NS_IMETHODIMP nsAddbookUrl::SetUsername(const nsACString &aUsername) +{ + return m_baseURL->SetUsername(aUsername); +} + +NS_IMETHODIMP nsAddbookUrl::GetPassword(nsACString &aPassword) +{ + return m_baseURL->GetPassword(aPassword); +} + +NS_IMETHODIMP nsAddbookUrl::SetPassword(const nsACString &aPassword) +{ + return m_baseURL->SetPassword(aPassword); +} + +NS_IMETHODIMP nsAddbookUrl::GetHostPort(nsACString &aHostPort) +{ + return m_baseURL->GetHostPort(aHostPort); +} + +NS_IMETHODIMP nsAddbookUrl::SetHostPort(const nsACString &aHostPort) +{ + return m_baseURL->SetHostPort(aHostPort); +} + +NS_IMETHODIMP nsAddbookUrl::SetHostAndPort(const nsACString &aHostPort) +{ + return m_baseURL->SetHostAndPort(aHostPort); +} + +NS_IMETHODIMP nsAddbookUrl::GetHost(nsACString &aHost) +{ + return m_baseURL->GetHost(aHost); +} + +NS_IMETHODIMP nsAddbookUrl::SetHost(const nsACString &aHost) +{ + return m_baseURL->SetHost(aHost); +} + +NS_IMETHODIMP nsAddbookUrl::GetPort(int32_t *aPort) +{ + return m_baseURL->GetPort(aPort); +} + +NS_IMETHODIMP nsAddbookUrl::SetPort(int32_t aPort) +{ + return m_baseURL->SetPort(aPort); +} + +NS_IMETHODIMP nsAddbookUrl::GetPath(nsACString &aPath) +{ + return m_baseURL->GetPath(aPath); +} + +NS_IMETHODIMP nsAddbookUrl::SetPath(const nsACString &aPath) +{ + m_baseURL->SetPath(aPath); + return ParseUrl(); +} + +NS_IMETHODIMP nsAddbookUrl::GetAsciiHost(nsACString &aHostA) +{ + return m_baseURL->GetAsciiHost(aHostA); +} + +NS_IMETHODIMP nsAddbookUrl::GetAsciiHostPort(nsACString &aHostPortA) +{ + return m_baseURL->GetAsciiHostPort(aHostPortA); +} + +NS_IMETHODIMP nsAddbookUrl::GetAsciiSpec(nsACString &aSpecA) +{ + return m_baseURL->GetAsciiSpec(aSpecA); +} + +NS_IMETHODIMP nsAddbookUrl::GetOriginCharset(nsACString &aOriginCharset) +{ + return m_baseURL->GetOriginCharset(aOriginCharset); +} + +NS_IMETHODIMP nsAddbookUrl::SchemeIs(const char *aScheme, bool *_retval) +{ + return m_baseURL->SchemeIs(aScheme, _retval); +} + +NS_IMETHODIMP nsAddbookUrl::Equals(nsIURI *other, bool *_retval) +{ + // The passed-in URI might be an nsMailtoUrl. Pass our inner URL to its + // Equals method. The other nsMailtoUrl will then pass its inner URL to + // to the Equals method of our inner URL. Other URIs will return false. + if (other) + return other->Equals(m_baseURL, _retval); + + return m_baseURL->Equals(other, _retval); +} + +nsresult +nsAddbookUrl::CloneInternal(RefHandlingEnum aRefHandlingMode, + const nsACString& newRef, nsIURI** _retval) +{ + NS_ENSURE_ARG_POINTER(_retval); + + RefPtr<nsAddbookUrl> clone = new nsAddbookUrl(); + + if (!clone) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + if (aRefHandlingMode == eHonorRef) { + rv = m_baseURL->Clone(getter_AddRefs(clone->m_baseURL)); + } else if (aRefHandlingMode == eReplaceRef) { + rv = m_baseURL->CloneWithNewRef(newRef, getter_AddRefs(clone->m_baseURL)); + } else { + rv = m_baseURL->CloneIgnoringRef(getter_AddRefs(clone->m_baseURL)); + } + NS_ENSURE_SUCCESS(rv, rv); + clone->ParseUrl(); + clone.forget(_retval); + return NS_OK; +} + +NS_IMETHODIMP nsAddbookUrl::Clone(nsIURI **_retval) +{ + return CloneInternal(eHonorRef, EmptyCString(), _retval); +} + +NS_IMETHODIMP +nsAddbookUrl::CloneIgnoringRef(nsIURI** _retval) +{ + return CloneInternal(eIgnoreRef, EmptyCString(), _retval); +} + +NS_IMETHODIMP +nsAddbookUrl::CloneWithNewRef(const nsACString& newRef, nsIURI** _retval) +{ + return CloneInternal(eReplaceRef, newRef, _retval); +} + +NS_IMETHODIMP nsAddbookUrl::Resolve(const nsACString &relativePath, nsACString &result) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsAddbookUrl::GetRef(nsACString &result) +{ + return m_baseURL->GetRef(result); +} + +NS_IMETHODIMP +nsAddbookUrl::SetRef(const nsACString &aRef) +{ + m_baseURL->SetRef(aRef); + return ParseUrl(); +} + +NS_IMETHODIMP nsAddbookUrl::EqualsExceptRef(nsIURI *other, bool *_retval) +{ + // The passed-in URI might be an nsMailtoUrl. Pass our inner URL to its + // Equals method. The other nsMailtoUrl will then pass its inner URL to + // to the Equals method of our inner URL. Other URIs will return false. + if (other) + return other->EqualsExceptRef(m_baseURL, _retval); + + return m_baseURL->EqualsExceptRef(other, _retval); +} + +NS_IMETHODIMP +nsAddbookUrl::GetSpecIgnoringRef(nsACString &result) +{ + return m_baseURL->GetSpecIgnoringRef(result); +} + +NS_IMETHODIMP +nsAddbookUrl::GetHasRef(bool *result) +{ + return m_baseURL->GetHasRef(result); +} + +// +// Specific nsAddbookUrl operations +// +NS_IMETHODIMP +nsAddbookUrl::GetAddbookOperation(int32_t *_retval) +{ + *_retval = mOperationType; + return NS_OK; +} diff --git a/mailnews/addrbook/src/nsAddbookUrl.h b/mailnews/addrbook/src/nsAddbookUrl.h new file mode 100644 index 000000000..71a34abe8 --- /dev/null +++ b/mailnews/addrbook/src/nsAddbookUrl.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsAddbookUrl_h__ +#define nsAddbookUrl_h__ + +#include "nsIURI.h" +#include "nsCOMPtr.h" +#include "nsIAddbookUrl.h" + +class nsAddbookUrl : public nsIAddbookUrl +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIURI + NS_DECL_NSIADDBOOKURL + + nsAddbookUrl(); + +protected: + enum RefHandlingEnum { + eIgnoreRef, + eHonorRef, + eReplaceRef + }; + virtual ~nsAddbookUrl(); + nsresult + CloneInternal(RefHandlingEnum aRefHandlingMode, + const nsACString& newRef, nsIURI** _retval); + + nsresult ParseUrl(); + int32_t mOperationType; // the internal ID for the operation + + nsCOMPtr<nsIURI> m_baseURL; // the base URL for the object +}; + +#endif // nsAddbookUrl_h__ diff --git a/mailnews/addrbook/src/nsAddrDatabase.cpp b/mailnews/addrbook/src/nsAddrDatabase.cpp new file mode 100644 index 000000000..ea29ba8af --- /dev/null +++ b/mailnews/addrbook/src/nsAddrDatabase.cpp @@ -0,0 +1,3335 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +// this file implements the nsAddrDatabase interface using the MDB Interface. + +#include "nsAddrDatabase.h" +#include "nsStringGlue.h" +#include "nsAutoPtr.h" +#include "nsUnicharUtils.h" +#include "nsAbBaseCID.h" +#include "nsIAbMDBDirectory.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsMsgUtils.h" +#include "nsMorkCID.h" +#include "nsIMdbFactoryFactory.h" +#include "prprf.h" +#include "nsIMutableArray.h" +#include "nsArrayUtils.h" +#include "nsIPromptService.h" +#include "nsIStringBundle.h" +#include "nsIFile.h" +#include "nsEmbedCID.h" +#include "nsIProperty.h" +#include "nsIVariant.h" +#include "nsCOMArray.h" +#include "nsArrayEnumerator.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsIAbManager.h" +#include "mozilla/Services.h" +#include <algorithm> + +#define ID_PAB_TABLE 1 +#define ID_DELETEDCARDS_TABLE 2 + +// There's two books by default, although Mac may have one more, so set this +// to three. Its not going to affect much, but will save us a few reallocations +// when the cache is allocated. +const uint32_t kInitialAddrDBCacheSize = 3; + +static const char kPabTableKind[] = "ns:addrbk:db:table:kind:pab"; +static const char kDeletedCardsTableKind[] = "ns:addrbk:db:table:kind:deleted"; // this table is used to keep the deleted cards + +static const char kCardRowScope[] = "ns:addrbk:db:row:scope:card:all"; +static const char kListRowScope[] = "ns:addrbk:db:row:scope:list:all"; +static const char kDataRowScope[] = "ns:addrbk:db:row:scope:data:all"; + +#define DATAROW_ROWID 1 + +#define COLUMN_STR_MAX 16 + +#define PURGE_CUTOFF_COUNT 50 + +static const char kRecordKeyColumn[] = "RecordKey"; +static const char kLastRecordKeyColumn[] = "LastRecordKey"; +static const char kRowIDProperty[] = "DbRowID"; + +static const char kLowerListNameColumn[] = "LowercaseListName"; + +struct mdbOid gAddressBookTableOID; + +static const char kMailListAddressFormat[] = "Address%d"; + +nsAddrDatabase::nsAddrDatabase() + : m_mdbEnv(nullptr), m_mdbStore(nullptr), + m_mdbPabTable(nullptr), + m_mdbDeletedCardsTable(nullptr), + m_mdbTokensInitialized(false), + m_PabTableKind(0), + m_MailListTableKind(0), + m_DeletedCardsTableKind(0), + m_CardRowScopeToken(0), + m_FirstNameColumnToken(0), + m_LastNameColumnToken(0), + m_PhoneticFirstNameColumnToken(0), + m_PhoneticLastNameColumnToken(0), + m_DisplayNameColumnToken(0), + m_NickNameColumnToken(0), + m_PriEmailColumnToken(0), + m_2ndEmailColumnToken(0), + m_WorkPhoneColumnToken(0), + m_HomePhoneColumnToken(0), + m_FaxColumnToken(0), + m_PagerColumnToken(0), + m_CellularColumnToken(0), + m_WorkPhoneTypeColumnToken(0), + m_HomePhoneTypeColumnToken(0), + m_FaxTypeColumnToken(0), + m_PagerTypeColumnToken(0), + m_CellularTypeColumnToken(0), + m_HomeAddressColumnToken(0), + m_HomeAddress2ColumnToken(0), + m_HomeCityColumnToken(0), + m_HomeStateColumnToken(0), + m_HomeZipCodeColumnToken(0), + m_HomeCountryColumnToken(0), + m_WorkAddressColumnToken(0), + m_WorkAddress2ColumnToken(0), + m_WorkCityColumnToken(0), + m_WorkStateColumnToken(0), + m_WorkZipCodeColumnToken(0), + m_WorkCountryColumnToken(0), + m_CompanyColumnToken(0), + m_AimScreenNameColumnToken(0), + m_AnniversaryYearColumnToken(0), + m_AnniversaryMonthColumnToken(0), + m_AnniversaryDayColumnToken(0), + m_SpouseNameColumnToken(0), + m_FamilyNameColumnToken(0), + m_DefaultAddressColumnToken(0), + m_CategoryColumnToken(0), + m_WebPage1ColumnToken(0), + m_WebPage2ColumnToken(0), + m_BirthYearColumnToken(0), + m_BirthMonthColumnToken(0), + m_BirthDayColumnToken(0), + m_Custom1ColumnToken(0), + m_Custom2ColumnToken(0), + m_Custom3ColumnToken(0), + m_Custom4ColumnToken(0), + m_NotesColumnToken(0), + m_LastModDateColumnToken(0), + m_MailFormatColumnToken(0), + m_PopularityIndexColumnToken(0), + m_AddressCharSetColumnToken(0), + m_LastRecordKey(0), + m_dbDirectory(nullptr) +{ +} + +nsAddrDatabase::~nsAddrDatabase() +{ + Close(false); // better have already been closed. + + // better not be any listeners, because we're going away. + NS_ASSERTION(m_ChangeListeners.Length() == 0, "shouldn't have any listeners"); + + RemoveFromCache(this); + // clean up after ourself! + if (m_mdbPabTable) + m_mdbPabTable->Release(); + if (m_mdbDeletedCardsTable) + m_mdbDeletedCardsTable->Release(); + NS_IF_RELEASE(m_mdbStore); + NS_IF_RELEASE(m_mdbEnv); +} + +NS_IMPL_ISUPPORTS(nsAddrDatabase, nsIAddrDatabase, nsIAddrDBAnnouncer) + +NS_IMETHODIMP nsAddrDatabase::AddListener(nsIAddrDBListener *listener) +{ + NS_ENSURE_ARG_POINTER(listener); + return m_ChangeListeners.AppendElement(listener) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsAddrDatabase::RemoveListener(nsIAddrDBListener *listener) +{ + NS_ENSURE_ARG_POINTER(listener); + return m_ChangeListeners.RemoveElement(listener) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsAddrDatabase::NotifyCardAttribChange(uint32_t abCode) +{ + NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(m_ChangeListeners, nsIAddrDBListener, + OnCardAttribChange, (abCode)); + return NS_OK; +} + +NS_IMETHODIMP nsAddrDatabase::NotifyCardEntryChange(uint32_t aAbCode, nsIAbCard *aCard, nsIAbDirectory *aParent) +{ + int32_t currentDisplayNameVersion = 0; + + //Update "mail.displayname.version" prefernce + nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID)); + + prefs->GetIntPref("mail.displayname.version",¤tDisplayNameVersion); + + prefs->SetIntPref("mail.displayname.version",++currentDisplayNameVersion); + + NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(m_ChangeListeners, nsIAddrDBListener, + OnCardEntryChange, (aAbCode, aCard, aParent)); + return NS_OK; +} + +nsresult nsAddrDatabase::NotifyListEntryChange(uint32_t abCode, nsIAbDirectory *dir) +{ + NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(m_ChangeListeners, nsIAddrDBListener, + OnListEntryChange, (abCode, dir)); + return NS_OK; +} + + +NS_IMETHODIMP nsAddrDatabase::NotifyAnnouncerGoingAway(void) +{ + NS_OBSERVER_ARRAY_NOTIFY_OBSERVERS(m_ChangeListeners, nsIAddrDBListener, + OnAnnouncerGoingAway, ()); + return NS_OK; +} + + +// Apparently its not good for nsTArray to be allocated as static. Don't know +// why it isn't but its not, so don't think about making it a static variable. +// Maybe bz knows. +nsTArray<nsAddrDatabase*>* nsAddrDatabase::m_dbCache = nullptr; + +nsTArray<nsAddrDatabase*>* +nsAddrDatabase::GetDBCache() +{ + if (!m_dbCache) + m_dbCache = new AutoTArray<nsAddrDatabase*, kInitialAddrDBCacheSize>; + + return m_dbCache; +} + +void +nsAddrDatabase::CleanupCache() +{ + if (m_dbCache) + { + for (int32_t i = m_dbCache->Length() - 1; i >= 0; --i) + { + nsAddrDatabase* pAddrDB = m_dbCache->ElementAt(i); + if (pAddrDB) + pAddrDB->ForceClosed(); + } + // NS_ASSERTION(m_dbCache.Length() == 0, "some msg dbs left open"); // better not be any open db's. + delete m_dbCache; + m_dbCache = nullptr; + } +} + +//---------------------------------------------------------------------- +// FindInCache - this addrefs the db it finds. +//---------------------------------------------------------------------- +nsAddrDatabase* nsAddrDatabase::FindInCache(nsIFile *dbName) +{ + nsTArray<nsAddrDatabase*>* dbCache = GetDBCache(); + uint32_t length = dbCache->Length(); + for (uint32_t i = 0; i < length; ++i) + { + nsAddrDatabase* pAddrDB = dbCache->ElementAt(i); + if (pAddrDB->MatchDbName(dbName)) + { + NS_ADDREF(pAddrDB); + return pAddrDB; + } + } + return nullptr; +} + +bool nsAddrDatabase::MatchDbName(nsIFile* dbName) // returns true if they match +{ + bool dbMatches = false; + + nsresult rv = m_dbName->Equals(dbName, &dbMatches); + if (NS_FAILED(rv)) + return false; + + return dbMatches; +} + +//---------------------------------------------------------------------- +// RemoveFromCache +//---------------------------------------------------------------------- +void nsAddrDatabase::RemoveFromCache(nsAddrDatabase* pAddrDB) +{ + if (m_dbCache) + m_dbCache->RemoveElement(pAddrDB); +} + +void nsAddrDatabase::GetMDBFactory(nsIMdbFactory ** aMdbFactory) +{ + if (!mMdbFactory) + { + nsresult rv; + nsCOMPtr <nsIMdbFactoryService> mdbFactoryService = do_GetService(NS_MORK_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv) && mdbFactoryService) + rv = mdbFactoryService->GetMdbFactory(getter_AddRefs(mMdbFactory)); + } + NS_IF_ADDREF(*aMdbFactory = mMdbFactory); +} + +/* caller need to delete *aDbPath */ +NS_IMETHODIMP nsAddrDatabase::GetDbPath(nsIFile* *aDbPath) +{ + if (!aDbPath) + return NS_ERROR_NULL_POINTER; + + return m_dbName->Clone(aDbPath); +} + +NS_IMETHODIMP nsAddrDatabase::SetDbPath(nsIFile* aDbPath) +{ + return aDbPath->Clone(getter_AddRefs(m_dbName)); +} + +NS_IMETHODIMP nsAddrDatabase::Open +(nsIFile *aMabFile, bool aCreate, bool upgrading /* unused */, nsIAddrDatabase** pAddrDB) +{ + *pAddrDB = nullptr; + + nsAddrDatabase *pAddressBookDB = FindInCache(aMabFile); + + if (pAddressBookDB) { + *pAddrDB = pAddressBookDB; + return NS_OK; + } + + nsresult rv = OpenInternal(aMabFile, aCreate, pAddrDB); + if (NS_SUCCEEDED(rv)) + return NS_OK; + + if (rv == NS_ERROR_FILE_ACCESS_DENIED) + { + static bool gAlreadyAlerted; + // only do this once per session to avoid annoying the user + if (!gAlreadyAlerted) + { + gAlreadyAlerted = true; + nsAutoString mabFileName; + rv = aMabFile->GetLeafName(mabFileName); + NS_ENSURE_SUCCESS(rv, rv); + AlertAboutLockedMabFile(mabFileName.get()); + + // We just overwrote rv, so return the proper value here. + return NS_ERROR_FILE_ACCESS_DENIED; + } + } + // try one more time + // but first rename corrupt mab file + // and prompt the user + else if (aCreate) + { + nsCOMPtr<nsIFile> dummyBackupMabFile; + nsCOMPtr<nsIFile> actualBackupMabFile; + + // First create a clone of the corrupt mab file that we'll + // use to generate the name for the backup file that we are + // going to move it to. + rv = aMabFile->Clone(getter_AddRefs(dummyBackupMabFile)); + NS_ENSURE_SUCCESS(rv, rv); + + // Now create a second clone that we'll use to do the move + // (this allows us to leave the original name intact) + rv = aMabFile->Clone(getter_AddRefs(actualBackupMabFile)); + NS_ENSURE_SUCCESS(rv, rv); + + // Now we try and generate a new name for the corrupt mab + // file using the dummy backup mab file + + // First append .bak - we have to do this the long way as + // AppendNative is to the path, not the LeafName. + nsAutoCString dummyBackupMabFileName; + rv = dummyBackupMabFile->GetNativeLeafName(dummyBackupMabFileName); + NS_ENSURE_SUCCESS(rv, rv); + + dummyBackupMabFileName.Append(NS_LITERAL_CSTRING(".bak")); + + rv = dummyBackupMabFile->SetNativeLeafName(dummyBackupMabFileName); + NS_ENSURE_SUCCESS(rv, rv); + + // Now see if we can create it unique + rv = dummyBackupMabFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); + NS_ENSURE_SUCCESS(rv, rv); + + // Now get the new name + nsAutoCString backupMabFileName; + rv = dummyBackupMabFile->GetNativeLeafName(backupMabFileName); + NS_ENSURE_SUCCESS(rv, rv); + + // And the parent directory + nsCOMPtr<nsIFile> parentDir; + rv = dummyBackupMabFile->GetParent(getter_AddRefs(parentDir)); + NS_ENSURE_SUCCESS(rv, rv); + + // Now move the corrupt file to its backup location + rv = actualBackupMabFile->MoveToNative(parentDir, backupMabFileName); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to rename corrupt mab file"); + + if (NS_SUCCEEDED(rv)) { + // now we can try to recreate the original mab file + rv = OpenInternal(aMabFile, aCreate, pAddrDB); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to create .mab file, after rename"); + + if (NS_SUCCEEDED(rv)) { + nsAutoString originalMabFileName; + rv = aMabFile->GetLeafName(originalMabFileName); + NS_ENSURE_SUCCESS(rv, rv); + + // if this fails, we don't care + (void)AlertAboutCorruptMabFile(originalMabFileName.get(), + NS_ConvertASCIItoUTF16(backupMabFileName).get()); + } + } + } + return rv; +} + +nsresult nsAddrDatabase::DisplayAlert(const char16_t *titleName, const char16_t *alertStringName, const char16_t **formatStrings, int32_t numFormatStrings) +{ + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> bundle; + rv = bundleService->CreateBundle("chrome://messenger/locale/addressbook/addressBook.properties", getter_AddRefs(bundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString alertMessage; + rv = bundle->FormatStringFromName(alertStringName, formatStrings, numFormatStrings, + getter_Copies(alertMessage)); + NS_ENSURE_SUCCESS(rv, rv); + + nsString alertTitle; + rv = bundle->GetStringFromName(titleName, getter_Copies(alertTitle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPromptService> prompter = + do_GetService(NS_PROMPTSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + return prompter->Alert(nullptr /* we don't know the parent window */, alertTitle.get(), alertMessage.get()); +} + +nsresult nsAddrDatabase::AlertAboutCorruptMabFile(const char16_t *aOldFileName, const char16_t *aNewFileName) +{ + const char16_t *formatStrings[] = { aOldFileName, aOldFileName, aNewFileName }; + return DisplayAlert(u"corruptMabFileTitle", + u"corruptMabFileAlert", formatStrings, 3); +} + +nsresult nsAddrDatabase::AlertAboutLockedMabFile(const char16_t *aFileName) +{ + const char16_t *formatStrings[] = { aFileName }; + return DisplayAlert(u"lockedMabFileTitle", + u"lockedMabFileAlert", formatStrings, 1); +} + +nsresult +nsAddrDatabase::OpenInternal(nsIFile *aMabFile, bool aCreate, nsIAddrDatabase** pAddrDB) +{ + nsAddrDatabase *pAddressBookDB = new nsAddrDatabase(); + if (!pAddressBookDB) { + return NS_ERROR_OUT_OF_MEMORY; + } + + NS_ADDREF(pAddressBookDB); + + nsresult rv = pAddressBookDB->OpenMDB(aMabFile, aCreate); + if (NS_SUCCEEDED(rv)) + { + pAddressBookDB->SetDbPath(aMabFile); + GetDBCache()->AppendElement(pAddressBookDB); + *pAddrDB = pAddressBookDB; + } + else + { + *pAddrDB = nullptr; + pAddressBookDB->ForceClosed(); + NS_IF_RELEASE(pAddressBookDB); + pAddressBookDB = nullptr; + } + return rv; +} + +// Open the MDB database synchronously. If successful, this routine +// will set up the m_mdbStore and m_mdbEnv of the database object +// so other database calls can work. +NS_IMETHODIMP nsAddrDatabase::OpenMDB(nsIFile *dbName, bool create) +{ + nsresult ret; + nsCOMPtr<nsIMdbFactory> mdbFactory; + GetMDBFactory(getter_AddRefs(mdbFactory)); + NS_ENSURE_TRUE(mdbFactory, NS_ERROR_FAILURE); + + ret = mdbFactory->MakeEnv(NULL, &m_mdbEnv); + if (NS_SUCCEEDED(ret)) + { + nsIMdbThumb *thumb = nullptr; + nsAutoCString filePath; + + ret = dbName->GetNativePath(filePath); + NS_ENSURE_SUCCESS(ret, ret); + + nsIMdbHeap* dbHeap = nullptr; + + if (m_mdbEnv) + m_mdbEnv->SetAutoClear(true); + + bool dbNameExists = false; + ret = dbName->Exists(&dbNameExists); + NS_ENSURE_SUCCESS(ret, ret); + + if (!dbNameExists) + ret = NS_ERROR_FILE_NOT_FOUND; + else + { + mdbOpenPolicy inOpenPolicy; + mdb_bool canOpen; + mdbYarn outFormatVersion; + nsIMdbFile* oldFile = nullptr; + int64_t fileSize; + ret = dbName->GetFileSize(&fileSize); + NS_ENSURE_SUCCESS(ret, ret); + + ret = mdbFactory->OpenOldFile(m_mdbEnv, dbHeap, filePath.get(), + mdbBool_kFalse, // not readonly, we want modifiable + &oldFile); + if ( oldFile ) + { + if (NS_SUCCEEDED(ret)) + { + ret = mdbFactory->CanOpenFilePort(m_mdbEnv, oldFile, // the file to investigate + &canOpen, &outFormatVersion); + if (NS_SUCCEEDED(ret) && canOpen) + { + inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0; + inOpenPolicy.mOpenPolicy_MinMemory = 0; + inOpenPolicy.mOpenPolicy_MaxLazy = 0; + + ret = mdbFactory->OpenFileStore(m_mdbEnv, dbHeap, + oldFile, &inOpenPolicy, &thumb); + } + else if (fileSize != 0) + ret = NS_ERROR_FILE_ACCESS_DENIED; + } + NS_RELEASE(oldFile); // always release our file ref, store has own + } + if (NS_FAILED(ret)) + ret = NS_ERROR_FILE_ACCESS_DENIED; + } + + if (NS_SUCCEEDED(ret) && thumb) + { + mdb_count outTotal; // total somethings to do in operation + mdb_count outCurrent; // subportion of total completed so far + mdb_bool outDone = false; // is operation finished? + mdb_bool outBroken; // is operation irreparably dead and broken? + do + { + ret = thumb->DoMore(m_mdbEnv, &outTotal, &outCurrent, &outDone, &outBroken); + if (NS_FAILED(ret)) + { + outDone = true; + break; + } + } + while (NS_SUCCEEDED(ret) && !outBroken && !outDone); + if (NS_SUCCEEDED(ret) && outDone) + { + ret = mdbFactory->ThumbToOpenStore(m_mdbEnv, thumb, &m_mdbStore); + if (NS_SUCCEEDED(ret) && m_mdbStore) + { + ret = InitExistingDB(); + create = false; + } + } + } + else if (create && ret != NS_ERROR_FILE_ACCESS_DENIED) + { + nsIMdbFile* newFile = 0; + ret = mdbFactory->CreateNewFile(m_mdbEnv, dbHeap, filePath.get(), &newFile); + if ( newFile ) + { + if (NS_SUCCEEDED(ret)) + { + mdbOpenPolicy inOpenPolicy; + + inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0; + inOpenPolicy.mOpenPolicy_MinMemory = 0; + inOpenPolicy.mOpenPolicy_MaxLazy = 0; + + ret = mdbFactory->CreateNewFileStore(m_mdbEnv, dbHeap, + newFile, &inOpenPolicy, + &m_mdbStore); + if (NS_SUCCEEDED(ret)) + ret = InitNewDB(); + } + NS_RELEASE(newFile); // always release our file ref, store has own + } + } + NS_IF_RELEASE(thumb); + } + return ret; +} + +NS_IMETHODIMP nsAddrDatabase::CloseMDB(bool commit) +{ + if (commit) + Commit(nsAddrDBCommitType::kSessionCommit); +//??? RemoveFromCache(this); // if we've closed it, better not leave it in the cache. + return NS_OK; +} + +// force the database to close - this'll flush out anybody holding onto +// a database without having a listener! +// This is evil in the com world, but there are times we need to delete the file. +NS_IMETHODIMP nsAddrDatabase::ForceClosed() +{ + nsresult err = NS_OK; + nsCOMPtr<nsIAddrDatabase> aDb(do_QueryInterface(this, &err)); + + // make sure someone has a reference so object won't get deleted out from under us. + AddRef(); + NotifyAnnouncerGoingAway(); + // OK, remove from cache first and close the store. + RemoveFromCache(this); + + err = CloseMDB(false); // since we're about to delete it, no need to commit. + NS_IF_RELEASE(m_mdbStore); + Release(); + return err; +} + +NS_IMETHODIMP nsAddrDatabase::Commit(uint32_t commitType) +{ + nsresult err = NS_OK; + nsIMdbThumb *commitThumb = nullptr; + + if (commitType == nsAddrDBCommitType::kLargeCommit || + commitType == nsAddrDBCommitType::kSessionCommit) + { + mdb_percent outActualWaste = 0; + mdb_bool outShould; + if (m_mdbStore && m_mdbEnv) + { + // check how much space would be saved by doing a compress commit. + // If it's more than 30%, go for it. + // N.B. - I'm not sure this calls works in Mork for all cases. + err = m_mdbStore->ShouldCompress(m_mdbEnv, 30, &outActualWaste, &outShould); + if (NS_SUCCEEDED(err) && outShould) + { + commitType = nsAddrDBCommitType::kCompressCommit; + } + } + } + + if (m_mdbStore && m_mdbEnv) + { + switch (commitType) + { + case nsAddrDBCommitType::kLargeCommit: + err = m_mdbStore->LargeCommit(m_mdbEnv, &commitThumb); + break; + case nsAddrDBCommitType::kSessionCommit: + // comment out until persistence works. + err = m_mdbStore->SessionCommit(m_mdbEnv, &commitThumb); + break; + case nsAddrDBCommitType::kCompressCommit: + err = m_mdbStore->CompressCommit(m_mdbEnv, &commitThumb); + break; + } + } + if (commitThumb && m_mdbEnv) + { + mdb_count outTotal = 0; // total somethings to do in operation + mdb_count outCurrent = 0; // subportion of total completed so far + mdb_bool outDone = false; // is operation finished? + mdb_bool outBroken = false; // is operation irreparably dead and broken? + while (!outDone && !outBroken && NS_SUCCEEDED(err)) + { + err = commitThumb->DoMore(m_mdbEnv, &outTotal, &outCurrent, &outDone, &outBroken); + } + NS_RELEASE(commitThumb); + } + // ### do something with error, but clear it now because mork errors out on commits. + if (m_mdbEnv) + m_mdbEnv->ClearErrors(); + return err; +} + +NS_IMETHODIMP nsAddrDatabase::Close(bool forceCommit /* = TRUE */) +{ + return CloseMDB(forceCommit); +} + +// set up empty tablesetc. +nsresult nsAddrDatabase::InitNewDB() +{ + nsresult err = InitMDBInfo(); + if (NS_SUCCEEDED(err)) + { + err = InitPabTable(); + err = InitLastRecorKey(); + Commit(nsAddrDBCommitType::kLargeCommit); + } + return err; +} + +nsresult nsAddrDatabase::AddRowToDeletedCardsTable(nsIAbCard *card, nsIMdbRow **pCardRow) +{ + if (!m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsresult rv = NS_OK; + if (!m_mdbDeletedCardsTable) + rv = InitDeletedCardsTable(true); + + if (NS_SUCCEEDED(rv)) { + // lets first purge old records if there are more than PURGE_CUTOFF_COUNT records + PurgeDeletedCardTable(); + nsCOMPtr<nsIMdbRow> cardRow; + rv = GetNewRow(getter_AddRefs(cardRow)); + if (NS_SUCCEEDED(rv) && cardRow) { + nsresult merror = m_mdbDeletedCardsTable->AddRow(m_mdbEnv, cardRow); + NS_ENSURE_SUCCESS(merror, NS_ERROR_FAILURE); + nsString unicodeStr; + card->GetFirstName(unicodeStr); + AddFirstName(cardRow, NS_ConvertUTF16toUTF8(unicodeStr).get()); + + card->GetLastName(unicodeStr); + AddLastName(cardRow, NS_ConvertUTF16toUTF8(unicodeStr).get()); + + card->GetDisplayName(unicodeStr); + AddDisplayName(cardRow, NS_ConvertUTF16toUTF8(unicodeStr).get()); + + card->GetPrimaryEmail(unicodeStr); + if (!unicodeStr.IsEmpty()) + AddUnicodeToColumn(cardRow, m_PriEmailColumnToken, m_LowerPriEmailColumnToken, unicodeStr.get()); + + card->GetPropertyAsAString(k2ndEmailProperty, unicodeStr); + if (!unicodeStr.IsEmpty()) + AddUnicodeToColumn(cardRow, m_2ndEmailColumnToken, m_Lower2ndEmailColumnToken, unicodeStr.get()); + + uint32_t nowInSeconds; + PRTime now = PR_Now(); + PRTime2Seconds(now, &nowInSeconds); + AddIntColumn(cardRow, m_LastModDateColumnToken, nowInSeconds); + + nsString value; + GetCardValue(card, CARD_ATTRIB_PALMID, getter_Copies(value)); + if (!value.IsEmpty()) + { + nsCOMPtr<nsIAbCard> addedCard; + rv = CreateCardFromDeletedCardsTable(cardRow, 0, getter_AddRefs(addedCard)); + if (NS_SUCCEEDED(rv)) + SetCardValue(addedCard, CARD_ATTRIB_PALMID, value.get(), false); + } + NS_IF_ADDREF(*pCardRow = cardRow); + } + Commit(nsAddrDBCommitType::kLargeCommit); + } + return rv; +} + +nsresult nsAddrDatabase::DeleteRowFromDeletedCardsTable(nsIMdbRow *pCardRow) +{ + if (!m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsresult merror = NS_OK; + if (m_mdbDeletedCardsTable) { + pCardRow->CutAllColumns(m_mdbEnv); + merror = m_mdbDeletedCardsTable->CutRow(m_mdbEnv, pCardRow); + } + return merror; +} + + +nsresult nsAddrDatabase::InitDeletedCardsTable(bool aCreate) +{ + nsresult mdberr = NS_OK; + if (!m_mdbDeletedCardsTable) + { + struct mdbOid deletedCardsTableOID; + deletedCardsTableOID.mOid_Scope = m_CardRowScopeToken; + deletedCardsTableOID.mOid_Id = ID_DELETEDCARDS_TABLE; + if (m_mdbStore && m_mdbEnv) + { + m_mdbStore->GetTable(m_mdbEnv, &deletedCardsTableOID, &m_mdbDeletedCardsTable); + // if deletedCardsTable does not exist and bCreate is set, create a new one + if (!m_mdbDeletedCardsTable && aCreate) + { + mdberr = (nsresult) m_mdbStore->NewTableWithOid(m_mdbEnv, &deletedCardsTableOID, + m_DeletedCardsTableKind, + true, (const mdbOid*)nullptr, + &m_mdbDeletedCardsTable); + } + } + } + return mdberr; +} + +nsresult nsAddrDatabase::InitPabTable() +{ + return m_mdbStore && m_mdbEnv ? m_mdbStore->NewTableWithOid(m_mdbEnv, + &gAddressBookTableOID, + m_PabTableKind, + false, + (const mdbOid*)nullptr, + &m_mdbPabTable) + : NS_ERROR_NULL_POINTER; +} + +//save the last record number, store in m_DataRowScopeToken, row 1 +nsresult nsAddrDatabase::InitLastRecorKey() +{ + if (!m_mdbPabTable || !m_mdbStore || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsIMdbRow *pDataRow = nullptr; + mdbOid dataRowOid; + dataRowOid.mOid_Scope = m_DataRowScopeToken; + dataRowOid.mOid_Id = DATAROW_ROWID; + nsresult err = m_mdbStore->NewRowWithOid(m_mdbEnv, &dataRowOid, &pDataRow); + + if (NS_SUCCEEDED(err) && pDataRow) + { + m_LastRecordKey = 0; + err = AddIntColumn(pDataRow, m_LastRecordKeyColumnToken, 0); + err = m_mdbPabTable->AddRow(m_mdbEnv, pDataRow); + NS_RELEASE(pDataRow); + } + return err; +} + +nsresult nsAddrDatabase::GetDataRow(nsIMdbRow **pDataRow) +{ + if (!m_mdbStore || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsIMdbRow *pRow = nullptr; + mdbOid dataRowOid; + dataRowOid.mOid_Scope = m_DataRowScopeToken; + dataRowOid.mOid_Id = DATAROW_ROWID; + m_mdbStore->GetRow(m_mdbEnv, &dataRowOid, &pRow); + *pDataRow = pRow; + + return pRow ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult nsAddrDatabase::GetLastRecordKey() +{ + if (!m_mdbPabTable) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr <nsIMdbRow> pDataRow; + nsresult err = GetDataRow(getter_AddRefs(pDataRow)); + + if (NS_SUCCEEDED(err) && pDataRow) + { + m_LastRecordKey = 0; + err = GetIntColumn(pDataRow, m_LastRecordKeyColumnToken, &m_LastRecordKey, 0); + if (NS_FAILED(err)) + err = NS_ERROR_NOT_AVAILABLE; + return NS_OK; + } + + return NS_ERROR_NOT_AVAILABLE; +} + +nsresult nsAddrDatabase::UpdateLastRecordKey() +{ + if (!m_mdbPabTable || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr <nsIMdbRow> pDataRow; + nsresult err = GetDataRow(getter_AddRefs(pDataRow)); + + if (NS_SUCCEEDED(err) && pDataRow) + { + err = AddIntColumn(pDataRow, m_LastRecordKeyColumnToken, m_LastRecordKey); + err = m_mdbPabTable->AddRow(m_mdbEnv, pDataRow); + return NS_OK; + } + else if (!pDataRow) + err = InitLastRecorKey(); + else + return NS_ERROR_NOT_AVAILABLE; + return err; +} + +nsresult nsAddrDatabase::InitExistingDB() +{ + nsresult err = InitMDBInfo(); + if (NS_SUCCEEDED(err)) + { + if (!m_mdbStore || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + err = m_mdbStore->GetTable(m_mdbEnv, &gAddressBookTableOID, &m_mdbPabTable); + if (NS_SUCCEEDED(err) && m_mdbPabTable) + { + err = GetLastRecordKey(); + if (err == NS_ERROR_NOT_AVAILABLE) + CheckAndUpdateRecordKey(); + UpdateLowercaseEmailListName(); + } + } + return err; +} + +nsresult nsAddrDatabase::CheckAndUpdateRecordKey() +{ + if (!m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsresult err = NS_OK; + nsIMdbTableRowCursor* rowCursor = nullptr; + nsIMdbRow* findRow = nullptr; + mdb_pos rowPos = 0; + + nsresult merror = m_mdbPabTable->GetTableRowCursor(m_mdbEnv, -1, &rowCursor); + + NS_ENSURE_TRUE(NS_SUCCEEDED(merror) && rowCursor, NS_ERROR_FAILURE); + + nsCOMPtr <nsIMdbRow> pDataRow; + err = GetDataRow(getter_AddRefs(pDataRow)); + if (NS_FAILED(err)) + InitLastRecorKey(); + + do + { //add key to each card and mailing list row + merror = rowCursor->NextRow(m_mdbEnv, &findRow, &rowPos); + if (NS_SUCCEEDED(merror) && findRow) + { + mdbOid rowOid; + + if (NS_SUCCEEDED(findRow->GetOid(m_mdbEnv, &rowOid))) + { + if (!IsDataRowScopeToken(rowOid.mOid_Scope)) + { + m_LastRecordKey++; + err = AddIntColumn(findRow, m_RecordKeyColumnToken, m_LastRecordKey); + } + } + } + } while (findRow); + + UpdateLastRecordKey(); + Commit(nsAddrDBCommitType::kLargeCommit); + return NS_OK; +} + +nsresult nsAddrDatabase::UpdateLowercaseEmailListName() +{ + if (!m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsresult err = NS_OK; + nsIMdbTableRowCursor* rowCursor = nullptr; + nsIMdbRow* findRow = nullptr; + mdb_pos rowPos = 0; + bool commitRequired = false; + + nsresult merror = m_mdbPabTable->GetTableRowCursor(m_mdbEnv, -1, &rowCursor); + + NS_ENSURE_TRUE(NS_SUCCEEDED(merror) && rowCursor, NS_ERROR_FAILURE); + + do + { // Add lowercase primary+secondary email to each card and mailing list row. + merror = rowCursor->NextRow(m_mdbEnv, &findRow, &rowPos); + if (NS_SUCCEEDED(merror) && findRow) + { + mdbOid rowOid; + + if (NS_SUCCEEDED(findRow->GetOid(m_mdbEnv, &rowOid))) + { + nsAutoString tempString; + if (IsCardRowScopeToken(rowOid.mOid_Scope)) + { + err = GetStringColumn(findRow, m_LowerPriEmailColumnToken, tempString); + if (NS_FAILED(err)) // not set yet + { + err = ConvertAndAddLowercaseColumn(findRow, m_PriEmailColumnToken, + m_LowerPriEmailColumnToken); + commitRequired = commitRequired || NS_SUCCEEDED(err); + } + + err = GetStringColumn(findRow, m_Lower2ndEmailColumnToken, tempString); + if (NS_FAILED(err)) // not set yet + { + err = ConvertAndAddLowercaseColumn(findRow, m_2ndEmailColumnToken, + m_Lower2ndEmailColumnToken); + commitRequired = commitRequired || NS_SUCCEEDED(err); + } + } + else if (IsListRowScopeToken(rowOid.mOid_Scope)) + { + err = GetStringColumn(findRow, m_LowerListNameColumnToken, tempString); + if (NS_SUCCEEDED(err)) // already set up + continue; + + err = ConvertAndAddLowercaseColumn(findRow, m_ListNameColumnToken, + m_LowerListNameColumnToken); + commitRequired = commitRequired || NS_SUCCEEDED(err); + } + } + findRow->Release(); + } + } while (findRow); + + if (findRow) + findRow->Release(); + rowCursor->Release(); + if (commitRequired) + Commit(nsAddrDBCommitType::kLargeCommit); + return NS_OK; +} + +/* +We store UTF8 strings in the database. We need to convert the UTF8 +string into unicode string, then convert to lower case. Before storing +back into the database, we need to convert the lowercase unicode string +into UTF8 string. +*/ +nsresult nsAddrDatabase::ConvertAndAddLowercaseColumn +(nsIMdbRow * row, mdb_token fromCol, mdb_token toCol) +{ + nsAutoString colString; + + nsresult rv = GetStringColumn(row, fromCol, colString); + if (!colString.IsEmpty()) + { + rv = AddLowercaseColumn(row, toCol, NS_ConvertUTF16toUTF8(colString).get()); + } + return rv; +} + +// Change the unicode string to lowercase, then convert to UTF8 string to store in db +nsresult nsAddrDatabase::AddUnicodeToColumn(nsIMdbRow * row, mdb_token aColToken, mdb_token aLowerCaseColToken, const char16_t* aUnicodeStr) +{ + nsresult rv = AddCharStringColumn(row, aColToken, NS_ConvertUTF16toUTF8(aUnicodeStr).get()); + NS_ENSURE_SUCCESS(rv,rv); + + rv = AddLowercaseColumn(row, aLowerCaseColToken, NS_ConvertUTF16toUTF8(aUnicodeStr).get()); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +// initialize the various tokens and tables in our db's env +nsresult nsAddrDatabase::InitMDBInfo() +{ + nsresult err = NS_OK; + + if (!m_mdbTokensInitialized && m_mdbStore && m_mdbEnv) + { + m_mdbTokensInitialized = true; + err = m_mdbStore->StringToToken(m_mdbEnv, kCardRowScope, &m_CardRowScopeToken); + err = m_mdbStore->StringToToken(m_mdbEnv, kListRowScope, &m_ListRowScopeToken); + err = m_mdbStore->StringToToken(m_mdbEnv, kDataRowScope, &m_DataRowScopeToken); + gAddressBookTableOID.mOid_Scope = m_CardRowScopeToken; + gAddressBookTableOID.mOid_Id = ID_PAB_TABLE; + if (NS_SUCCEEDED(err)) + { + m_mdbStore->StringToToken(m_mdbEnv, kFirstNameProperty, &m_FirstNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kLastNameProperty, &m_LastNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kPhoneticFirstNameProperty, &m_PhoneticFirstNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kPhoneticLastNameProperty, &m_PhoneticLastNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kDisplayNameProperty, &m_DisplayNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kNicknameProperty, &m_NickNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kPriEmailProperty, &m_PriEmailColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kLowerPriEmailColumn, &m_LowerPriEmailColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, k2ndEmailProperty, &m_2ndEmailColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kLower2ndEmailColumn, &m_Lower2ndEmailColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kPreferMailFormatProperty, &m_MailFormatColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kPopularityIndexProperty, &m_PopularityIndexColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kWorkPhoneProperty, &m_WorkPhoneColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kHomePhoneProperty, &m_HomePhoneColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kFaxProperty, &m_FaxColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kPagerProperty, &m_PagerColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kCellularProperty, &m_CellularColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kWorkPhoneTypeProperty, &m_WorkPhoneTypeColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kHomePhoneTypeProperty, &m_HomePhoneTypeColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kFaxTypeProperty, &m_FaxTypeColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kPagerTypeProperty, &m_PagerTypeColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kCellularTypeProperty, &m_CellularTypeColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kHomeAddressProperty, &m_HomeAddressColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kHomeAddress2Property, &m_HomeAddress2ColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kHomeCityProperty, &m_HomeCityColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kHomeStateProperty, &m_HomeStateColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kHomeZipCodeProperty, &m_HomeZipCodeColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kHomeCountryProperty, &m_HomeCountryColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kWorkAddressProperty, &m_WorkAddressColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kWorkAddress2Property, &m_WorkAddress2ColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kWorkCityProperty, &m_WorkCityColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kWorkStateProperty, &m_WorkStateColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kWorkZipCodeProperty, &m_WorkZipCodeColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kWorkCountryProperty, &m_WorkCountryColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kJobTitleProperty, &m_JobTitleColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kDepartmentProperty, &m_DepartmentColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kCompanyProperty, &m_CompanyColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kScreenNameProperty, &m_AimScreenNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kAnniversaryYearProperty, &m_AnniversaryYearColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kAnniversaryMonthProperty, &m_AnniversaryMonthColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kAnniversaryDayProperty, &m_AnniversaryDayColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kSpouseNameProperty, &m_SpouseNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kFamilyNameProperty, &m_FamilyNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kWorkWebPageProperty, &m_WebPage1ColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kHomeWebPageProperty, &m_WebPage2ColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kBirthYearProperty, &m_BirthYearColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kBirthMonthProperty, &m_BirthMonthColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kBirthDayProperty, &m_BirthDayColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kCustom1Property, &m_Custom1ColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kCustom2Property, &m_Custom2ColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kCustom3Property, &m_Custom3ColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kCustom4Property, &m_Custom4ColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kNotesProperty, &m_NotesColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kLastModifiedDateProperty, &m_LastModDateColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kRecordKeyColumn, &m_RecordKeyColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kAddressCharSetColumn, &m_AddressCharSetColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kLastRecordKeyColumn, &m_LastRecordKeyColumnToken); + + err = m_mdbStore->StringToToken(m_mdbEnv, kPabTableKind, &m_PabTableKind); + + m_mdbStore->StringToToken(m_mdbEnv, kMailListName, &m_ListNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kMailListNickName, &m_ListNickNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kMailListDescription, &m_ListDescriptionColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kMailListTotalAddresses, &m_ListTotalColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kLowerListNameColumn, &m_LowerListNameColumnToken); + m_mdbStore->StringToToken(m_mdbEnv, kDeletedCardsTableKind, &m_DeletedCardsTableKind); + } + } + return err; +} + +//////////////////////////////////////////////////////////////////////////////// + +nsresult nsAddrDatabase::AddRecordKeyColumnToRow(nsIMdbRow *pRow) +{ + if (pRow && m_mdbEnv) + { + m_LastRecordKey++; + nsresult err = AddIntColumn(pRow, m_RecordKeyColumnToken, m_LastRecordKey); + NS_ENSURE_SUCCESS(err, err); + + err = m_mdbPabTable->AddRow(m_mdbEnv, pRow); + UpdateLastRecordKey(); + return err; + } + return NS_ERROR_NULL_POINTER; +} + +nsresult nsAddrDatabase::AddAttributeColumnsToRow(nsIAbCard *card, nsIMdbRow *cardRow) +{ + nsresult rv = NS_OK; + + if ((!card && !cardRow) || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + mdbOid rowOid; + cardRow->GetOid(m_mdbEnv, &rowOid); + + card->SetPropertyAsUint32(kRowIDProperty, rowOid.mOid_Id); + + // add the row to the singleton table. + if (card && cardRow) + { + nsCOMPtr<nsISimpleEnumerator> properties; + rv = card->GetProperties(getter_AddRefs(properties)); + NS_ENSURE_SUCCESS(rv, rv); + + bool hasMore; + while (NS_SUCCEEDED(properties->HasMoreElements(&hasMore)) && hasMore) + { + nsCOMPtr<nsISupports> next; + rv = properties->GetNext(getter_AddRefs(next)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIProperty> prop = do_QueryInterface(next); + nsAutoString name; + prop->GetName(name); + + nsCOMPtr<nsIVariant> variant; + prop->GetValue(getter_AddRefs(variant)); + + // We can't get as a char * because that messes up UTF8 stuff + nsAutoCString value; + variant->GetAsAUTF8String(value); + + mdb_token token; + rv = m_mdbStore->StringToToken(m_mdbEnv, NS_ConvertUTF16toUTF8(name).get(), &token); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AddCharStringColumn(cardRow, token, value.get()); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Primary email is special: it is stored lowercase as well as in its + // original format. + nsAutoString primaryEmail; + card->GetPrimaryEmail(primaryEmail); + AddPrimaryEmail(cardRow, NS_ConvertUTF16toUTF8(primaryEmail).get()); + } + + return NS_OK; +} + +NS_IMETHODIMP nsAddrDatabase::CreateNewCardAndAddToDB(nsIAbCard *aNewCard, bool aNotify /* = FALSE */, nsIAbDirectory *aParent) +{ + nsCOMPtr <nsIMdbRow> cardRow; + + if (!aNewCard || !m_mdbPabTable || !m_mdbEnv || !m_mdbStore) + return NS_ERROR_NULL_POINTER; + + // Per the UUID requirements, we want to try to reuse the local id if at all + // possible. nsACString::ToInteger probably won't fail if the local id looks + // like "23bozo" (returning 23 instead), but it's okay since we aren't going + // to overwrite anything with 23 if it already exists and the id for the row + // doesn't matter otherwise. + nsresult rv; + + nsAutoCString id; + aNewCard->GetLocalId(id); + + mdbOid rowId; + rowId.mOid_Scope = m_CardRowScopeToken; + rowId.mOid_Id = id.ToInteger(&rv); + if (NS_SUCCEEDED(rv)) + { + // Mork is being very naughty here. If the table does not have the oid, we + // should be able to reuse it. To be on the safe side, however, we're going + // to reference the store's reference count. + mdb_count rowCount = 1; + m_mdbStore->GetRowRefCount(m_mdbEnv, &rowId, &rowCount); + if (rowCount == 0) + { + // So apparently, the row can have a count of 0 yet still exist (probably + // meaning we haven't flushed it out of memory). In this case, we need to + // get the row and cut its cells. + rv = m_mdbStore->GetRow(m_mdbEnv, &rowId, getter_AddRefs(cardRow)); + if (NS_SUCCEEDED(rv) && cardRow) + cardRow->CutAllColumns(m_mdbEnv); + else + rv = m_mdbStore->NewRowWithOid(m_mdbEnv, &rowId, getter_AddRefs(cardRow)); + } + } + + // If we don't have a cardRow yet, just get one with any ol' id. + if (!cardRow) + rv = GetNewRow(getter_AddRefs(cardRow)); + + if (NS_SUCCEEDED(rv) && cardRow) + { + AddAttributeColumnsToRow(aNewCard, cardRow); + AddRecordKeyColumnToRow(cardRow); + + // we need to do this for dnd + uint32_t key = 0; + rv = GetIntColumn(cardRow, m_RecordKeyColumnToken, &key, 0); + if (NS_SUCCEEDED(rv)) + aNewCard->SetPropertyAsUint32(kRecordKeyColumn, key); + + aNewCard->GetPropertyAsAUTF8String(kRowIDProperty, id); + aNewCard->SetLocalId(id); + + nsCOMPtr<nsIAbDirectory> abDir = do_QueryReferent(m_dbDirectory); + if (abDir) + abDir->GetUuid(id); + + aNewCard->SetDirectoryId(id); + + nsresult merror = m_mdbPabTable->AddRow(m_mdbEnv, cardRow); + NS_ENSURE_SUCCESS(merror, NS_ERROR_FAILURE); + } + else + return rv; + + // do notification + if (aNotify) + { + NotifyCardEntryChange(AB_NotifyInserted, aNewCard, aParent); + } + return rv; +} + +NS_IMETHODIMP nsAddrDatabase::CreateNewListCardAndAddToDB(nsIAbDirectory *aList, uint32_t listRowID, nsIAbCard *newCard, bool notify /* = FALSE */) +{ + if (!newCard || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsIMdbRow* pListRow = nullptr; + mdbOid listRowOid; + listRowOid.mOid_Scope = m_ListRowScopeToken; + listRowOid.mOid_Id = listRowID; + nsresult rv = m_mdbStore->GetRow(m_mdbEnv, &listRowOid, &pListRow); + NS_ENSURE_SUCCESS(rv,rv); + + if (!pListRow) + return NS_OK; + + nsCOMPtr<nsIMutableArray> addressList; + rv = aList->GetAddressLists(getter_AddRefs(addressList)); + NS_ENSURE_SUCCESS(rv,rv); + + uint32_t count; + addressList->GetLength(&count); + + nsAutoString newEmail; + rv = newCard->GetPrimaryEmail(newEmail); + NS_ENSURE_SUCCESS(rv,rv); + + uint32_t i; + for (i = 0; i < count; i++) { + nsCOMPtr<nsIAbCard> currentCard = do_QueryElementAt(addressList, i, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + bool equals; + rv = newCard->Equals(currentCard, &equals); + NS_ENSURE_SUCCESS(rv,rv); + + if (equals) { + // card is already in list, bail out. + // this can happen when dropping a card on a mailing list from the directory that contains the mailing list + return NS_OK; + } + + nsAutoString currentEmail; + rv = currentCard->GetPrimaryEmail(currentEmail); + NS_ENSURE_SUCCESS(rv,rv); + + if (newEmail.Equals(currentEmail)) { + // card is already in list, bail out + // this can happen when dropping a card on a mailing list from another directory (not the one that contains the mailing list + // or if you have multiple cards on a directory, with the same primary email address. + return NS_OK; + } + } + + // start from 1 + uint32_t totalAddress = GetListAddressTotal(pListRow) + 1; + SetListAddressTotal(pListRow, totalAddress); + nsCOMPtr<nsIAbCard> pNewCard; + rv = AddListCardColumnsToRow(newCard, pListRow, totalAddress, getter_AddRefs(pNewCard), true /* aInMailingList */, aList, nullptr); + NS_ENSURE_SUCCESS(rv,rv); + + addressList->AppendElement(newCard, false); + + if (notify) + NotifyCardEntryChange(AB_NotifyInserted, newCard, aList); + + return rv; +} + +NS_IMETHODIMP nsAddrDatabase::AddListCardColumnsToRow +(nsIAbCard *aPCard, nsIMdbRow *aPListRow, uint32_t aPos, nsIAbCard** aPNewCard, bool aInMailingList, nsIAbDirectory *aParent, nsIAbDirectory *aRoot) +{ + if (!aPCard || !aPListRow || !m_mdbStore || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsresult err = NS_OK; + nsString email; + aPCard->GetPrimaryEmail(email); + if (!email.IsEmpty()) + { + nsIMdbRow *pCardRow = nullptr; + // Please DO NOT change the 3rd param of GetRowFromAttribute() call to + // true (ie, case insensitive) without reading bugs #128535 and #121478. + err = GetRowFromAttribute(kPriEmailProperty, NS_ConvertUTF16toUTF8(email), + false /* retain case */, &pCardRow, nullptr); + bool cardWasAdded = false; + if (NS_FAILED(err) || !pCardRow) + { + //New Email, then add a new row with this email + err = GetNewRow(&pCardRow); + + if (NS_SUCCEEDED(err) && pCardRow) + { + AddPrimaryEmail(pCardRow, NS_ConvertUTF16toUTF8(email).get()); + err = m_mdbPabTable->AddRow(m_mdbEnv, pCardRow); + // Create a key for this row as well. + if (NS_SUCCEEDED(err)) + AddRecordKeyColumnToRow(pCardRow); + } + + cardWasAdded = true; + } + + NS_ENSURE_TRUE(pCardRow, NS_ERROR_NULL_POINTER); + + nsString name; + aPCard->GetDisplayName(name); + if (!name.IsEmpty()) { + AddDisplayName(pCardRow, NS_ConvertUTF16toUTF8(name).get()); + err = m_mdbPabTable->AddRow(m_mdbEnv, pCardRow); + } + + nsCOMPtr<nsIAbCard> newCard; + CreateABCard(pCardRow, 0, getter_AddRefs(newCard)); + NS_IF_ADDREF(*aPNewCard = newCard); + + if (cardWasAdded) { + NotifyCardEntryChange(AB_NotifyInserted, newCard, aParent); + if (aRoot) + NotifyCardEntryChange(AB_NotifyInserted, newCard, aRoot); + } + else if (!aInMailingList) { + nsresult rv; + nsCOMPtr<nsIAddrDBListener> parentListener(do_QueryInterface(aParent, &rv)); + + // Ensure the parent is in the listener list (and hence wants to be notified) + if (NS_SUCCEEDED(rv) && m_ChangeListeners.Contains(parentListener)) + parentListener->OnCardEntryChange(AB_NotifyInserted, aPCard, aParent); + } + else { + NotifyCardEntryChange(AB_NotifyPropertyChanged, aPCard, aParent); + } + + //add a column with address row id to the list row + mdb_token listAddressColumnToken; + + char columnStr[COLUMN_STR_MAX]; + PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, aPos); + m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken); + + mdbOid outOid; + + if (NS_SUCCEEDED(pCardRow->GetOid(m_mdbEnv, &outOid))) + { + //save address row ID to the list row + err = AddIntColumn(aPListRow, listAddressColumnToken, outOid.mOid_Id); + } + NS_RELEASE(pCardRow); + + } + + return NS_OK; +} + +nsresult nsAddrDatabase::AddListAttributeColumnsToRow(nsIAbDirectory *list, nsIMdbRow *listRow, nsIAbDirectory *aParent) +{ + nsresult err = NS_OK; + + if ((!list && !listRow) || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + mdbOid rowOid, tableOid; + m_mdbPabTable->GetOid(m_mdbEnv, &tableOid); + listRow->GetOid(m_mdbEnv, &rowOid); + + nsCOMPtr<nsIAbMDBDirectory> dblist(do_QueryInterface(list,&err)); + if (NS_SUCCEEDED(err)) + dblist->SetDbRowID(rowOid.mOid_Id); + + // add the row to the singleton table. + if (NS_SUCCEEDED(err) && listRow) + { + nsString unicodeStr; + + list->GetDirName(unicodeStr); + if (!unicodeStr.IsEmpty()) + AddUnicodeToColumn(listRow, m_ListNameColumnToken, m_LowerListNameColumnToken, unicodeStr.get()); + + list->GetListNickName(unicodeStr); + AddListNickName(listRow, NS_ConvertUTF16toUTF8(unicodeStr).get()); + + list->GetDescription(unicodeStr); + AddListDescription(listRow, NS_ConvertUTF16toUTF8(unicodeStr).get()); + + // XXX todo, this code has problems if you manually enter duplicate emails. + nsCOMPtr<nsIMutableArray> pAddressLists; + list->GetAddressLists(getter_AddRefs(pAddressLists)); + + uint32_t count; + pAddressLists->GetLength(&count); + + nsAutoString email; + uint32_t i, total; + total = 0; + for (i = 0; i < count; i++) + { + nsCOMPtr<nsIAbCard> pCard(do_QueryElementAt(pAddressLists, i, &err)); + + if (NS_FAILED(err)) + continue; + + pCard->GetPrimaryEmail(email); + if (!email.IsEmpty()) + total++; + } + SetListAddressTotal(listRow, total); + + uint32_t pos; + for (i = 0; i < count; i++) + { + nsCOMPtr<nsIAbCard> pCard(do_QueryElementAt(pAddressLists, i, &err)); + + if (NS_FAILED(err)) + continue; + + bool listHasCard = false; + err = list->HasCard(pCard, &listHasCard); + + // start from 1 + pos = i + 1; + pCard->GetPrimaryEmail(email); + if (!email.IsEmpty()) + { + nsCOMPtr<nsIAbCard> pNewCard; + err = AddListCardColumnsToRow(pCard, listRow, pos, getter_AddRefs(pNewCard), listHasCard, list, aParent); + if (pNewCard) + pAddressLists->ReplaceElementAt(pNewCard, i, false); + } + } + } + return NS_OK; +} + +uint32_t nsAddrDatabase::GetListAddressTotal(nsIMdbRow* listRow) +{ + uint32_t count = 0; + GetIntColumn(listRow, m_ListTotalColumnToken, &count, 0); + return count; +} + +NS_IMETHODIMP nsAddrDatabase::SetListAddressTotal(nsIMdbRow* aListRow, uint32_t aTotal) +{ + return AddIntColumn(aListRow, m_ListTotalColumnToken, aTotal); +} + +NS_IMETHODIMP nsAddrDatabase::FindRowByCard(nsIAbCard * aCard,nsIMdbRow **aRow) +{ + nsString primaryEmail; + aCard->GetPrimaryEmail(primaryEmail); + return GetRowForCharColumn(primaryEmail.get(), m_PriEmailColumnToken, + true, true, aRow, nullptr); +} + +nsresult nsAddrDatabase::GetAddressRowByPos(nsIMdbRow* listRow, uint16_t pos, nsIMdbRow** cardRow) +{ + if (!m_mdbStore || !listRow || !cardRow || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + mdb_token listAddressColumnToken; + + char columnStr[COLUMN_STR_MAX]; + PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, pos); + m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken); + + nsAutoString tempString; + mdb_id rowID; + nsresult err = GetIntColumn(listRow, listAddressColumnToken, (uint32_t*)&rowID, 0); + NS_ENSURE_SUCCESS(err, err); + + return GetCardRowByRowID(rowID, cardRow); +} + +NS_IMETHODIMP nsAddrDatabase::CreateMailListAndAddToDB(nsIAbDirectory *aNewList, bool aNotify /* = FALSE */, nsIAbDirectory *aParent) +{ + if (!aNewList || !m_mdbPabTable || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsIMdbRow *listRow; + nsresult err = GetNewListRow(&listRow); + + if (NS_SUCCEEDED(err) && listRow) + { + AddListAttributeColumnsToRow(aNewList, listRow, aParent); + AddRecordKeyColumnToRow(listRow); + nsresult merror = m_mdbPabTable->AddRow(m_mdbEnv, listRow); + NS_ENSURE_SUCCESS(merror, NS_ERROR_FAILURE); + + nsCOMPtr<nsIAbCard> listCard; + CreateABListCard(listRow, getter_AddRefs(listCard)); + NotifyCardEntryChange(AB_NotifyInserted, listCard, aParent); + + NS_RELEASE(listRow); + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +void nsAddrDatabase::DeleteCardFromAllMailLists(mdb_id cardRowID) +{ + if (!m_mdbEnv) + return; + + nsCOMPtr <nsIMdbTableRowCursor> rowCursor; + m_mdbPabTable->GetTableRowCursor(m_mdbEnv, -1, getter_AddRefs(rowCursor)); + + if (rowCursor) + { + nsCOMPtr <nsIMdbRow> pListRow; + mdb_pos rowPos; + do + { + nsresult err = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(pListRow), &rowPos); + + if (NS_SUCCEEDED(err) && pListRow) + { + mdbOid rowOid; + + if (NS_SUCCEEDED(pListRow->GetOid(m_mdbEnv, &rowOid))) + { + if (IsListRowScopeToken(rowOid.mOid_Scope)) + DeleteCardFromListRow(pListRow, cardRowID); + } + } + } while (pListRow); + } +} + +NS_IMETHODIMP nsAddrDatabase::DeleteCard(nsIAbCard *aCard, bool aNotify, nsIAbDirectory *aParent) +{ + if (!aCard || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsresult err = NS_OK; + bool bIsMailList = false; + aCard->GetIsMailList(&bIsMailList); + + // get the right row + nsIMdbRow* pCardRow = nullptr; + mdbOid rowOid; + + rowOid.mOid_Scope = bIsMailList ? m_ListRowScopeToken : m_CardRowScopeToken; + + err = aCard->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id); + NS_ENSURE_SUCCESS(err, err); + + err = m_mdbStore->GetRow(m_mdbEnv, &rowOid, &pCardRow); + NS_ENSURE_SUCCESS(err,err); + if (!pCardRow) + return NS_OK; + + // Reset the directory id + aCard->SetDirectoryId(EmptyCString()); + + // Add the deleted card to the deletedcards table + nsCOMPtr <nsIMdbRow> cardRow; + AddRowToDeletedCardsTable(aCard, getter_AddRefs(cardRow)); + err = DeleteRow(m_mdbPabTable, pCardRow); + + //delete the person card from all mailing list + if (!bIsMailList) + DeleteCardFromAllMailLists(rowOid.mOid_Id); + + if (NS_SUCCEEDED(err)) { + if (aNotify) + NotifyCardEntryChange(AB_NotifyDeleted, aCard, aParent); + } + else + DeleteRowFromDeletedCardsTable(cardRow); + + NS_RELEASE(pCardRow); + return NS_OK; +} + +nsresult nsAddrDatabase::DeleteCardFromListRow(nsIMdbRow* pListRow, mdb_id cardRowID) +{ + NS_ENSURE_ARG_POINTER(pListRow); + if (!m_mdbStore || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsresult err = NS_OK; + + uint32_t totalAddress = GetListAddressTotal(pListRow); + + uint32_t pos; + for (pos = 1; pos <= totalAddress; pos++) + { + mdb_token listAddressColumnToken; + mdb_id rowID; + + char columnStr[COLUMN_STR_MAX]; + PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, pos); + m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken); + + err = GetIntColumn(pListRow, listAddressColumnToken, (uint32_t*)&rowID, 0); + + if (cardRowID == rowID) + { + if (pos == totalAddress) + err = pListRow->CutColumn(m_mdbEnv, listAddressColumnToken); + else + { + //replace the deleted one with the last one and delete the last one + mdb_id lastRowID; + mdb_token lastAddressColumnToken; + PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, totalAddress); + m_mdbStore->StringToToken(m_mdbEnv, columnStr, &lastAddressColumnToken); + + err = GetIntColumn(pListRow, lastAddressColumnToken, (uint32_t*)&lastRowID, 0); + NS_ENSURE_SUCCESS(err, err); + + err = AddIntColumn(pListRow, listAddressColumnToken, lastRowID); + NS_ENSURE_SUCCESS(err, err); + + err = pListRow->CutColumn(m_mdbEnv, lastAddressColumnToken); + NS_ENSURE_SUCCESS(err, err); + } + + // Reset total count after the card has been deleted. + SetListAddressTotal(pListRow, totalAddress-1); + break; + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsAddrDatabase::DeleteCardFromMailList(nsIAbDirectory *mailList, nsIAbCard *card, bool aNotify) +{ + if (!card || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsresult err = NS_OK; + + // get the right row + nsIMdbRow* pListRow = nullptr; + mdbOid listRowOid; + listRowOid.mOid_Scope = m_ListRowScopeToken; + + nsCOMPtr<nsIAbMDBDirectory> dbmailList(do_QueryInterface(mailList,&err)); + NS_ENSURE_SUCCESS(err, err); + + dbmailList->GetDbRowID((uint32_t*)&listRowOid.mOid_Id); + + err = m_mdbStore->GetRow(m_mdbEnv, &listRowOid, &pListRow); + NS_ENSURE_SUCCESS(err,err); + if (!pListRow) + return NS_OK; + + uint32_t cardRowID; + + err = card->GetPropertyAsUint32(kRowIDProperty, &cardRowID); + if (NS_FAILED(err)) + return NS_ERROR_NULL_POINTER; + + err = DeleteCardFromListRow(pListRow, cardRowID); + if (NS_SUCCEEDED(err) && aNotify) { + NotifyCardEntryChange(AB_NotifyDeleted, card, mailList); + } + NS_RELEASE(pListRow); + return NS_OK; +} + +NS_IMETHODIMP nsAddrDatabase::SetCardValue(nsIAbCard *card, const char *name, const char16_t *value, bool notify) +{ + NS_ENSURE_ARG_POINTER(card); + NS_ENSURE_ARG_POINTER(name); + NS_ENSURE_ARG_POINTER(value); + if (!m_mdbStore || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsresult rv = NS_OK; + + nsCOMPtr <nsIMdbRow> cardRow; + mdbOid rowOid; + rowOid.mOid_Scope = m_CardRowScopeToken; + + // it might be that the caller always has a nsAbMDBCard + rv = card->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id); + NS_ENSURE_SUCCESS(rv, rv); + + rv = m_mdbStore->GetRow(m_mdbEnv, &rowOid, getter_AddRefs(cardRow)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!cardRow) + return NS_OK; + + mdb_token token; + rv = m_mdbStore->StringToToken(m_mdbEnv, name, &token); + NS_ENSURE_SUCCESS(rv, rv); + + return AddCharStringColumn(cardRow, token, NS_ConvertUTF16toUTF8(value).get()); +} + +NS_IMETHODIMP nsAddrDatabase::GetCardValue(nsIAbCard *card, const char *name, char16_t **value) +{ + if (!m_mdbStore || !card || !name || !value || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsresult rv = NS_OK; + + nsCOMPtr <nsIMdbRow> cardRow; + mdbOid rowOid; + rowOid.mOid_Scope = m_CardRowScopeToken; + + // it might be that the caller always has a nsAbMDBCard + rv = card->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id); + NS_ENSURE_SUCCESS(rv, rv); + + rv = m_mdbStore->GetRow(m_mdbEnv, &rowOid, getter_AddRefs(cardRow)); + NS_ENSURE_SUCCESS(rv, rv); + + if (!cardRow) { + *value = nullptr; + // this can happen when adding cards when editing a mailing list + return NS_OK; + } + + mdb_token token; + m_mdbStore->StringToToken(m_mdbEnv, name, &token); + + // XXX fix me + // avoid extra copying and allocations (did dmb already do this on the trunk?) + nsAutoString tempString; + rv = GetStringColumn(cardRow, token, tempString); + if (NS_FAILED(rv)) { + // not all cards are going this column + *value = nullptr; + return NS_OK; + } + + *value = NS_strdup(tempString.get()); + if (!*value) + return NS_ERROR_OUT_OF_MEMORY; + return NS_OK; +} + +NS_IMETHODIMP nsAddrDatabase::GetDeletedCardList(nsIArray **aResult) +{ + if (!m_mdbEnv || !aResult) + return NS_ERROR_NULL_POINTER; + + *aResult = nullptr; + + nsresult rv; + nsCOMPtr<nsIMutableArray> resultCardArray = + do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // make sure the member is set properly + InitDeletedCardsTable(false); + if (m_mdbDeletedCardsTable) + { + nsCOMPtr<nsIMdbTableRowCursor> rowCursor; + mdb_pos rowPos; + bool done = false; + nsCOMPtr<nsIMdbRow> currentRow; + + m_mdbDeletedCardsTable->GetTableRowCursor(m_mdbEnv, -1, getter_AddRefs(rowCursor)); + if (!rowCursor) + return NS_ERROR_FAILURE; + while (!done) + { + rv = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(currentRow), &rowPos); + if (currentRow && NS_SUCCEEDED(rv)) + { + mdbOid rowOid; + if (NS_SUCCEEDED(currentRow->GetOid(m_mdbEnv, &rowOid))) + { + nsCOMPtr<nsIAbCard> card; + rv = CreateCardFromDeletedCardsTable(currentRow, 0, getter_AddRefs(card)); + if (NS_SUCCEEDED(rv)) { + resultCardArray->AppendElement(card, false); + } + } + } + else + done = true; + } + } + + NS_IF_ADDREF(*aResult = resultCardArray); + + return NS_OK; +} + +NS_IMETHODIMP nsAddrDatabase::GetDeletedCardCount(uint32_t *aCount) +{ + // initialize count first + *aCount = 0; + InitDeletedCardsTable(false); + if (m_mdbDeletedCardsTable) + return m_mdbDeletedCardsTable->GetCount(m_mdbEnv, aCount); + return NS_OK; +} + +NS_IMETHODIMP nsAddrDatabase::PurgeDeletedCardTable() +{ + if (!m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + if (m_mdbDeletedCardsTable) { + mdb_count cardCount=0; + // if not too many cards let it be + m_mdbDeletedCardsTable->GetCount(m_mdbEnv, &cardCount); + if(cardCount < PURGE_CUTOFF_COUNT) + return NS_OK; + uint32_t purgeTimeInSec; + PRTime2Seconds(PR_Now(), &purgeTimeInSec); + purgeTimeInSec -= (182*24*60*60); // six months in seconds + nsCOMPtr<nsIMdbTableRowCursor> rowCursor; + nsresult rv = m_mdbDeletedCardsTable->GetTableRowCursor(m_mdbEnv, -1, getter_AddRefs(rowCursor)); + while(NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIMdbRow> currentRow; + mdb_pos rowPos; + rv = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(currentRow), &rowPos); + if(currentRow) { + uint32_t deletedTimeStamp = 0; + GetIntColumn(currentRow, m_LastModDateColumnToken, &deletedTimeStamp, 0); + // if record was deleted more than six months earlier, purge it + if(deletedTimeStamp && (deletedTimeStamp < purgeTimeInSec)) { + if(NS_SUCCEEDED(currentRow->CutAllColumns(m_mdbEnv))) + m_mdbDeletedCardsTable->CutRow(m_mdbEnv, currentRow); + } + else + // since the ordering in Mork is maintained and thus + // the cards added later appear on the top when retrieved + break; + } + else + break; // no more row + } + } + + return NS_OK; +} + +NS_IMETHODIMP nsAddrDatabase::EditCard(nsIAbCard *aCard, bool aNotify, nsIAbDirectory *aParent) +{ + // XXX make sure this isn't getting called when we're just editing one or two well known fields + if (!aCard || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsresult err = NS_OK; + + nsCOMPtr <nsIMdbRow> cardRow; + mdbOid rowOid; + rowOid.mOid_Scope = m_CardRowScopeToken; + + uint32_t nowInSeconds; + PRTime now = PR_Now(); + PRTime2Seconds(now, &nowInSeconds); + aCard->SetPropertyAsUint32(kLastModifiedDateProperty, nowInSeconds); + err = aCard->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id); + NS_ENSURE_SUCCESS(err, err); + + err = m_mdbStore->GetRow(m_mdbEnv, &rowOid, getter_AddRefs(cardRow)); + NS_ENSURE_SUCCESS(err, err); + + if (!cardRow) + return NS_OK; + + err = AddAttributeColumnsToRow(aCard, cardRow); + NS_ENSURE_SUCCESS(err, err); + + if (aNotify) + NotifyCardEntryChange(AB_NotifyPropertyChanged, aCard, aParent); + + return NS_OK; +} + +NS_IMETHODIMP nsAddrDatabase::ContainsCard(nsIAbCard *card, bool *hasCard) +{ + if (!card || !m_mdbPabTable || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsresult err = NS_OK; + mdb_bool hasOid; + mdbOid rowOid; + bool bIsMailList; + + card->GetIsMailList(&bIsMailList); + + if (bIsMailList) + rowOid.mOid_Scope = m_ListRowScopeToken; + else + rowOid.mOid_Scope = m_CardRowScopeToken; + + err = card->GetPropertyAsUint32(kRowIDProperty, &rowOid.mOid_Id); + NS_ENSURE_SUCCESS(err, err); + + err = m_mdbPabTable->HasOid(m_mdbEnv, &rowOid, &hasOid); + if (NS_SUCCEEDED(err)) + { + *hasCard = hasOid; + } + + return err; +} + +NS_IMETHODIMP nsAddrDatabase::DeleteMailList(nsIAbDirectory *aMailList, + nsIAbDirectory *aParent) +{ + if (!aMailList || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsresult err = NS_OK; + + // get the row + nsCOMPtr<nsIMdbRow> pListRow; + mdbOid rowOid; + rowOid.mOid_Scope = m_ListRowScopeToken; + + nsCOMPtr<nsIAbMDBDirectory> dbmailList(do_QueryInterface(aMailList, &err)); + NS_ENSURE_SUCCESS(err, err); + dbmailList->GetDbRowID((uint32_t*)&rowOid.mOid_Id); + + err = m_mdbStore->GetRow(m_mdbEnv, &rowOid, getter_AddRefs(pListRow)); + NS_ENSURE_SUCCESS(err,err); + + if (!pListRow) + return NS_OK; + + nsCOMPtr<nsIAbCard> card; + err = CreateABListCard(pListRow, getter_AddRefs(card)); + NS_ENSURE_SUCCESS(err, err); + + err = DeleteRow(m_mdbPabTable, pListRow); + + if (NS_SUCCEEDED(err) && aParent) + NotifyCardEntryChange(AB_NotifyDeleted, card, aParent); + + return err; +} + +NS_IMETHODIMP nsAddrDatabase::EditMailList(nsIAbDirectory *mailList, nsIAbCard *listCard, bool notify) +{ + if (!mailList || !m_mdbPabTable || !m_mdbStore || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsresult err = NS_OK; + + nsIMdbRow* pListRow = nullptr; + mdbOid rowOid; + rowOid.mOid_Scope = m_ListRowScopeToken; + + nsCOMPtr<nsIAbMDBDirectory> dbmailList(do_QueryInterface(mailList, &err)); + NS_ENSURE_SUCCESS(err, err); + dbmailList->GetDbRowID((uint32_t*)&rowOid.mOid_Id); + + err = m_mdbStore->GetRow(m_mdbEnv, &rowOid, &pListRow); + NS_ENSURE_SUCCESS(err, err); + + if (!pListRow) + return NS_OK; + + err = AddListAttributeColumnsToRow(mailList, pListRow, mailList); + NS_ENSURE_SUCCESS(err, err); + + if (notify) + { + NotifyListEntryChange(AB_NotifyPropertyChanged, mailList); + + if (listCard) + { + NotifyCardEntryChange(AB_NotifyPropertyChanged, listCard, mailList); + } + } + + NS_RELEASE(pListRow); + return NS_OK; +} + +NS_IMETHODIMP nsAddrDatabase::ContainsMailList(nsIAbDirectory *mailList, bool *hasList) +{ + if (!mailList || !m_mdbPabTable || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsresult err = NS_OK; + mdb_bool hasOid; + mdbOid rowOid; + + rowOid.mOid_Scope = m_ListRowScopeToken; + + nsCOMPtr<nsIAbMDBDirectory> dbmailList(do_QueryInterface(mailList,&err)); + NS_ENSURE_SUCCESS(err, err); + dbmailList->GetDbRowID((uint32_t*)&rowOid.mOid_Id); + + err = m_mdbPabTable->HasOid(m_mdbEnv, &rowOid, &hasOid); + if (NS_SUCCEEDED(err)) + *hasList = hasOid; + + return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsAddrDatabase::GetNewRow(nsIMdbRow * *newRow) +{ + if (!m_mdbStore || !newRow || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + return m_mdbStore->NewRow(m_mdbEnv, m_CardRowScopeToken, newRow); +} + +NS_IMETHODIMP nsAddrDatabase::GetNewListRow(nsIMdbRow * *newRow) +{ + if (!m_mdbStore || !newRow || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + return m_mdbStore->NewRow(m_mdbEnv, m_ListRowScopeToken, newRow); +} + +NS_IMETHODIMP nsAddrDatabase::AddCardRowToDB(nsIMdbRow *newRow) +{ + if (m_mdbPabTable && m_mdbEnv) + { + if (NS_SUCCEEDED(m_mdbPabTable->AddRow(m_mdbEnv, newRow))) + { + AddRecordKeyColumnToRow(newRow); + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsAddrDatabase::AddLdifListMember(nsIMdbRow* listRow, const char* value) +{ + if (!m_mdbStore || !listRow || !value || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + uint32_t total = GetListAddressTotal(listRow); + //add member + nsAutoCString valueString(value); + nsAutoCString email; + int32_t emailPos = valueString.Find("mail="); + emailPos += strlen("mail="); + email = Substring(valueString, emailPos); + nsCOMPtr <nsIMdbRow> cardRow; + // Please DO NOT change the 3rd param of GetRowFromAttribute() call to + // true (ie, case insensitive) without reading bugs #128535 and #121478. + nsresult rv = GetRowFromAttribute(kPriEmailProperty, email, false /* retain case */, + getter_AddRefs(cardRow), nullptr); + if (NS_SUCCEEDED(rv) && cardRow) + { + mdbOid outOid; + mdb_id rowID = 0; + if (NS_SUCCEEDED(cardRow->GetOid(m_mdbEnv, &outOid))) + rowID = outOid.mOid_Id; + + // start from 1 + total += 1; + mdb_token listAddressColumnToken; + char columnStr[COLUMN_STR_MAX]; + PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, total); + m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken); + + rv = AddIntColumn(listRow, listAddressColumnToken, rowID); + NS_ENSURE_SUCCESS(rv, rv); + + SetListAddressTotal(listRow, total); + } + return NS_OK; +} + + +void nsAddrDatabase::GetCharStringYarn(char* str, struct mdbYarn* strYarn) +{ + strYarn->mYarn_Grow = nullptr; + strYarn->mYarn_Buf = str; + strYarn->mYarn_Size = PL_strlen((const char *) strYarn->mYarn_Buf) + 1; + strYarn->mYarn_Fill = strYarn->mYarn_Size - 1; + strYarn->mYarn_Form = 0; +} + +void nsAddrDatabase::GetStringYarn(const nsAString & aStr, struct mdbYarn* strYarn) +{ + strYarn->mYarn_Buf = ToNewUTF8String(aStr); + strYarn->mYarn_Size = PL_strlen((const char *) strYarn->mYarn_Buf) + 1; + strYarn->mYarn_Fill = strYarn->mYarn_Size - 1; + strYarn->mYarn_Form = 0; +} + +void nsAddrDatabase::GetIntYarn(uint32_t nValue, struct mdbYarn* intYarn) +{ + intYarn->mYarn_Fill = intYarn->mYarn_Size; + intYarn->mYarn_Form = 0; + intYarn->mYarn_Grow = nullptr; + + PR_snprintf((char*)intYarn->mYarn_Buf, intYarn->mYarn_Size, "%lx", nValue); + intYarn->mYarn_Fill = PL_strlen((const char *) intYarn->mYarn_Buf); +} + +nsresult nsAddrDatabase::AddCharStringColumn(nsIMdbRow* cardRow, mdb_column inColumn, const char* str) +{ + if (!m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + struct mdbYarn yarn; + + GetCharStringYarn((char *) str, &yarn); + nsresult err = cardRow->AddColumn(m_mdbEnv, inColumn, &yarn); + + return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult nsAddrDatabase::AddStringColumn(nsIMdbRow* aCardRow, mdb_column aInColumn, const nsAString & aStr) +{ + if (!m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + struct mdbYarn yarn; + + GetStringYarn(aStr, &yarn); + nsresult err = aCardRow->AddColumn(m_mdbEnv, aInColumn, &yarn); + + return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult nsAddrDatabase::AddIntColumn(nsIMdbRow* cardRow, mdb_column inColumn, uint32_t nValue) +{ + if (!m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + struct mdbYarn yarn; + char yarnBuf[100]; + + yarn.mYarn_Buf = (void *) yarnBuf; + yarn.mYarn_Size = sizeof(yarnBuf); + GetIntYarn(nValue, &yarn); + nsresult err = cardRow->AddColumn(m_mdbEnv, inColumn, &yarn); + + return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult nsAddrDatabase::AddBoolColumn(nsIMdbRow* cardRow, mdb_column inColumn, bool bValue) +{ + if (!m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + struct mdbYarn yarn; + char yarnBuf[100]; + + yarn.mYarn_Buf = (void *) yarnBuf; + yarn.mYarn_Size = sizeof(yarnBuf); + + GetIntYarn(bValue ? 1 : 0, &yarn); + + nsresult err = cardRow->AddColumn(m_mdbEnv, inColumn, &yarn); + + return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE; +} + +nsresult nsAddrDatabase::GetStringColumn(nsIMdbRow *cardRow, mdb_token outToken, nsString& str) +{ + nsresult err = NS_ERROR_NULL_POINTER; + nsIMdbCell *cardCell; + + if (cardRow && m_mdbEnv) + { + err = cardRow->GetCell(m_mdbEnv, outToken, &cardCell); + if (NS_SUCCEEDED(err) && cardCell) + { + struct mdbYarn yarn; + cardCell->AliasYarn(m_mdbEnv, &yarn); + NS_ConvertUTF8toUTF16 uniStr((const char*) yarn.mYarn_Buf, yarn.mYarn_Fill); + if (!uniStr.IsEmpty()) + str.Assign(uniStr); + else + err = NS_ERROR_FAILURE; + cardCell->Release(); // always release ref + } + else + err = NS_ERROR_FAILURE; + } + return err; +} + +void nsAddrDatabase::YarnToUInt32(struct mdbYarn *yarn, uint32_t *pResult) +{ + uint8_t numChars = std::min<mdb_fill>(8, yarn->mYarn_Fill); + *pResult = MsgUnhex((char *) yarn->mYarn_Buf, numChars); +} + +nsresult nsAddrDatabase::GetIntColumn +(nsIMdbRow *cardRow, mdb_token outToken, uint32_t* pValue, uint32_t defaultValue) +{ + nsresult err = NS_ERROR_NULL_POINTER; + nsIMdbCell *cardCell; + + if (pValue) + *pValue = defaultValue; + if (cardRow && m_mdbEnv) + { + err = cardRow->GetCell(m_mdbEnv, outToken, &cardCell); + if (NS_SUCCEEDED(err) && cardCell) + { + struct mdbYarn yarn; + cardCell->AliasYarn(m_mdbEnv, &yarn); + YarnToUInt32(&yarn, pValue); + cardCell->Release(); + } + else + err = NS_ERROR_FAILURE; + } + return err; +} + +nsresult nsAddrDatabase::GetBoolColumn(nsIMdbRow *cardRow, mdb_token outToken, bool* pValue) +{ + NS_ENSURE_ARG_POINTER(pValue); + + nsresult err = NS_ERROR_NULL_POINTER; + nsIMdbCell *cardCell; + uint32_t nValue = 0; + + if (cardRow && m_mdbEnv) + { + err = cardRow->GetCell(m_mdbEnv, outToken, &cardCell); + if (NS_SUCCEEDED(err) && cardCell) + { + struct mdbYarn yarn; + cardCell->AliasYarn(m_mdbEnv, &yarn); + YarnToUInt32(&yarn, &nValue); + cardCell->Release(); + } + else + err = NS_ERROR_FAILURE; + } + + *pValue = nValue ? true : false; + return err; +} + +/* value is UTF8 string */ +NS_IMETHODIMP nsAddrDatabase::AddPrimaryEmail(nsIMdbRow *aRow, const char *aValue) +{ + NS_ENSURE_ARG_POINTER(aValue); + + nsresult rv = AddCharStringColumn(aRow, m_PriEmailColumnToken, aValue); + NS_ENSURE_SUCCESS(rv,rv); + + rv = AddLowercaseColumn(aRow, m_LowerPriEmailColumnToken, aValue); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +/* value is UTF8 string */ +NS_IMETHODIMP nsAddrDatabase::Add2ndEmail(nsIMdbRow *aRow, const char *aValue) +{ + NS_ENSURE_ARG_POINTER(aValue); + + nsresult rv = AddCharStringColumn(aRow, m_2ndEmailColumnToken, aValue); + NS_ENSURE_SUCCESS(rv,rv); + + rv = AddLowercaseColumn(aRow, m_Lower2ndEmailColumnToken, aValue); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +/* value is UTF8 string */ +NS_IMETHODIMP nsAddrDatabase::AddListName(nsIMdbRow *aRow, const char *aValue) +{ + NS_ENSURE_ARG_POINTER(aValue); + + nsresult rv = AddCharStringColumn(aRow, m_ListNameColumnToken, aValue); + NS_ENSURE_SUCCESS(rv,rv); + + rv = AddLowercaseColumn(aRow, m_LowerListNameColumnToken, aValue); + NS_ENSURE_SUCCESS(rv,rv); + return rv; +} + +/* +columnValue is UTF8 string, need to convert back to lowercase unicode then +back to UTF8 string +*/ +nsresult nsAddrDatabase::AddLowercaseColumn +(nsIMdbRow * row, mdb_token columnToken, const char* columnValue) +{ + nsresult rv = NS_OK; + if (columnValue) + { + NS_ConvertUTF8toUTF16 newUnicodeString(columnValue); + ToLowerCase(newUnicodeString); + rv = AddCharStringColumn(row, columnToken, NS_ConvertUTF16toUTF8(newUnicodeString).get()); + } + return rv; +} + +NS_IMETHODIMP nsAddrDatabase::InitCardFromRow(nsIAbCard *newCard, nsIMdbRow* cardRow) +{ + nsresult rv = NS_OK; + if (!newCard || !cardRow || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsCOMPtr<nsIMdbRowCellCursor> cursor; + nsCOMPtr<nsIMdbCell> cell; + + rv = cardRow->GetRowCellCursor(m_mdbEnv, -1, getter_AddRefs(cursor)); + NS_ENSURE_SUCCESS(rv, rv); + + mdb_column columnNumber; + char columnName[100]; + struct mdbYarn colYarn = {columnName, 0, sizeof(columnName), 0, 0, nullptr}; + struct mdbYarn cellYarn; + + do + { + rv = cursor->NextCell(m_mdbEnv, getter_AddRefs(cell), &columnNumber, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + + if (!cell) + break; + + // Get the value of the cell + cell->AliasYarn(m_mdbEnv, &cellYarn); + NS_ConvertUTF8toUTF16 value(static_cast<const char*>(cellYarn.mYarn_Buf), + cellYarn.mYarn_Fill); + + if (!value.IsEmpty()) + { + // Get the column of the cell + // Mork makes this so hard... + rv = m_mdbStore->TokenToString(m_mdbEnv, columnNumber, &colYarn); + NS_ENSURE_SUCCESS(rv, rv); + + char *name = PL_strndup(static_cast<char *>(colYarn.mYarn_Buf), + colYarn.mYarn_Fill); + newCard->SetPropertyAsAString(name, value); + PL_strfree(name); + } + } while (true); + + uint32_t key = 0; + rv = GetIntColumn(cardRow, m_RecordKeyColumnToken, &key, 0); + if (NS_SUCCEEDED(rv)) + newCard->SetPropertyAsUint32(kRecordKeyColumn, key); + + return NS_OK; +} + +nsresult nsAddrDatabase::GetListCardFromDB(nsIAbCard *listCard, nsIMdbRow* listRow) +{ + nsresult err = NS_OK; + if (!listCard || !listRow) + return NS_ERROR_NULL_POINTER; + + nsAutoString tempString; + + err = GetStringColumn(listRow, m_ListNameColumnToken, tempString); + if (NS_SUCCEEDED(err) && !tempString.IsEmpty()) + { + listCard->SetDisplayName(tempString); + listCard->SetLastName(tempString); + } + err = GetStringColumn(listRow, m_ListNickNameColumnToken, tempString); + if (NS_SUCCEEDED(err) && !tempString.IsEmpty()) + { + listCard->SetPropertyAsAString(kNicknameProperty, tempString); + } + err = GetStringColumn(listRow, m_ListDescriptionColumnToken, tempString); + if (NS_SUCCEEDED(err) && !tempString.IsEmpty()) + { + listCard->SetPropertyAsAString(kNotesProperty, tempString); + } + uint32_t key = 0; + err = GetIntColumn(listRow, m_RecordKeyColumnToken, &key, 0); + if (NS_SUCCEEDED(err)) + listCard->SetPropertyAsUint32(kRecordKeyColumn, key); + return err; +} + +nsresult nsAddrDatabase::GetListFromDB(nsIAbDirectory *newList, nsIMdbRow* listRow) +{ + nsresult err = NS_OK; + if (!newList || !listRow || !m_mdbStore || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsAutoString tempString; + + err = GetStringColumn(listRow, m_ListNameColumnToken, tempString); + if (NS_SUCCEEDED(err) && !tempString.IsEmpty()) + { + newList->SetDirName(tempString); + } + err = GetStringColumn(listRow, m_ListNickNameColumnToken, tempString); + if (NS_SUCCEEDED(err) && !tempString.IsEmpty()) + { + newList->SetListNickName(tempString); + } + err = GetStringColumn(listRow, m_ListDescriptionColumnToken, tempString); + if (NS_SUCCEEDED(err) && !tempString.IsEmpty()) + { + newList->SetDescription(tempString); + } + + nsCOMPtr<nsIAbMDBDirectory> dbnewList(do_QueryInterface(newList, &err)); + NS_ENSURE_SUCCESS(err, err); + + uint32_t totalAddress = GetListAddressTotal(listRow); + uint32_t pos; + for (pos = 1; pos <= totalAddress; ++pos) + { + mdb_token listAddressColumnToken; + mdb_id rowID; + + char columnStr[COLUMN_STR_MAX]; + PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, pos); + m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken); + + nsCOMPtr <nsIMdbRow> cardRow; + err = GetIntColumn(listRow, listAddressColumnToken, (uint32_t*)&rowID, 0); + NS_ENSURE_SUCCESS(err, err); + err = GetCardRowByRowID(rowID, getter_AddRefs(cardRow)); + NS_ENSURE_SUCCESS(err, err); + + if (cardRow) + { + nsCOMPtr<nsIAbCard> card; + err = CreateABCard(cardRow, 0, getter_AddRefs(card)); + + if(NS_SUCCEEDED(err)) + dbnewList->AddAddressToList(card); + } +// NS_IF_ADDREF(card); + } + + return err; +} + +class nsAddrDBEnumerator : public nsISimpleEnumerator, public nsIAddrDBListener +{ +public: + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator methods: + NS_DECL_NSISIMPLEENUMERATOR + NS_DECL_NSIADDRDBLISTENER + // nsAddrDBEnumerator methods: + + nsAddrDBEnumerator(nsAddrDatabase* aDb); + void Clear(); +protected: + virtual ~nsAddrDBEnumerator(); + RefPtr<nsAddrDatabase> mDb; + nsIMdbTable *mDbTable; + nsCOMPtr<nsIMdbTableRowCursor> mRowCursor; + nsCOMPtr<nsIMdbRow> mCurrentRow; + mdb_pos mRowPos; +}; + +nsAddrDBEnumerator::nsAddrDBEnumerator(nsAddrDatabase* aDb) + : mDb(aDb), + mDbTable(aDb->GetPabTable()), + mRowPos(-1) +{ + if (aDb) + aDb->AddListener(this); +} + +nsAddrDBEnumerator::~nsAddrDBEnumerator() +{ + Clear(); +} + +void nsAddrDBEnumerator::Clear() +{ + mRowCursor = nullptr; + mCurrentRow = nullptr; + mDbTable = nullptr; + if (mDb) + mDb->RemoveListener(this); +} + +NS_IMPL_ISUPPORTS(nsAddrDBEnumerator, nsISimpleEnumerator, nsIAddrDBListener) + +NS_IMETHODIMP +nsAddrDBEnumerator::HasMoreElements(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = false; + + if (!mDbTable || !mDb->GetEnv()) + { + return NS_ERROR_NULL_POINTER; + } + + nsCOMPtr<nsIMdbTableRowCursor> rowCursor; + mDbTable->GetTableRowCursor(mDb->GetEnv(), mRowPos, + getter_AddRefs(rowCursor)); + NS_ENSURE_TRUE(rowCursor, NS_ERROR_FAILURE); + + mdbOid rowOid; + rowCursor->NextRowOid(mDb->GetEnv(), &rowOid, nullptr); + while (rowOid.mOid_Id != (mdb_id)-1) + { + if (mDb->IsListRowScopeToken(rowOid.mOid_Scope) || + mDb->IsCardRowScopeToken(rowOid.mOid_Scope)) + { + *aResult = true; + + return NS_OK; + } + + if (!mDb->IsDataRowScopeToken(rowOid.mOid_Scope)) + { + return NS_ERROR_FAILURE; + } + + rowCursor->NextRowOid(mDb->GetEnv(), &rowOid, nullptr); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsAddrDBEnumerator::GetNext(nsISupports **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = nullptr; + + if (!mDbTable || !mDb->GetEnv()) + { + return NS_ERROR_NULL_POINTER; + } + + if (!mRowCursor) + { + mDbTable->GetTableRowCursor(mDb->GetEnv(), -1, + getter_AddRefs(mRowCursor)); + NS_ENSURE_TRUE(mRowCursor, NS_ERROR_FAILURE); + } + + nsCOMPtr<nsIAbCard> resultCard; + mRowCursor->NextRow(mDb->GetEnv(), getter_AddRefs(mCurrentRow), &mRowPos); + while (mCurrentRow) + { + mdbOid rowOid; + if (NS_SUCCEEDED(mCurrentRow->GetOid(mDb->GetEnv(), &rowOid))) + { + nsresult rv; + if (mDb->IsListRowScopeToken(rowOid.mOid_Scope)) + { + rv = mDb->CreateABListCard(mCurrentRow, + getter_AddRefs(resultCard)); + NS_ENSURE_SUCCESS(rv, rv); + } + else if (mDb->IsCardRowScopeToken(rowOid.mOid_Scope)) + { + rv = mDb->CreateABCard(mCurrentRow, 0, + getter_AddRefs(resultCard)); + NS_ENSURE_SUCCESS(rv, rv); + } + else if (!mDb->IsDataRowScopeToken(rowOid.mOid_Scope)) + { + return NS_ERROR_FAILURE; + } + + if (resultCard) + { + return CallQueryInterface(resultCard, aResult); + } + } + + mRowCursor->NextRow(mDb->GetEnv(), getter_AddRefs(mCurrentRow), + &mRowPos); + } + + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP nsAddrDBEnumerator::OnCardAttribChange(uint32_t abCode) +{ + return NS_OK; +} + +/* void onCardEntryChange (in unsigned long aAbCode, in nsIAbCard aCard, in nsIAbDirectory aParent); */ +NS_IMETHODIMP nsAddrDBEnumerator::OnCardEntryChange(uint32_t aAbCode, nsIAbCard *aCard, nsIAbDirectory *aParent) +{ + return NS_OK; +} + +/* void onListEntryChange (in unsigned long abCode, in nsIAbDirectory list); */ +NS_IMETHODIMP nsAddrDBEnumerator::OnListEntryChange(uint32_t abCode, nsIAbDirectory *list) +{ + return NS_OK; +} + +/* void onAnnouncerGoingAway (); */ +NS_IMETHODIMP nsAddrDBEnumerator::OnAnnouncerGoingAway() +{ + Clear(); + return NS_OK; +} + +class nsListAddressEnumerator final : public nsISimpleEnumerator +{ +public: + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator methods: + NS_DECL_NSISIMPLEENUMERATOR + + // nsListAddressEnumerator methods: + + nsListAddressEnumerator(nsAddrDatabase* aDb, mdb_id aRowID); + +protected: + ~nsListAddressEnumerator() {} + RefPtr<nsAddrDatabase> mDb; + nsIMdbTable *mDbTable; + nsCOMPtr<nsIMdbRow> mListRow; + mdb_id mListRowID; + uint32_t mAddressTotal; + uint16_t mAddressPos; +}; + +nsListAddressEnumerator::nsListAddressEnumerator(nsAddrDatabase* aDb, + mdb_id aRowID) + : mDb(aDb), + mDbTable(aDb->GetPabTable()), + mListRowID(aRowID), + mAddressPos(0) +{ + mDb->GetListRowByRowID(mListRowID, getter_AddRefs(mListRow)); + mAddressTotal = aDb->GetListAddressTotal(mListRow); +} + +NS_IMPL_ISUPPORTS(nsListAddressEnumerator, nsISimpleEnumerator) + +NS_IMETHODIMP +nsListAddressEnumerator::HasMoreElements(bool *aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = false; + + if (!mDbTable || !mDb->GetEnv()) + { + return NS_ERROR_NULL_POINTER; + } + + // In some cases it is possible that GetAddressRowByPos returns success, + // but currentRow is null. This is typically due to the fact that a card + // has been deleted from the parent and not the list. Whilst we have fixed + // that there are still a few dbs around there that we need to support + // correctly. Therefore, whilst processing lists ensure that we don't return + // false if the only thing stopping us is a blank row, just skip it and try + // the next one. + while (mAddressPos < mAddressTotal) + { + nsCOMPtr<nsIMdbRow> currentRow; + nsresult rv = mDb->GetAddressRowByPos(mListRow, mAddressPos + 1, + getter_AddRefs(currentRow)); + NS_ENSURE_SUCCESS(rv, rv); + + if (currentRow) + { + *aResult = true; + break; + } + + ++mAddressPos; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsListAddressEnumerator::GetNext(nsISupports **aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + *aResult = nullptr; + + if (!mDbTable || !mDb->GetEnv()) + { + return NS_ERROR_NULL_POINTER; + } + + if (++mAddressPos <= mAddressTotal) + { + nsCOMPtr<nsIMdbRow> currentRow; + nsresult rv = mDb->GetAddressRowByPos(mListRow, mAddressPos, + getter_AddRefs(currentRow)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbCard> resultCard; + rv = mDb->CreateABCard(currentRow, mListRowID, + getter_AddRefs(resultCard)); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(resultCard, aResult); + } + + return NS_ERROR_FAILURE; +} + +//////////////////////////////////////////////////////////////////////////////// + +NS_IMETHODIMP nsAddrDatabase::EnumerateCards(nsIAbDirectory *directory, nsISimpleEnumerator **result) +{ + nsAddrDBEnumerator* e = new nsAddrDBEnumerator(this); + m_dbDirectory = do_GetWeakReference(directory); + if (!e) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(e); + *result = e; + return NS_OK; +} + +NS_IMETHODIMP nsAddrDatabase::GetMailingListsFromDB(nsIAbDirectory *parentDir) +{ + nsCOMPtr<nsIAbDirectory> resultList; + nsIMdbTableRowCursor* rowCursor = nullptr; + nsCOMPtr<nsIMdbRow> currentRow; + mdb_pos rowPos; + bool done = false; + + if (!m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + m_dbDirectory = do_GetWeakReference(parentDir); + + nsIMdbTable* dbTable = GetPabTable(); + + if (!dbTable) + return NS_ERROR_FAILURE; + + dbTable->GetTableRowCursor(m_mdbEnv, -1, &rowCursor); + if (!rowCursor) + return NS_ERROR_FAILURE; + + while (!done) + { + nsresult rv = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(currentRow), &rowPos); + if (currentRow && NS_SUCCEEDED(rv)) + { + mdbOid rowOid; + + if (NS_SUCCEEDED(currentRow->GetOid(m_mdbEnv, &rowOid))) + { + if (IsListRowScopeToken(rowOid.mOid_Scope)) + rv = CreateABList(currentRow, getter_AddRefs(resultList)); + } + } + else + done = true; + } + NS_IF_RELEASE(rowCursor); + return NS_OK; +} + +NS_IMETHODIMP nsAddrDatabase::EnumerateListAddresses(nsIAbDirectory *directory, nsISimpleEnumerator **result) +{ + nsresult rv = NS_OK; + mdb_id rowID; + + nsCOMPtr<nsIAbMDBDirectory> dbdirectory(do_QueryInterface(directory,&rv)); + + if(NS_SUCCEEDED(rv)) + { + dbdirectory->GetDbRowID((uint32_t*)&rowID); + + nsListAddressEnumerator* e = new nsListAddressEnumerator(this, rowID); + m_dbDirectory = do_GetWeakReference(directory); + if (!e) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(e); + *result = e; + } + return rv; +} + +nsresult nsAddrDatabase::CreateCardFromDeletedCardsTable(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result) +{ + if (!cardRow || !m_mdbEnv || !result) + return NS_ERROR_NULL_POINTER; + + nsresult rv = NS_OK; + + mdbOid outOid; + mdb_id rowID = 0; + + if (NS_SUCCEEDED(cardRow->GetOid(m_mdbEnv, &outOid))) + rowID = outOid.mOid_Id; + + if(NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIAbCard> personCard; + personCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + InitCardFromRow(personCard, cardRow); + personCard->SetPropertyAsUint32(kRowIDProperty, rowID); + + NS_IF_ADDREF(*result = personCard); + } + + return rv; +} + +nsresult nsAddrDatabase::CreateCard(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result) +{ + if (!cardRow || !m_mdbEnv || !result) + return NS_ERROR_NULL_POINTER; + + nsresult rv = NS_OK; + + mdbOid outOid; + mdb_id rowID = 0; + + if (NS_SUCCEEDED(cardRow->GetOid(m_mdbEnv, &outOid))) + rowID = outOid.mOid_Id; + + if(NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIAbCard> personCard; + personCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + InitCardFromRow(personCard, cardRow); + personCard->SetPropertyAsUint32(kRowIDProperty, rowID); + + nsAutoCString id; + id.AppendInt(rowID); + personCard->SetLocalId(id); + + nsCOMPtr<nsIAbDirectory> abDir(do_QueryReferent(m_dbDirectory)); + if (abDir) + abDir->GetUuid(id); + + personCard->SetDirectoryId(id); + + NS_IF_ADDREF(*result = personCard); + } + + return rv; +} + +nsresult nsAddrDatabase::CreateABCard(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result) +{ + return CreateCard(cardRow, listRowID, result); +} + +/* create a card for mailing list in the address book */ +nsresult nsAddrDatabase::CreateABListCard(nsIMdbRow* listRow, nsIAbCard **result) +{ + if (!listRow || !m_mdbEnv || !result) + return NS_ERROR_NULL_POINTER; + + nsresult rv = NS_OK; + + mdbOid outOid; + mdb_id rowID = 0; + + if (NS_SUCCEEDED(listRow->GetOid(m_mdbEnv, &outOid))) + rowID = outOid.mOid_Id; + + char* listURI = nullptr; + + nsAutoString fileName; + rv = m_dbName->GetLeafName(fileName); + NS_ENSURE_SUCCESS(rv, rv); + listURI = PR_smprintf("%s%s/MailList%ld", kMDBDirectoryRoot, NS_ConvertUTF16toUTF8(fileName).get(), rowID); + + nsCOMPtr<nsIAbCard> personCard; + nsCOMPtr<nsIAbMDBDirectory> dbm_dbDirectory(do_QueryReferent(m_dbDirectory, + &rv)); + if (NS_SUCCEEDED(rv) && dbm_dbDirectory) + { + personCard = do_CreateInstance(NS_ABMDBCARD_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv,rv); + + if (personCard) + { + GetListCardFromDB(personCard, listRow); + + personCard->SetPropertyAsUint32(kRowIDProperty, rowID); + personCard->SetIsMailList(true); + personCard->SetMailListURI(listURI); + + nsAutoCString id; + id.AppendInt(rowID); + personCard->SetLocalId(id); + + nsCOMPtr<nsIAbDirectory> abDir(do_QueryReferent(m_dbDirectory)); + if (abDir) + abDir->GetUuid(id); + personCard->SetDirectoryId(id); + } + + NS_IF_ADDREF(*result = personCard); + } + if (listURI) + PR_smprintf_free(listURI); + + return rv; +} + +/* create a sub directory for mailing list in the address book left pane */ +nsresult nsAddrDatabase::CreateABList(nsIMdbRow* listRow, nsIAbDirectory **result) +{ + nsresult rv = NS_OK; + + if (!listRow || !m_mdbEnv || !result) + return NS_ERROR_NULL_POINTER; + + mdbOid outOid; + mdb_id rowID = 0; + + if (NS_SUCCEEDED(listRow->GetOid(m_mdbEnv, &outOid))) + rowID = outOid.mOid_Id; + + char* listURI = nullptr; + + nsAutoString fileName; + m_dbName->GetLeafName(fileName); + NS_ENSURE_SUCCESS(rv, rv); + + listURI = PR_smprintf("%s%s/MailList%ld", kMDBDirectoryRoot, NS_ConvertUTF16toUTF8(fileName).get(), rowID); + + nsCOMPtr<nsIAbDirectory> mailList; + nsCOMPtr<nsIAbMDBDirectory> dbm_dbDirectory(do_QueryReferent(m_dbDirectory, + &rv)); + if (NS_SUCCEEDED(rv) && dbm_dbDirectory) + { + rv = dbm_dbDirectory->AddDirectory(listURI, getter_AddRefs(mailList)); + + nsCOMPtr<nsIAbMDBDirectory> dbmailList (do_QueryInterface(mailList, &rv)); + + if (mailList) + { + // if we are using turbo, and we "exit" and restart with the same profile + // the current mailing list will still be in memory, so when we do + // GetResource() and QI, we'll get it again. + // in that scenario, the mailList that we pass in will already be + // be a mailing list, with a valid row and all the entries + // in that scenario, we can skip GetListFromDB(), which would have + // have added all the cards to the list again. + // see bug #134743 + mdb_id existingID; + dbmailList->GetDbRowID(&existingID); + if (existingID != rowID) { + // Ensure IsMailList is set up first. + mailList->SetIsMailList(true); + GetListFromDB(mailList, listRow); + dbmailList->SetDbRowID(rowID); + } + + dbm_dbDirectory->AddMailListToDirectory(mailList); + NS_IF_ADDREF(*result = mailList); + } + } + + if (listURI) + PR_smprintf_free(listURI); + + return rv; +} + +nsresult nsAddrDatabase::GetCardRowByRowID(mdb_id rowID, nsIMdbRow **dbRow) +{ + if (!m_mdbStore || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + mdbOid rowOid; + rowOid.mOid_Scope = m_CardRowScopeToken; + rowOid.mOid_Id = rowID; + + return m_mdbStore->GetRow(m_mdbEnv, &rowOid, dbRow); +} + +nsresult nsAddrDatabase::GetListRowByRowID(mdb_id rowID, nsIMdbRow **dbRow) +{ + if (!m_mdbStore || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + mdbOid rowOid; + rowOid.mOid_Scope = m_ListRowScopeToken; + rowOid.mOid_Id = rowID; + + return m_mdbStore->GetRow(m_mdbEnv, &rowOid, dbRow); +} + +nsresult nsAddrDatabase::GetRowFromAttribute(const char *aName, + const nsACString &aUTF8Value, + bool aCaseInsensitive, + nsIMdbRow **aCardRow, + mdb_pos *aRowPos) +{ + NS_ENSURE_ARG_POINTER(aName); + NS_ENSURE_ARG_POINTER(aCardRow); + if (!m_mdbStore || !m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + mdb_token token; + m_mdbStore->StringToToken(m_mdbEnv, aName, &token); + NS_ConvertUTF8toUTF16 newUnicodeString(aUTF8Value); + + return GetRowForCharColumn(newUnicodeString.get(), token, true, + aCaseInsensitive, aCardRow, aRowPos); +} + +NS_IMETHODIMP nsAddrDatabase::GetCardFromAttribute(nsIAbDirectory *aDirectory, + const char *aName, + const nsACString &aUTF8Value, + bool aCaseInsensitive, + nsIAbCard **aCardResult) +{ + NS_ENSURE_ARG_POINTER(aCardResult); + + m_dbDirectory = do_GetWeakReference(aDirectory); + nsCOMPtr<nsIMdbRow> cardRow; + if (NS_SUCCEEDED(GetRowFromAttribute(aName, aUTF8Value, aCaseInsensitive, + getter_AddRefs(cardRow), nullptr)) && cardRow) + return CreateABCard(cardRow, 0, aCardResult); + + *aCardResult = nullptr; + return NS_OK; +} + +NS_IMETHODIMP nsAddrDatabase::GetCardsFromAttribute(nsIAbDirectory *aDirectory, + const char *aName, + const nsACString & aUTF8Value, + bool aCaseInsensitive, + nsISimpleEnumerator **cards) +{ + NS_ENSURE_ARG_POINTER(cards); + + m_dbDirectory = do_GetWeakReference(aDirectory); + nsCOMPtr<nsIMdbRow> row; + bool done = false; + nsCOMArray<nsIAbCard> list; + nsCOMPtr<nsIAbCard> card; + mdb_pos rowPos = -1; + + do + { + if (NS_SUCCEEDED(GetRowFromAttribute(aName, aUTF8Value, aCaseInsensitive, + getter_AddRefs(row), &rowPos)) && row) + { + if (NS_FAILED(CreateABCard(row, 0, getter_AddRefs(card)))) + continue; + list.AppendObject(card); + } + else + done = true; + } while (!done); + + return NS_NewArrayEnumerator(cards, list); +} + +NS_IMETHODIMP nsAddrDatabase::AddListDirNode(nsIMdbRow * listRow) +{ + nsresult rv = NS_OK; + + nsCOMPtr<nsIAbManager> abManager(do_GetService(NS_ABMANAGER_CONTRACTID, &rv)); + + if (NS_SUCCEEDED(rv)) + { + nsAutoString parentURI; + rv = m_dbName->GetLeafName(parentURI); + NS_ENSURE_SUCCESS(rv, rv); + + parentURI.Replace(0, 0, NS_LITERAL_STRING(kMDBDirectoryRoot)); + + nsCOMPtr<nsIAbDirectory> parentDir; + rv = abManager->GetDirectory(NS_ConvertUTF16toUTF8(parentURI), + getter_AddRefs(parentDir)); + NS_ENSURE_SUCCESS(rv, rv); + + if (parentDir) + { + m_dbDirectory = do_GetWeakReference(parentDir); + nsCOMPtr<nsIAbDirectory> mailList; + rv = CreateABList(listRow, getter_AddRefs(mailList)); + if (mailList) + { + nsCOMPtr<nsIAbMDBDirectory> dbparentDir(do_QueryInterface(parentDir, &rv)); + if(NS_SUCCEEDED(rv)) + dbparentDir->NotifyDirItemAdded(mailList); + } + } + } + return rv; +} + +NS_IMETHODIMP nsAddrDatabase::FindMailListbyUnicodeName(const char16_t *listName, bool *exist) +{ + nsAutoString unicodeString(listName); + ToLowerCase(unicodeString); + + nsCOMPtr <nsIMdbRow> listRow; + nsresult rv = GetRowForCharColumn(unicodeString.get(), + m_LowerListNameColumnToken, false, + false, getter_AddRefs(listRow), nullptr); + *exist = (NS_SUCCEEDED(rv) && listRow); + return rv; +} + +NS_IMETHODIMP nsAddrDatabase::GetCardCount(uint32_t *count) +{ + nsresult rv; + mdb_count c; + rv = m_mdbPabTable->GetCount(m_mdbEnv, &c); + if (NS_SUCCEEDED(rv)) + *count = c - 1; // Don't count LastRecordKey + + return rv; +} + +bool +nsAddrDatabase::HasRowButDeletedForCharColumn(const char16_t *unicodeStr, mdb_column findColumn, bool aIsCard, nsIMdbRow **aFindRow) +{ + if (!m_mdbStore || !aFindRow || !m_mdbEnv) + return false; + + mdbYarn sourceYarn; + + NS_ConvertUTF16toUTF8 UTF8String(unicodeStr); + sourceYarn.mYarn_Buf = (void *) UTF8String.get(); + sourceYarn.mYarn_Fill = UTF8String.Length(); + sourceYarn.mYarn_Form = 0; + sourceYarn.mYarn_Size = sourceYarn.mYarn_Fill; + + mdbOid outRowId; + nsresult rv; + + if (aIsCard) + { + rv = m_mdbStore->FindRow(m_mdbEnv, m_CardRowScopeToken, + findColumn, &sourceYarn, &outRowId, aFindRow); + + // no such card, so bail out early + if (NS_FAILED(rv) || !*aFindRow) + return false; + + // we might not have loaded the "delete cards" table yet + // so do that (but don't create it, if we don't have one), + // so we can see if the row is really a delete card. + if (!m_mdbDeletedCardsTable) + rv = InitDeletedCardsTable(false); + + // if still no deleted cards table, there are no deleted cards + if (!m_mdbDeletedCardsTable) + return false; + + mdb_bool hasRow = false; + rv = m_mdbDeletedCardsTable->HasRow(m_mdbEnv, *aFindRow, &hasRow); + return (NS_SUCCEEDED(rv) && hasRow); + } + + rv = m_mdbStore->FindRow(m_mdbEnv, m_ListRowScopeToken, + findColumn, &sourceYarn, &outRowId, aFindRow); + return (NS_SUCCEEDED(rv) && *aFindRow); +} + +/* @param aRowPos Contains the row position for multiple calls. Should be + * instantiated to -1 on the first call. Or can be null + * if you are not making multiple calls. + */ +nsresult +nsAddrDatabase::GetRowForCharColumn(const char16_t *unicodeStr, + mdb_column findColumn, bool aIsCard, + bool aCaseInsensitive, + nsIMdbRow **aFindRow, + mdb_pos *aRowPos) +{ + NS_ENSURE_ARG_POINTER(unicodeStr); + NS_ENSURE_ARG_POINTER(aFindRow); + NS_ENSURE_TRUE(m_mdbEnv && m_mdbPabTable, NS_ERROR_NULL_POINTER); + + *aFindRow = nullptr; + + // see bug #198303 + // the addition of the m_mdbDeletedCardsTable table has complicated life in the addressbook + // (it was added for palm sync). until we fix the underlying problem, we have to jump through hoops + // in order to know if we have a row (think card) for a given column value (think email=foo@bar.com) + // there are 4 scenarios: + // 1) no cards with a match + // 2) at least one deleted card with a match, but no non-deleted cards + // 3) at least one non-deleted card with a match, but no deleted cards + // 4) at least one deleted card, and one non-deleted card with a match. + // + // if we have no cards that match (FindRow() returns nothing), we can bail early + // but if FindRow() returns something, we have to check if it is in the deleted table + // if not in the deleted table we can return the row (we found a non-deleted card) + // but if so, we have to search through the table of non-deleted cards + // for a match. If we find one, we return it. but if not, we report that there are no + // non-deleted cards. This is the expensive part. The worse case scenario is to have + // deleted lots of cards, and then have a lot of non-deleted cards. + // we'd have to call FindRow(), HasRow(), and then search the list of non-deleted cards + // each time we call GetRowForCharColumn(). + if (!aRowPos && !HasRowButDeletedForCharColumn(unicodeStr, findColumn, aIsCard, aFindRow)) + { + // If we have a row, it's the row for the non-delete card, so return NS_OK. + // If we don't have a row, there are two possible conditions: either the + // card does not exist, or we are doing case-insensitive searching and the + // value isn't lowercase. + + // Valid result, return. + if (*aFindRow) + return NS_OK; + + // We definitely don't have anything at this point if case-sensitive. + if (!aCaseInsensitive) + return NS_ERROR_FAILURE; + } + + // check if there is a non-deleted card + nsCOMPtr<nsIMdbTableRowCursor> rowCursor; + mdb_pos rowPos = -1; + bool done = false; + nsCOMPtr<nsIMdbRow> currentRow; + nsAutoString columnValue; + + if (aRowPos) + rowPos = *aRowPos; + + mdb_scope targetScope = aIsCard ? m_CardRowScopeToken : m_ListRowScopeToken; + + m_mdbPabTable->GetTableRowCursor(m_mdbEnv, rowPos, getter_AddRefs(rowCursor)); + if (!rowCursor) + return NS_ERROR_FAILURE; + + while (!done) + { + nsresult rv = rowCursor->NextRow(m_mdbEnv, getter_AddRefs(currentRow), &rowPos); + if (currentRow && NS_SUCCEEDED(rv)) + { + mdbOid rowOid; + if (NS_SUCCEEDED(currentRow->GetOid(m_mdbEnv, &rowOid)) && (rowOid.mOid_Scope == targetScope)) + { + rv = GetStringColumn(currentRow, findColumn, columnValue); + + bool equals = aCaseInsensitive ? + columnValue.Equals(unicodeStr, nsCaseInsensitiveStringComparator()) : + columnValue.Equals(unicodeStr); + + if (NS_SUCCEEDED(rv) && equals) + { + NS_IF_ADDREF(*aFindRow = currentRow); + if (aRowPos) + *aRowPos = rowPos; + return NS_OK; + } + } + } + else + done = true; + } + return NS_ERROR_FAILURE; +} + +nsresult nsAddrDatabase::DeleteRow(nsIMdbTable* dbTable, nsIMdbRow* dbRow) +{ + if (!m_mdbEnv) + return NS_ERROR_NULL_POINTER; + + nsresult err = dbRow->CutAllColumns(m_mdbEnv); + err = dbTable->CutRow(m_mdbEnv, dbRow); + + return (NS_SUCCEEDED(err)) ? NS_OK : NS_ERROR_FAILURE; +} diff --git a/mailnews/addrbook/src/nsAddrDatabase.h b/mailnews/addrbook/src/nsAddrDatabase.h new file mode 100644 index 000000000..3b4e4eee6 --- /dev/null +++ b/mailnews/addrbook/src/nsAddrDatabase.h @@ -0,0 +1,439 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef _nsAddrDatabase_H_ +#define _nsAddrDatabase_H_ + +#include "mozilla/Attributes.h" +#include "nsIAddrDatabase.h" +#include "mdb.h" +#include "nsStringGlue.h" +#include "nsIAddrDBListener.h" +#include "nsCOMPtr.h" +#include "nsTObserverArray.h" +#include "nsWeakPtr.h" +#include "nsIWeakReferenceUtils.h" + +typedef enum +{ + AB_NotifyInserted, + AB_NotifyDeleted, + AB_NotifyPropertyChanged +} AB_NOTIFY_CODE; + +class nsAddrDatabase : public nsIAddrDatabase +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIADDRDBANNOUNCER + ////////////////////////////////////////////////////////////////////////////// + // nsIAddrDatabase methods: + + NS_IMETHOD GetDbPath(nsIFile * *aDbPath) override; + NS_IMETHOD SetDbPath(nsIFile * aDbPath) override; + NS_IMETHOD Open(nsIFile *aMabFile, bool aCreate, bool upgrading, nsIAddrDatabase **pCardDB) override; + NS_IMETHOD Close(bool forceCommit) override; + NS_IMETHOD OpenMDB(nsIFile *dbName, bool create) override; + NS_IMETHOD CloseMDB(bool commit) override; + NS_IMETHOD Commit(uint32_t commitType) override; + NS_IMETHOD ForceClosed() override; + + NS_IMETHOD CreateNewCardAndAddToDB(nsIAbCard *newCard, bool notify, nsIAbDirectory *parent) override; + NS_IMETHOD CreateNewListCardAndAddToDB(nsIAbDirectory *list, uint32_t listRowID, nsIAbCard *newCard, bool notify) override; + NS_IMETHOD CreateMailListAndAddToDB(nsIAbDirectory *newList, bool notify, nsIAbDirectory *parent) override; + NS_IMETHOD EnumerateCards(nsIAbDirectory *directory, nsISimpleEnumerator **result) override; + NS_IMETHOD GetMailingListsFromDB(nsIAbDirectory *parentDir) override; + NS_IMETHOD EnumerateListAddresses(nsIAbDirectory *directory, nsISimpleEnumerator **result) override; + NS_IMETHOD DeleteCard(nsIAbCard *newCard, bool notify, nsIAbDirectory *parent) override; + NS_IMETHOD EditCard(nsIAbCard *card, bool notify, nsIAbDirectory *parent) override; + NS_IMETHOD ContainsCard(nsIAbCard *card, bool *hasCard) override; + NS_IMETHOD DeleteMailList(nsIAbDirectory *aMailList, nsIAbDirectory *aParent) override; + NS_IMETHOD EditMailList(nsIAbDirectory *mailList, nsIAbCard *listCard, bool notify) override; + NS_IMETHOD ContainsMailList(nsIAbDirectory *mailList, bool *hasCard) override; + NS_IMETHOD DeleteCardFromMailList(nsIAbDirectory *mailList, nsIAbCard *card, bool aNotify) override; + NS_IMETHOD GetCardFromAttribute(nsIAbDirectory *aDirectory, const char *aName, + const nsACString &aValue, + bool aCaseInsensitive, nsIAbCard **card) override; + NS_IMETHOD GetCardsFromAttribute(nsIAbDirectory *aDirectory, + const char *aName, + const nsACString & uUTF8Value, + bool aCaseInsensitive, + nsISimpleEnumerator **cards) override; + NS_IMETHOD GetNewRow(nsIMdbRow * *newRow) override; + NS_IMETHOD GetNewListRow(nsIMdbRow * *newRow) override; + NS_IMETHOD AddCardRowToDB(nsIMdbRow *newRow) override; + NS_IMETHOD AddLdifListMember(nsIMdbRow* row, const char * value) override; + + NS_IMETHOD GetDeletedCardList(nsIArray **aResult) override; + NS_IMETHOD GetDeletedCardCount(uint32_t *aCount) override; + NS_IMETHOD PurgeDeletedCardTable(); + + NS_IMETHOD AddFirstName(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_FirstNameColumnToken, value); } + + NS_IMETHOD AddLastName(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_LastNameColumnToken, value); } + + NS_IMETHOD AddPhoneticFirstName(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_PhoneticFirstNameColumnToken, value); } + + NS_IMETHOD AddPhoneticLastName(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_PhoneticLastNameColumnToken, value); } + + NS_IMETHOD AddDisplayName(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_DisplayNameColumnToken, value); } + + NS_IMETHOD AddNickName(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_NickNameColumnToken, value); } + + NS_IMETHOD AddPrimaryEmail(nsIMdbRow * row, const char * value) override; + + NS_IMETHOD Add2ndEmail(nsIMdbRow * row, const char * value) override; + + NS_IMETHOD AddPreferMailFormat(nsIMdbRow * row, uint32_t value) override + { return AddIntColumn(row, m_MailFormatColumnToken, value); } + + NS_IMETHOD AddPopularityIndex(nsIMdbRow * row, uint32_t value) override + { return AddIntColumn(row, m_PopularityIndexColumnToken, value); } + + NS_IMETHOD AddWorkPhone(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_WorkPhoneColumnToken, value); } + + NS_IMETHOD AddHomePhone(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_HomePhoneColumnToken, value); } + + NS_IMETHOD AddFaxNumber(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_FaxColumnToken, value); } + + NS_IMETHOD AddPagerNumber(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_PagerColumnToken, value); } + + NS_IMETHOD AddCellularNumber(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_CellularColumnToken, value); } + + NS_IMETHOD AddWorkPhoneType(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_WorkPhoneTypeColumnToken, value); } + + NS_IMETHOD AddHomePhoneType(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_HomePhoneTypeColumnToken, value); } + + NS_IMETHOD AddFaxNumberType(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_FaxTypeColumnToken, value); } + + NS_IMETHOD AddPagerNumberType(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_PagerTypeColumnToken, value); } + + NS_IMETHOD AddCellularNumberType(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_CellularTypeColumnToken, value); } + + NS_IMETHOD AddHomeAddress(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_HomeAddressColumnToken, value); } + + NS_IMETHOD AddHomeAddress2(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_HomeAddress2ColumnToken, value); } + + NS_IMETHOD AddHomeCity(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_HomeCityColumnToken, value); } + + NS_IMETHOD AddHomeState(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_HomeStateColumnToken, value); } + + NS_IMETHOD AddHomeZipCode(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_HomeZipCodeColumnToken, value); } + + NS_IMETHOD AddHomeCountry(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_HomeCountryColumnToken, value); } + + NS_IMETHOD AddWorkAddress(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_WorkAddressColumnToken, value); } + + NS_IMETHOD AddWorkAddress2(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_WorkAddress2ColumnToken, value); } + + NS_IMETHOD AddWorkCity(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_WorkCityColumnToken, value); } + + NS_IMETHOD AddWorkState(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_WorkStateColumnToken, value); } + + NS_IMETHOD AddWorkZipCode(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_WorkZipCodeColumnToken, value); } + + NS_IMETHOD AddWorkCountry(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_WorkCountryColumnToken, value); } + + NS_IMETHOD AddJobTitle(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_JobTitleColumnToken, value); } + + NS_IMETHOD AddDepartment(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_DepartmentColumnToken, value); } + + NS_IMETHOD AddCompany(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_CompanyColumnToken, value); } + + NS_IMETHOD AddAimScreenName(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_AimScreenNameColumnToken, value); } + + NS_IMETHOD AddAnniversaryYear(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_AnniversaryYearColumnToken, value); } + + NS_IMETHOD AddAnniversaryMonth(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_AnniversaryMonthColumnToken, value); } + + NS_IMETHOD AddAnniversaryDay(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_AnniversaryDayColumnToken, value); } + + NS_IMETHOD AddSpouseName(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_SpouseNameColumnToken, value); } + + NS_IMETHOD AddFamilyName(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_FamilyNameColumnToken, value); } + + NS_IMETHOD AddDefaultAddress(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_DefaultAddressColumnToken, value); } + + NS_IMETHOD AddCategory(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_CategoryColumnToken, value); } + + NS_IMETHOD AddWebPage1(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_WebPage1ColumnToken, value); } + + NS_IMETHOD AddWebPage2(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_WebPage2ColumnToken, value); } + + NS_IMETHOD AddBirthYear(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_BirthYearColumnToken, value); } + + NS_IMETHOD AddBirthMonth(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_BirthMonthColumnToken, value); } + + NS_IMETHOD AddBirthDay(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_BirthDayColumnToken, value); } + + NS_IMETHOD AddCustom1(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_Custom1ColumnToken, value); } + + NS_IMETHOD AddCustom2(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_Custom2ColumnToken, value); } + + NS_IMETHOD AddCustom3(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_Custom3ColumnToken, value); } + + NS_IMETHOD AddCustom4(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_Custom4ColumnToken, value); } + + NS_IMETHOD AddNotes(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_NotesColumnToken, value); } + + NS_IMETHOD AddListName(nsIMdbRow * row, const char * value) override; + + NS_IMETHOD AddListNickName(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_ListNickNameColumnToken, value); } + + NS_IMETHOD AddListDescription(nsIMdbRow * row, const char * value) override + { return AddCharStringColumn(row, m_ListDescriptionColumnToken, value); } + + + NS_IMETHOD AddListDirNode(nsIMdbRow * listRow) override; + + NS_IMETHOD FindMailListbyUnicodeName(const char16_t *listName, bool *exist) override; + + NS_IMETHOD GetCardCount(uint32_t *count) override; + + NS_IMETHOD SetCardValue(nsIAbCard *card, const char *name, const char16_t *value, bool notify) override; + NS_IMETHOD GetCardValue(nsIAbCard *card, const char *name, char16_t **value) override; + // nsAddrDatabase methods: + + nsAddrDatabase(); + + void GetMDBFactory(nsIMdbFactory ** aMdbFactory); + nsIMdbEnv *GetEnv() {return m_mdbEnv;} + uint32_t GetCurVersion(); + nsIMdbTableRowCursor *GetTableRowCursor(); + nsIMdbTable *GetPabTable() {return m_mdbPabTable;} + + static nsAddrDatabase* FindInCache(nsIFile *dbName); + + static void CleanupCache(); + + nsresult CreateABCard(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result); + nsresult CreateABListCard(nsIMdbRow* listRow, nsIAbCard **result); + nsresult CreateABList(nsIMdbRow* listRow, nsIAbDirectory **result); + + bool IsListRowScopeToken(mdb_scope scope) { return (scope == m_ListRowScopeToken) ? true: false; } + bool IsCardRowScopeToken(mdb_scope scope) { return (scope == m_CardRowScopeToken) ? true: false; } + bool IsDataRowScopeToken(mdb_scope scope) { return (scope == m_DataRowScopeToken) ? true: false; } + nsresult GetCardRowByRowID(mdb_id rowID, nsIMdbRow **dbRow); + nsresult GetListRowByRowID(mdb_id rowID, nsIMdbRow **dbRow); + + uint32_t GetListAddressTotal(nsIMdbRow* listRow); + nsresult GetAddressRowByPos(nsIMdbRow* listRow, uint16_t pos, nsIMdbRow** cardRow); + + NS_IMETHOD AddListCardColumnsToRow(nsIAbCard *aPCard, nsIMdbRow *aPListRow, uint32_t aPos, nsIAbCard** aPNewCard, bool aInMailingList, nsIAbDirectory *aParent, nsIAbDirectory *aRoot) override; + NS_IMETHOD InitCardFromRow(nsIAbCard *aNewCard, nsIMdbRow* aCardRow) override; + NS_IMETHOD SetListAddressTotal(nsIMdbRow* aListRow, uint32_t aTotal) override; + NS_IMETHOD FindRowByCard(nsIAbCard * card,nsIMdbRow **aRow) override; + +protected: + virtual ~nsAddrDatabase(); + + static void RemoveFromCache(nsAddrDatabase* pAddrDB); + bool MatchDbName(nsIFile *dbName); // returns TRUE if they match + + void YarnToUInt32(struct mdbYarn *yarn, uint32_t *pResult); + void GetCharStringYarn(char* str, struct mdbYarn* strYarn); + void GetStringYarn(const nsAString & aStr, struct mdbYarn* strYarn); + void GetIntYarn(uint32_t nValue, struct mdbYarn* intYarn); + nsresult AddCharStringColumn(nsIMdbRow* cardRow, mdb_column inColumn, const char* str); + nsresult AddStringColumn(nsIMdbRow* aCardRow, mdb_column aInColumn, const nsAString & aStr); + nsresult AddIntColumn(nsIMdbRow* cardRow, mdb_column inColumn, uint32_t nValue); + nsresult AddBoolColumn(nsIMdbRow* cardRow, mdb_column inColumn, bool bValue); + nsresult GetStringColumn(nsIMdbRow *cardRow, mdb_token outToken, nsString& str); + nsresult GetIntColumn(nsIMdbRow *cardRow, mdb_token outToken, + uint32_t* pValue, uint32_t defaultValue); + nsresult GetBoolColumn(nsIMdbRow *cardRow, mdb_token outToken, bool* pValue); + nsresult GetListCardFromDB(nsIAbCard *listCard, nsIMdbRow* listRow); + nsresult GetListFromDB(nsIAbDirectory *newCard, nsIMdbRow* listRow); + nsresult AddRecordKeyColumnToRow(nsIMdbRow *pRow); + nsresult AddAttributeColumnsToRow(nsIAbCard *card, nsIMdbRow *cardRow); + nsresult AddListAttributeColumnsToRow(nsIAbDirectory *list, nsIMdbRow *listRow, nsIAbDirectory *parent); + nsresult CreateCard(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result); + nsresult CreateCardFromDeletedCardsTable(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard **result); + nsresult DeleteCardFromListRow(nsIMdbRow* pListRow, mdb_id cardRowID); + void DeleteCardFromAllMailLists(mdb_id cardRowID); + nsresult NotifyListEntryChange(uint32_t abCode, nsIAbDirectory *dir); + + nsresult AddLowercaseColumn(nsIMdbRow * row, mdb_token columnToken, const char* utf8String); + nsresult GetRowFromAttribute(const char *aName, const nsACString &aUTF8Value, + bool aCaseInsensitive, nsIMdbRow **aCardRow, + mdb_pos *aRowPos); + + static nsTArray<nsAddrDatabase*>* m_dbCache; + static nsTArray<nsAddrDatabase*>* GetDBCache(); + + // mdb bookkeeping stuff + nsresult InitExistingDB(); + nsresult InitNewDB(); + nsresult InitMDBInfo(); + nsresult InitPabTable(); + nsresult InitDeletedCardsTable(bool aCreate); + nsresult AddRowToDeletedCardsTable(nsIAbCard *card, nsIMdbRow **pCardRow); + nsresult DeleteRowFromDeletedCardsTable(nsIMdbRow *pCardRow); + + nsresult InitLastRecorKey(); + nsresult GetDataRow(nsIMdbRow **pDataRow); + nsresult GetLastRecordKey(); + nsresult UpdateLastRecordKey(); + nsresult CheckAndUpdateRecordKey(); + nsresult UpdateLowercaseEmailListName(); + nsresult ConvertAndAddLowercaseColumn(nsIMdbRow * row, mdb_token fromCol, mdb_token toCol); + nsresult AddUnicodeToColumn(nsIMdbRow * row, mdb_token colToken, mdb_token lowerCaseColToken, const char16_t* pUnicodeStr); + + nsresult DeleteRow(nsIMdbTable* dbTable, nsIMdbRow* dbRow); + + nsIMdbEnv *m_mdbEnv; // to be used in all the db calls. + nsIMdbStore *m_mdbStore; + nsIMdbTable *m_mdbPabTable; + nsIMdbTable *m_mdbDeletedCardsTable; + nsCOMPtr<nsIFile> m_dbName; + bool m_mdbTokensInitialized; + nsTObserverArray<nsIAddrDBListener*> m_ChangeListeners; + + mdb_kind m_PabTableKind; + mdb_kind m_MailListTableKind; + mdb_kind m_DeletedCardsTableKind; + + mdb_scope m_CardRowScopeToken; + mdb_scope m_ListRowScopeToken; + mdb_scope m_DataRowScopeToken; + + mdb_token m_FirstNameColumnToken; + mdb_token m_LastNameColumnToken; + mdb_token m_PhoneticFirstNameColumnToken; + mdb_token m_PhoneticLastNameColumnToken; + mdb_token m_DisplayNameColumnToken; + mdb_token m_NickNameColumnToken; + mdb_token m_PriEmailColumnToken; + mdb_token m_2ndEmailColumnToken; + mdb_token m_DefaultEmailColumnToken; + mdb_token m_CardTypeColumnToken; + mdb_token m_WorkPhoneColumnToken; + mdb_token m_HomePhoneColumnToken; + mdb_token m_FaxColumnToken; + mdb_token m_PagerColumnToken; + mdb_token m_CellularColumnToken; + mdb_token m_WorkPhoneTypeColumnToken; + mdb_token m_HomePhoneTypeColumnToken; + mdb_token m_FaxTypeColumnToken; + mdb_token m_PagerTypeColumnToken; + mdb_token m_CellularTypeColumnToken; + mdb_token m_HomeAddressColumnToken; + mdb_token m_HomeAddress2ColumnToken; + mdb_token m_HomeCityColumnToken; + mdb_token m_HomeStateColumnToken; + mdb_token m_HomeZipCodeColumnToken; + mdb_token m_HomeCountryColumnToken; + mdb_token m_WorkAddressColumnToken; + mdb_token m_WorkAddress2ColumnToken; + mdb_token m_WorkCityColumnToken; + mdb_token m_WorkStateColumnToken; + mdb_token m_WorkZipCodeColumnToken; + mdb_token m_WorkCountryColumnToken; + mdb_token m_JobTitleColumnToken; + mdb_token m_DepartmentColumnToken; + mdb_token m_CompanyColumnToken; + mdb_token m_AimScreenNameColumnToken; + mdb_token m_AnniversaryYearColumnToken; + mdb_token m_AnniversaryMonthColumnToken; + mdb_token m_AnniversaryDayColumnToken; + mdb_token m_SpouseNameColumnToken; + mdb_token m_FamilyNameColumnToken; + mdb_token m_DefaultAddressColumnToken; + mdb_token m_CategoryColumnToken; + mdb_token m_WebPage1ColumnToken; + mdb_token m_WebPage2ColumnToken; + mdb_token m_BirthYearColumnToken; + mdb_token m_BirthMonthColumnToken; + mdb_token m_BirthDayColumnToken; + mdb_token m_Custom1ColumnToken; + mdb_token m_Custom2ColumnToken; + mdb_token m_Custom3ColumnToken; + mdb_token m_Custom4ColumnToken; + mdb_token m_NotesColumnToken; + mdb_token m_LastModDateColumnToken; + mdb_token m_RecordKeyColumnToken; + mdb_token m_LowerPriEmailColumnToken; + mdb_token m_Lower2ndEmailColumnToken; + + mdb_token m_MailFormatColumnToken; + mdb_token m_PopularityIndexColumnToken; + + mdb_token m_AddressCharSetColumnToken; + mdb_token m_LastRecordKeyColumnToken; + + mdb_token m_ListNameColumnToken; + mdb_token m_ListNickNameColumnToken; + mdb_token m_ListDescriptionColumnToken; + mdb_token m_ListTotalColumnToken; + mdb_token m_LowerListNameColumnToken; + + uint32_t m_LastRecordKey; + nsWeakPtr m_dbDirectory; + nsCOMPtr<nsIMdbFactory> mMdbFactory; + +private: + nsresult GetRowForCharColumn(const char16_t *unicodeStr, + mdb_column findColumn, bool bIsCard, + bool aCaseInsensitive, nsIMdbRow **findRow, + mdb_pos *aRowPos); + bool HasRowButDeletedForCharColumn(const char16_t *unicodeStr, mdb_column findColumn, bool aIsCard, nsIMdbRow **aFindRow); + nsresult OpenInternal(nsIFile *aMabFile, bool aCreate, nsIAddrDatabase **pCardDB); + nsresult AlertAboutCorruptMabFile(const char16_t *aOldFileName, const char16_t *aNewFileName); + nsresult AlertAboutLockedMabFile(const char16_t *aFileName); + nsresult DisplayAlert(const char16_t *titleName, const char16_t *alertStringName, + const char16_t **formatStrings, int32_t numFormatStrings); +}; + +#endif diff --git a/mailnews/addrbook/src/nsAddrbook.manifest b/mailnews/addrbook/src/nsAddrbook.manifest new file mode 100644 index 000000000..56efc3bda --- /dev/null +++ b/mailnews/addrbook/src/nsAddrbook.manifest @@ -0,0 +1,12 @@ +component {5b259db2-e451-4de9-8a6f-cfba91402973} nsAbAutoCompleteMyDomain.js +contract @mozilla.org/autocomplete/search;1?name=mydomain {5b259db2-e451-4de9-8a6f-cfba91402973} +component {2f946df9-114c-41fe-8899-81f10daf4f0c} nsAbAutoCompleteSearch.js +contract @mozilla.org/autocomplete/search;1?name=addrbook {2f946df9-114c-41fe-8899-81f10daf4f0c} +component {127b341a-bdda-4270-85e1-edff569a9b85} nsAbLDAPAttributeMap.js +contract @mozilla.org/addressbook/ldap-attribute-map;1 {127b341a-bdda-4270-85e1-edff569a9b85} +component {4ed7d5e1-8800-40da-9e78-c4f509d7ac5e} nsAbLDAPAttributeMap.js +contract @mozilla.org/addressbook/ldap-attribute-map-service;1 {4ed7d5e1-8800-40da-9e78-c4f509d7ac5e} +#ifdef MOZ_LDAP_XPCOM +component {227e6482-fe9f-441f-9b7d-7b60375e7449} nsAbLDAPAutoCompleteSearch.js +contract @mozilla.org/autocomplete/search;1?name=ldap {227e6482-fe9f-441f-9b7d-7b60375e7449} +#endif
\ No newline at end of file diff --git a/mailnews/addrbook/src/nsDirPrefs.cpp b/mailnews/addrbook/src/nsDirPrefs.cpp new file mode 100644 index 000000000..5e4c046b2 --- /dev/null +++ b/mailnews/addrbook/src/nsDirPrefs.cpp @@ -0,0 +1,1452 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* directory server preferences (used to be dirprefs.c in 4.x) */ + +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsDirPrefs.h" +#include "nsIPrefLocalizedString.h" +#include "nsIObserver.h" +#include "nsTArray.h" +#include "nsServiceManagerUtils.h" +#include "nsMemory.h" +#include "nsIAddrDatabase.h" +#include "nsAbBaseCID.h" +#include "nsIAbManager.h" +#include "nsIFile.h" +#include "nsWeakReference.h" +#include "nsIAbMDBDirectory.h" +#if defined(MOZ_LDAP_XPCOM) +#include "nsIAbLDAPDirectory.h" +#endif +#include "prmem.h" +#include "prprf.h" +#include "plstr.h" +#include "nsQuickSort.h" +#include "nsComponentManagerUtils.h" +#include "msgCore.h" +#include "nsStringGlue.h" + +#include <ctype.h> + +/***************************************************************************** + * Private definitions + */ + +/* Default settings for site-configurable prefs */ +#define kDefaultPosition 1 +static bool dir_IsServerDeleted(DIR_Server * server); + +static char *DIR_GetStringPref(const char *prefRoot, const char *prefLeaf, const char *defaultValue); +static int32_t DIR_GetIntPref(const char *prefRoot, const char *prefLeaf, int32_t defaultValue); +static char *DIR_GetLocalizedStringPref(const char *prefRoot, const char *prefLeaf); + +static char * dir_ConvertDescriptionToPrefName(DIR_Server * server); + +void DIR_SetFileName(char** filename, const char* leafName); +static void DIR_SetIntPref(const char *prefRoot, const char *prefLeaf, int32_t value, int32_t defaultValue); +static DIR_Server *dir_MatchServerPrefToServer(nsTArray<DIR_Server*> *wholeList, const char *pref); +static bool dir_ValidateAndAddNewServer(nsTArray<DIR_Server*> *wholeList, const char *fullprefname); +static void DIR_DeleteServerList(nsTArray<DIR_Server*> *wholeList); + +static char *dir_CreateServerPrefName(DIR_Server *server); +static void DIR_GetPrefsForOneServer(DIR_Server *server); + +static void DIR_InitServer(DIR_Server *server, DirectoryType dirType = (DirectoryType)0); +static DIR_PrefId DIR_AtomizePrefName(const char *prefname); + +const int32_t DIR_POS_APPEND = -1; +const int32_t DIR_POS_DELETE = -2; +static bool DIR_SetServerPosition(nsTArray<DIR_Server*> *wholeList, DIR_Server *server, int32_t position); + +/* These two routines should be called to initialize and save + * directory preferences from the XP Java Script preferences + */ +static nsresult DIR_GetServerPreferences(nsTArray<DIR_Server*>** list); +static void DIR_SaveServerPreferences(nsTArray<DIR_Server*> *wholeList); + +static int32_t dir_UserId = 0; +nsTArray<DIR_Server*> *dir_ServerList = nullptr; + +/***************************************************************************** + * Functions for creating the new back end managed DIR_Server list. + */ +class DirPrefObserver final : public nsSupportsWeakReference, + public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + +private: + ~DirPrefObserver() {} +}; + +NS_IMPL_ISUPPORTS(DirPrefObserver, nsISupportsWeakReference, nsIObserver) + +NS_IMETHODIMP DirPrefObserver::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) +{ + nsCOMPtr<nsIPrefBranch> prefBranch(do_QueryInterface(aSubject)); + nsCString strPrefName; + strPrefName.Assign(NS_ConvertUTF16toUTF8(aData)); + const char *prefname = strPrefName.get(); + + DIR_PrefId id = DIR_AtomizePrefName(prefname); + + // Just get out if we get nothing here - we don't need to do anything + if (id == idNone) + return NS_OK; + + /* Check to see if the server is in the unified server list. + */ + DIR_Server *server = dir_MatchServerPrefToServer(dir_ServerList, prefname); + if (server) + { + /* If the server is in the process of being saved, just ignore this + * change. The DIR_Server structure is not really changing. + */ + if (server->savingServer) + return NS_OK; + + /* If the pref that changed is the position, read it in. If the new + * position is zero, remove the server from the list. + */ + if (id == idPosition) + { + int32_t position; + + /* We must not do anything if the new position is the same as the + * position in the DIR_Server. This avoids recursion in cases + * where we are deleting the server. + */ + prefBranch->GetIntPref(prefname, &position); + if (position != server->position) + { + server->position = position; + if (dir_IsServerDeleted(server)) + DIR_SetServerPosition(dir_ServerList, server, DIR_POS_DELETE); + } + } + + if (id == idDescription) { + // Ensure the local copy of the description is kept up to date. + PR_FREEIF(server->description); + server->description = DIR_GetLocalizedStringPref(prefname, nullptr); + } + } + /* If the server is not in the unified list, we may need to add it. Servers + * are only added when the position, serverName and description are valid. + */ + else if (id == idPosition || id == idType || id == idDescription) + { + dir_ValidateAndAddNewServer(dir_ServerList, prefname); + } + + return NS_OK; +} + +// A pointer to the pref observer +static DirPrefObserver *prefObserver = nullptr; + +static nsresult DIR_GetDirServers() +{ + nsresult rv = NS_OK; + + if (!dir_ServerList) + { + /* we need to build the DIR_Server list */ + rv = DIR_GetServerPreferences(&dir_ServerList); + + /* Register the preference call back if necessary. */ + if (NS_SUCCEEDED(rv) && !prefObserver) + { + nsCOMPtr<nsIPrefBranch> pbi(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + prefObserver = new DirPrefObserver(); + + if (!prefObserver) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(prefObserver); + + pbi->AddObserver(PREF_LDAP_SERVER_TREE_NAME, prefObserver, true); + } + } + return rv; +} + +nsTArray<DIR_Server*>* DIR_GetDirectories() +{ + if (!dir_ServerList) + DIR_GetDirServers(); + return dir_ServerList; +} + +DIR_Server* DIR_GetServerFromList(const char* prefName) +{ + DIR_Server* result = nullptr; + + if (!dir_ServerList) + DIR_GetDirServers(); + + if (dir_ServerList) + { + int32_t count = dir_ServerList->Length(); + int32_t i; + for (i = 0; i < count; ++i) + { + DIR_Server *server = dir_ServerList->ElementAt(i); + + if (server && strcmp(server->prefName, prefName) == 0) + { + result = server; + break; + } + } + } + return result; +} + +static nsresult SavePrefsFile() +{ + nsresult rv; + nsCOMPtr<nsIPrefService> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + return pPref->SavePrefFile(nullptr); +} + +nsresult DIR_ShutDown() /* FEs should call this when the app is shutting down. It frees all DIR_Servers regardless of ref count values! */ +{ + nsresult rv = SavePrefsFile(); + NS_ENSURE_SUCCESS(rv, rv); + + DIR_DeleteServerList(dir_ServerList); + dir_ServerList = nullptr; + + /* unregister the preference call back, if necessary. + * we need to do this as DIR_Shutdown() is called when switching profiles + * when using turbo. (see nsAbDirectoryDataSource::Observe()) + * When switching profiles, prefs get unloaded and then re-loaded + * we don't want our callback to get called for all that. + * We'll reset our callback the first time DIR_GetDirServers() is called + * after we've switched profiles. + */ + NS_IF_RELEASE(prefObserver); + + return NS_OK; +} + +nsresult DIR_ContainsServer(DIR_Server* pServer, bool *hasDir) +{ + if (dir_ServerList) + { + int32_t count = dir_ServerList->Length(); + int32_t i; + for (i = 0; i < count; i++) + { + DIR_Server* server = dir_ServerList->ElementAt(i); + if (server == pServer) + { + *hasDir = true; + return NS_OK; + } + } + } + *hasDir = false; + return NS_OK; +} + +nsresult DIR_AddNewAddressBook(const nsAString &dirName, + const nsACString &fileName, + const nsACString &uri, + DirectoryType dirType, + const nsACString &prefName, + DIR_Server** pServer) +{ + DIR_Server * server = (DIR_Server *) PR_Malloc(sizeof(DIR_Server)); + if (!server) + return NS_ERROR_OUT_OF_MEMORY; + + DIR_InitServer(server, dirType); + if (!dir_ServerList) + DIR_GetDirServers(); + if (dir_ServerList) + { + server->description = ToNewCString(NS_ConvertUTF16toUTF8(dirName)); + server->position = kDefaultPosition; // don't set position so alphabetic sort will happen. + + if (!fileName.IsEmpty()) + server->fileName = ToNewCString(fileName); + else if (dirType == PABDirectory) + DIR_SetFileName(&server->fileName, kPersonalAddressbook); + else if (dirType == LDAPDirectory) + DIR_SetFileName(&server->fileName, kMainLdapAddressBook); + + if (dirType != PABDirectory) { + if (!uri.IsEmpty()) + server->uri = ToNewCString(uri); + } + + if (!prefName.IsEmpty()) + server->prefName = ToNewCString(prefName); + + dir_ServerList->AppendElement(server); + + DIR_SavePrefsForOneServer(server); + + *pServer = server; + + // save new address book into pref file + return SavePrefsFile(); + } + return NS_ERROR_FAILURE; +} + +/***************************************************************************** + * Functions for creating DIR_Servers + */ +static void DIR_InitServer(DIR_Server *server, DirectoryType dirType) +{ + if (!server) { + NS_WARNING("DIR_InitServer: server parameter not initialized"); + return; + } + + memset(server, 0, sizeof(DIR_Server)); + server->position = kDefaultPosition; + server->uri = nullptr; + server->savingServer = false; + server->dirType = dirType; +} + +/* Function for setting the position of a server. Can be used to append, + * delete, or move a server in a server list. + * + * The third parameter specifies the new position the server is to occupy. + * The resulting position may differ depending on the lock state of the + * given server and other servers in the list. The following special values + * are supported: + * DIR_POS_APPEND - Appends the server to the end of the list. If the server + * is already in the list, does nothing. + * DIR_POS_DELETE - Deletes the given server from the list. Note that this + * does not cause the server structure to be freed. + * + * Returns true if the server list was re-sorted. + */ +static bool DIR_SetServerPosition(nsTArray<DIR_Server*> *wholeList, DIR_Server *server, int32_t position) + { + NS_ENSURE_TRUE(wholeList, false); + + int32_t i, count, num; + bool resort = false; + DIR_Server *s=nullptr; + + switch (position) { + case DIR_POS_APPEND: + /* Do nothing if the request is to append a server that is already + * in the list. + */ + count = wholeList->Length(); + for (i= 0; i < count; i++) + { + if ((s = wholeList->ElementAt(i)) != nullptr) + if (s == server) + return false; + } + /* In general, if there are any servers already in the list, set the + * position to the position of the last server plus one. If there + * are none, set it to position 1. + */ + if (count > 0) + { + s = wholeList->ElementAt(count - 1); + server->position = s->position + 1; + } + else + server->position = 1; + + wholeList->AppendElement(server); + break; + + case DIR_POS_DELETE: + /* Remove the prefs corresponding to the given server. If the prefName + * value is nullptr, the server has never been saved and there are no + * prefs to remove. + */ + if (server->prefName) + { + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return false; + + pPref->DeleteBranch(server->prefName); + + // mark the server as deleted by setting its position to 0 + DIR_SetIntPref(server->prefName, "position", 0, -1); + } + + /* If the server is in the server list, remove it. + */ + num = wholeList->IndexOf(server); + if (num >= 0) + { + /* The list does not need to be re-sorted if the server is the + * last one in the list. + */ + count = wholeList->Length(); + if (num == count - 1) + { + wholeList->RemoveElementAt(num); + } + else + { + resort = true; + wholeList->RemoveElement(server); + } + } + break; + + default: + /* See if the server is already in the list. + */ + count = wholeList->Length(); + for (i= 0; i < count; i++) + { + if ((s = wholeList->ElementAt(i)) != nullptr) + if (s == server) + break; + } + + /* If the server is not in the list, add it to the beginning and re-sort. + */ + if (s == nullptr) + { + server->position = position; + wholeList->AppendElement(server); + resort = true; + } + + /* Don't re-sort if the server is already in the requested position. + */ + else if (server->position != position) + { + server->position = position; + wholeList->RemoveElement(server); + wholeList->AppendElement(server); + resort = true; + } + break; + } + + /* Make sure our position changes get saved back to prefs + */ + DIR_SaveServerPreferences(wholeList); + + return resort; +} + +/***************************************************************************** + * DIR_Server Callback Notification Functions + */ + +/* dir_matchServerPrefToServer + * + * This function finds the DIR_Server in the unified DIR_Server list to which + * the given preference string belongs. + */ +static DIR_Server *dir_MatchServerPrefToServer(nsTArray<DIR_Server*> *wholeList, const char *pref) +{ + DIR_Server *server; + + int32_t count = wholeList->Length(); + int32_t i; + for (i = 0; i < count; i++) + { + if ((server = wholeList->ElementAt(i)) != nullptr) + { + if (server->prefName && PL_strstr(pref, server->prefName) == pref) + { + char c = pref[PL_strlen(server->prefName)]; + if (c == 0 || c == '.') + return server; + } + } + } + return nullptr; +} + +/* dir_ValidateAndAddNewServer + * + * This function verifies that the position, serverName and description values + * are set for the given prefName. If they are then it adds the server to the + * unified server list. + */ +static bool dir_ValidateAndAddNewServer(nsTArray<DIR_Server*> *wholeList, const char *fullprefname) +{ + bool rc = false; + + const char *endname = PL_strchr(&fullprefname[PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1], '.'); + if (endname) + { + char *prefname = (char *)PR_Malloc(endname - fullprefname + 1); + if (prefname) + { + int32_t dirType; + char *t1 = nullptr, *t2 = nullptr; + + PL_strncpyz(prefname, fullprefname, endname - fullprefname + 1); + + dirType = DIR_GetIntPref(prefname, "dirType", -1); + if (dirType != -1 && + DIR_GetIntPref(prefname, "position", 0) != 0 && + (t1 = DIR_GetLocalizedStringPref(prefname, "description")) != nullptr) + { + if (dirType == PABDirectory || + (t2 = DIR_GetStringPref(prefname, "serverName", nullptr)) != nullptr) + { + DIR_Server *server = (DIR_Server *)PR_Malloc(sizeof(DIR_Server)); + if (server) + { + DIR_InitServer(server, (DirectoryType)dirType); + server->prefName = prefname; + DIR_GetPrefsForOneServer(server); + DIR_SetServerPosition(wholeList, server, server->position); + rc = true; + } + PR_FREEIF(t2); + } + PR_Free(t1); + } + else + PR_Free(prefname); + } + } + + return rc; +} + +static DIR_PrefId DIR_AtomizePrefName(const char *prefname) +{ + if (!prefname) + return idNone; + + DIR_PrefId rc = idNone; + + /* Skip the "ldap_2.servers.<server-name>." portion of the string. + */ + if (PL_strstr(prefname, PREF_LDAP_SERVER_TREE_NAME) == prefname) + { + prefname = PL_strchr(&prefname[PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1], '.'); + if (!prefname) + return idNone; + else + prefname = prefname + 1; + } + + switch (prefname[0]) { + case 'd': + switch (prefname[1]) { + case 'e': /* description */ + rc = idDescription; + break; + case 'i': /* dirType */ + rc = idType; + break; + } + break; + + case 'f': + rc = idFileName; + break; + + case 'p': + switch (prefname[1]) { + case 'o': + switch (prefname[2]) { + case 's': /* position */ + rc = idPosition; + break; + } + break; + } + break; + + case 'u': /* uri */ + rc = idUri; + break; + } + + return rc; +} + +/***************************************************************************** + * Functions for destroying DIR_Servers + */ + +/* this function determines if the passed in server is no longer part of the of + the global server list. */ +static bool dir_IsServerDeleted(DIR_Server * server) +{ + return (server && server->position == 0); +} + +/* when the back end manages the server list, deleting a server just decrements its ref count, + in the old world, we actually delete the server */ +static void DIR_DeleteServer(DIR_Server *server) +{ + if (server) + { + /* when destroying the server check its clear flag to see if things need cleared */ +#ifdef XP_FileRemove + if (DIR_TestFlag(server, DIR_CLEAR_SERVER)) + { + if (server->fileName) + XP_FileRemove (server->fileName, xpAddrBookNew); + } +#endif /* XP_FileRemove */ + PR_Free(server->prefName); + PR_Free(server->description); + PR_Free(server->fileName); + PR_Free(server->uri); + PR_Free(server); + } +} + +nsresult DIR_DeleteServerFromList(DIR_Server *server) +{ + if (!server) + return NS_ERROR_NULL_POINTER; + + nsresult rv = NS_OK; + nsCOMPtr<nsIFile> dbPath; + + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + rv = abManager->GetUserProfileDirectory(getter_AddRefs(dbPath)); + + if (NS_SUCCEEDED(rv)) + { + // close the database, as long as it isn't the special ones + // (personal addressbook and collected addressbook) + // which can never be deleted. There was a bug where we would slap in + // "abook.mab" as the file name for LDAP directories, which would cause a crash + // on delete of LDAP directories. this is just extra protection. + if (server->fileName && + strcmp(server->fileName, kPersonalAddressbook) && + strcmp(server->fileName, kCollectedAddressbook)) + { + nsCOMPtr<nsIAddrDatabase> database; + + rv = dbPath->AppendNative(nsDependentCString(server->fileName)); + NS_ENSURE_SUCCESS(rv, rv); + + // close file before delete it + nsCOMPtr<nsIAddrDatabase> addrDBFactory = + do_GetService(NS_ADDRDATABASE_CONTRACTID, &rv); + + if (NS_SUCCEEDED(rv) && addrDBFactory) + rv = addrDBFactory->Open(dbPath, false, true, getter_AddRefs(database)); + if (database) /* database exists */ + { + database->ForceClosed(); + rv = dbPath->Remove(false); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + nsTArray<DIR_Server*> *dirList = DIR_GetDirectories(); + DIR_SetServerPosition(dirList, server, DIR_POS_DELETE); + DIR_DeleteServer(server); + + return SavePrefsFile(); + } + + return NS_ERROR_NULL_POINTER; +} + +static void DIR_DeleteServerList(nsTArray<DIR_Server*> *wholeList) +{ + if (wholeList) + { + DIR_Server *server = nullptr; + + /* TBD: Send notifications? */ + int32_t count = wholeList->Length(); + int32_t i; + for (i = count - 1; i >=0; i--) + { + server = wholeList->ElementAt(i); + if (server != nullptr) + DIR_DeleteServer(server); + } + delete wholeList; + } +} + +/***************************************************************************** + * Functions for managing JavaScript prefs for the DIR_Servers + */ + +static int +comparePrefArrayMembers(const void* aElement1, const void* aElement2, void* aData) +{ + const char* element1 = *static_cast<const char* const *>(aElement1); + const char* element2 = *static_cast<const char* const *>(aElement2); + const uint32_t offset = *((const uint32_t*)aData); + + // begin the comparison at |offset| chars into the string - + // this avoids comparing the "ldap_2.servers." portion of every element, + // which will always remain the same. + return strcmp(element1 + offset, element2 + offset); +} + +static nsresult dir_GetChildList(const nsCString &aBranch, + uint32_t *aCount, char ***aChildList) +{ + uint32_t branchLen = aBranch.Length(); + + nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID); + if (!prefBranch) { + return NS_ERROR_FAILURE; + } + + nsresult rv = prefBranch->GetChildList(aBranch.get(), aCount, aChildList); + if (NS_FAILED(rv)) { + return rv; + } + + // traverse the list, and truncate all the descendant strings to just + // one branch level below the root branch. + for (uint32_t i = *aCount; i--; ) { + // The prefname we passed to GetChildList was of the form + // "ldap_2.servers." and we are returned the descendants + // in the form of "ldap_2.servers.servername.foo" + // But we want the prefbranch of the servername, so + // write a NUL character in to terminate the string early. + char *endToken = strchr((*aChildList)[i] + branchLen, '.'); + if (endToken) + *endToken = '\0'; + } + + if (*aCount > 1) { + // sort the list, in preparation for duplicate entry removal + NS_QuickSort(*aChildList, *aCount, sizeof(char*), comparePrefArrayMembers, &branchLen); + + // traverse the list and remove duplicate entries. + // we use two positions in the list; the current entry and the next + // entry; and perform a bunch of in-place ptr moves. so |cur| points + // to the last unique entry, and |next| points to some (possibly much + // later) entry to test, at any given point. we know we have >= 2 + // elements in the list here, so we just init the two counters sensibly + // to begin with. + uint32_t cur = 0; + for (uint32_t next = 1; next < *aCount; ++next) { + // check if the elements are equal or unique + if (!comparePrefArrayMembers(&((*aChildList)[cur]), &((*aChildList)[next]), &branchLen)) { + // equal - just free & increment the next element ptr + + free((*aChildList)[next]); + } else { + // cur & next are unique, so we need to shift the element. + // ++cur will point to the next free location in the + // reduced array (it's okay if that's == next) + (*aChildList)[++cur] = (*aChildList)[next]; + } + } + + // update the unique element count + *aCount = cur + 1; + } + + return NS_OK; +} + +static char *DIR_GetStringPref(const char *prefRoot, const char *prefLeaf, const char *defaultValue) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return nullptr; + + nsCString value; + nsAutoCString prefLocation(prefRoot); + + prefLocation.Append('.'); + prefLocation.Append(prefLeaf); + + if (NS_SUCCEEDED(pPref->GetCharPref(prefLocation.get(), getter_Copies(value)))) + { + /* unfortunately, there may be some prefs out there which look like this */ + if (value.EqualsLiteral("(null)")) + { + if (defaultValue) + value = defaultValue; + else + value.Truncate(); + } + + if (value.IsEmpty()) + { + rv = pPref->GetCharPref(prefLocation.get(), getter_Copies(value)); + } + } + else + value = defaultValue; + + return ToNewCString(value); +} + +/** + * Get localized unicode string pref from properties file, convert into an UTF8 string + * since address book prefs store as UTF8 strings. So far there are 2 default + * prefs stored in addressbook.properties. + * "ldap_2.servers.pab.description" + * "ldap_2.servers.history.description" + */ +static char *DIR_GetLocalizedStringPref(const char *prefRoot, const char *prefLeaf) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + + if (NS_FAILED(rv)) + return nullptr; + + nsAutoCString prefLocation(prefRoot); + if (prefLeaf) { + prefLocation.Append('.'); + prefLocation.Append(prefLeaf); + } + + nsString wvalue; + nsCOMPtr<nsIPrefLocalizedString> locStr; + + rv = pPref->GetComplexValue(prefLocation.get(), NS_GET_IID(nsIPrefLocalizedString), getter_AddRefs(locStr)); + if (NS_SUCCEEDED(rv)) + rv = locStr->ToString(getter_Copies(wvalue)); + + char *value = nullptr; + if (!wvalue.IsEmpty()) + { + value = ToNewCString(NS_ConvertUTF16toUTF8(wvalue)); + } + else + { + // In TB 2 only some prefs had chrome:// URIs. We had code in place that would + // only get the localized string pref for the particular address books that + // were built-in. + // Additionally, nsIPrefBranch::getComplexValue will only get a non-user-set, + // non-locked pref value if it is a chrome:// URI and will get the string + // value at that chrome URI. This breaks extensions/autoconfig that want to + // set default pref values and allow users to change directory names. + // + // Now we have to support this, and so if for whatever reason we fail to get + // the localized version, then we try and get the non-localized version + // instead. If the string value is empty, then we'll just get the empty value + // back here. + rv = pPref->GetCharPref(prefLocation.get(), &value); + if (NS_FAILED(rv)) + value = nullptr; + } + + return value; +} + +static int32_t DIR_GetIntPref(const char *prefRoot, const char *prefLeaf, int32_t defaultValue) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + + if (NS_FAILED(rv)) + return defaultValue; + + int32_t value; + nsAutoCString prefLocation(prefRoot); + + prefLocation.Append('.'); + prefLocation.Append(prefLeaf); + + if (NS_FAILED(pPref->GetIntPref(prefLocation.get(), &value))) + value = defaultValue; + + return value; +} + +/* This will convert from the old preference that was a path and filename */ +/* to a just a filename */ +static void DIR_ConvertServerFileName(DIR_Server* pServer) +{ + char* leafName = pServer->fileName; + char* newLeafName = nullptr; +#if defined(XP_WIN) + /* jefft -- bug 73349 This is to allow users share same address book. + * It only works if the user specify a full path filename. + */ +#ifdef XP_FileIsFullPath + if (! XP_FileIsFullPath(leafName)) + newLeafName = XP_STRRCHR (leafName, '\\'); +#endif /* XP_FileIsFullPath */ +#else + newLeafName = strrchr(leafName, '/'); +#endif + pServer->fileName = newLeafName ? strdup(newLeafName + 1) : strdup(leafName); + if (leafName) PR_Free(leafName); +} + +/* This will generate a correct filename and then remove the path. + * Note: we are assuming that the default name is in the native + * filesystem charset. The filename will be returned as a UTF8 + * string. + */ +void DIR_SetFileName(char** fileName, const char* defaultName) +{ + if (!fileName) + return; + + nsresult rv = NS_OK; + nsCOMPtr<nsIFile> dbPath; + + *fileName = nullptr; + + nsCOMPtr<nsIAbManager> abManager = do_GetService(NS_ABMANAGER_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + rv = abManager->GetUserProfileDirectory(getter_AddRefs(dbPath)); + if (NS_SUCCEEDED(rv)) + { + rv = dbPath->AppendNative(nsDependentCString(defaultName)); + if (NS_SUCCEEDED(rv)) + { + rv = dbPath->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0664); + + nsAutoString realFileName; + rv = dbPath->GetLeafName(realFileName); + + if (NS_SUCCEEDED(rv)) + *fileName = ToNewUTF8String(realFileName); + } + } +} + +/**************************************************************** +Helper function used to generate a file name from the description +of a directory. Caller must free returned string. +An extension is not applied +*****************************************************************/ + +static char * dir_ConvertDescriptionToPrefName(DIR_Server * server) +{ +#define MAX_PREF_NAME_SIZE 25 + char * fileName = nullptr; + char fileNameBuf[MAX_PREF_NAME_SIZE]; + int32_t srcIndex = 0; + int32_t destIndex = 0; + int32_t numSrcBytes = 0; + const char * descr = nullptr; + if (server && server->description) + { + descr = server->description; + numSrcBytes = PL_strlen(descr); + while (srcIndex < numSrcBytes && destIndex < MAX_PREF_NAME_SIZE-1) + { + if (IS_DIGIT(descr[srcIndex]) || IS_ALPHA(descr[srcIndex])) + { + fileNameBuf[destIndex] = descr[srcIndex]; + destIndex++; + } + + srcIndex++; + } + + fileNameBuf[destIndex] = '\0'; /* zero out the last character */ + } + + if (destIndex) /* have at least one character in the file name? */ + fileName = strdup(fileNameBuf); + + return fileName; +} + + +void DIR_SetServerFileName(DIR_Server *server) +{ + char * tempName = nullptr; + const char * prefName = nullptr; + uint32_t numHeaderBytes = 0; + + if (server && (!server->fileName || !(*server->fileName)) ) + { + PR_FREEIF(server->fileName); // might be one byte empty string. + /* make sure we have a pref name...*/ + if (!server->prefName || !*server->prefName) + server->prefName = dir_CreateServerPrefName(server); + + /* set default personal address book file name*/ + if ((server->position == 1) && (server->dirType == PABDirectory)) + server->fileName = strdup(kPersonalAddressbook); + else + { + /* now use the pref name as the file name since we know the pref name + will be unique */ + prefName = server->prefName; + if (prefName && *prefName) + { + /* extract just the pref name part and not the ldap tree name portion from the string */ + numHeaderBytes = PL_strlen(PREF_LDAP_SERVER_TREE_NAME) + 1; /* + 1 for the '.' b4 the name */ + if (PL_strlen(prefName) > numHeaderBytes) + tempName = strdup(prefName + numHeaderBytes); + + if (tempName) + { + server->fileName = PR_smprintf("%s%s", tempName, kABFileName_CurrentSuffix); + PR_Free(tempName); + } + } + } + + if (!server->fileName || !*server->fileName) /* when all else has failed, generate a default name */ + { + if (server->dirType == LDAPDirectory) + DIR_SetFileName(&(server->fileName), kMainLdapAddressBook); /* generates file name with an ldap prefix */ + else + DIR_SetFileName(&(server->fileName), kPersonalAddressbook); + } + } +} + +static char *dir_CreateServerPrefName (DIR_Server *server) +{ + /* we are going to try to be smart in how we generate our server + pref name. We'll try to convert the description into a pref name + and then verify that it is unique. If it is unique then use it... */ + char * leafName = dir_ConvertDescriptionToPrefName(server); + char * prefName = nullptr; + bool isUnique = false; + + if (!leafName || !*leafName) + { + // we need to handle this in case the description has no alphanumeric chars + // it's very common for cjk users + leafName = strdup("_nonascii"); + } + + if (leafName) + { + int32_t uniqueIDCnt = 0; + char **children = nullptr; + /* we need to verify that this pref string name is unique */ + prefName = PR_smprintf(PREF_LDAP_SERVER_TREE_NAME".%s", leafName); + isUnique = false; + uint32_t prefCount; + nsresult rv = dir_GetChildList(NS_LITERAL_CSTRING(PREF_LDAP_SERVER_TREE_NAME "."), + &prefCount, &children); + if (NS_SUCCEEDED(rv)) + { + while (!isUnique && prefName) + { + isUnique = true; /* now flip the logic and assume we are unique until we find a match */ + for (uint32_t i = 0; i < prefCount && isUnique; ++i) + { + if (!PL_strcasecmp(children[i], prefName)) /* are they the same branch? */ + isUnique = false; + } + if (!isUnique) /* then try generating a new pref name and try again */ + { + PR_smprintf_free(prefName); + prefName = PR_smprintf(PREF_LDAP_SERVER_TREE_NAME".%s_%d", leafName, ++uniqueIDCnt); + } + } /* if we have a list of pref Names */ + + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, children); + } /* while we don't have a unique name */ + + // fallback to "user_directory_N" form if we failed to verify + if (!isUnique && prefName) + { + PR_smprintf_free(prefName); + prefName = nullptr; + } + + PR_Free(leafName); + + } /* if leafName */ + + if (!prefName) /* last resort if we still don't have a pref name is to use user_directory string */ + return PR_smprintf(PREF_LDAP_SERVER_TREE_NAME".user_directory_%d", ++dir_UserId); + else + return prefName; +} + +static void DIR_GetPrefsForOneServer(DIR_Server *server) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return; + + char *prefstring = server->prefName; + + // this call fills in tempstring with the position pref, and + // we then check to see if it's locked. + server->position = DIR_GetIntPref (prefstring, "position", kDefaultPosition); + + // For default address books, this will get the name from the chrome + // file referenced, for other address books it'll just retrieve it from prefs + // as normal. + server->description = DIR_GetLocalizedStringPref(prefstring, "description"); + + server->dirType = (DirectoryType)DIR_GetIntPref (prefstring, "dirType", LDAPDirectory); + + server->fileName = DIR_GetStringPref (prefstring, "filename", ""); + // if we don't have a file name try and get one + if (!server->fileName || !*(server->fileName)) + DIR_SetServerFileName (server); + if (server->fileName && *server->fileName) + DIR_ConvertServerFileName(server); + + // the string "s" is the default uri ( <scheme> + "://" + <filename> ) + nsCString s((server->dirType == PABDirectory || server->dirType == MAPIDirectory) ? +#if defined(MOZ_LDAP_XPCOM) + kMDBDirectoryRoot : kLDAPDirectoryRoot); +#else + // Fallback to the all directory root in the non-ldap enabled case. + kMDBDirectoryRoot : kAllDirectoryRoot); +#endif + s.Append (server->fileName); + server->uri = DIR_GetStringPref (prefstring, "uri", s.get ()); +} + +static nsresult dir_GetPrefs(nsTArray<DIR_Server*> **list) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return rv; + + (*list) = new nsTArray<DIR_Server*>(); + if (!(*list)) + return NS_ERROR_OUT_OF_MEMORY; + + char **children; + uint32_t prefCount; + + rv = dir_GetChildList(NS_LITERAL_CSTRING(PREF_LDAP_SERVER_TREE_NAME "."), + &prefCount, &children); + if (NS_FAILED(rv)) + return rv; + + /* TBD: Temporary code to read broken "ldap" preferences tree. + * Remove line with if statement after M10. + */ + if (dir_UserId == 0) + pPref->GetIntPref(PREF_LDAP_GLOBAL_TREE_NAME".user_id", &dir_UserId); + + for (uint32_t i = 0; i < prefCount; ++i) + { + DIR_Server *server; + + server = (DIR_Server *)PR_Calloc(1, sizeof(DIR_Server)); + if (server) + { + DIR_InitServer(server); + server->prefName = strdup(children[i]); + DIR_GetPrefsForOneServer(server); + if (server->description && server->description[0] && + ((server->dirType == PABDirectory || + server->dirType == MAPIDirectory || + server->dirType == FixedQueryLDAPDirectory || // this one might go away + server->dirType == LDAPDirectory))) + { + if (!dir_IsServerDeleted(server)) + { + (*list)->AppendElement(server); + } + else + DIR_DeleteServer(server); + } + else + { + DIR_DeleteServer(server); + } + } + } + + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(prefCount, children); + + return NS_OK; +} + +// I don't think we care about locked positions, etc. +void DIR_SortServersByPosition(nsTArray<DIR_Server*> *serverList) +{ + int i, j; + DIR_Server *server; + + int count = serverList->Length(); + for (i = 0; i < count - 1; i++) + { + for (j = i + 1; j < count; j++) + { + if (serverList->ElementAt(j)->position < serverList->ElementAt(i)->position) + { + server = serverList->ElementAt(i); + serverList->ReplaceElementAt(i, serverList->ElementAt(j)); + serverList->ReplaceElementAt(j, server); + } + } + } +} + +static nsresult DIR_GetServerPreferences(nsTArray<DIR_Server*>** list) +{ + nsresult err; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &err)); + if (NS_FAILED(err)) + return err; + + int32_t version = -1; + nsTArray<DIR_Server*> *newList = nullptr; + + /* Update the ldap list version and see if there are old prefs to migrate. */ + err = pPref->GetIntPref(PREF_LDAP_VERSION_NAME, &version); + NS_ENSURE_SUCCESS(err, err); + + /* Find the new-style "ldap_2.servers" tree in prefs */ + err = dir_GetPrefs(&newList); + + if (version < kCurrentListVersion) + { + pPref->SetIntPref(PREF_LDAP_VERSION_NAME, kCurrentListVersion); + } + + DIR_SortServersByPosition(newList); + + *list = newList; + + return err; +} + +static void DIR_SetStringPref(const char *prefRoot, const char *prefLeaf, const char *value, const char *defaultValue) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return; + + nsCString defaultPref; + nsAutoCString prefLocation(prefRoot); + + prefLocation.Append('.'); + prefLocation.Append(prefLeaf); + + if (NS_SUCCEEDED(pPref->GetCharPref(prefLocation.get(), getter_Copies(defaultPref)))) + { + /* If there's a default pref, just set ours in and let libpref worry + * about potential defaults in all.js + */ + if (value) /* added this check to make sure we have a value before we try to set it..*/ + rv = pPref->SetCharPref (prefLocation.get(), value); + else + rv = pPref->ClearUserPref(prefLocation.get()); + } + else + { + /* If there's no default pref, look for a user pref, and only set our value in + * if the user pref is different than one of them. + */ + nsCString userPref; + if (NS_SUCCEEDED(pPref->GetCharPref (prefLocation.get(), getter_Copies(userPref)))) + { + if (value && (defaultValue ? PL_strcasecmp(value, defaultValue) : value != defaultValue)) + rv = pPref->SetCharPref (prefLocation.get(), value); + else + rv = pPref->ClearUserPref(prefLocation.get()); + } + else + { + if (value && (defaultValue ? PL_strcasecmp(value, defaultValue) : value != defaultValue)) + rv = pPref->SetCharPref (prefLocation.get(), value); + } + } + + NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref in DIR_SetStringPref"); +} + +static void DIR_SetLocalizedStringPref +(const char *prefRoot, const char *prefLeaf, const char *value) +{ + nsresult rv; + nsCOMPtr<nsIPrefService> prefSvc(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + + if (NS_FAILED(rv)) + return; + + nsAutoCString prefLocation(prefRoot); + prefLocation.Append('.'); + + nsCOMPtr<nsIPrefBranch> prefBranch; + rv = prefSvc->GetBranch(prefLocation.get(), getter_AddRefs(prefBranch)); + if (NS_FAILED(rv)) + return; + + nsString wvalue; + nsCOMPtr<nsIPrefLocalizedString> newStr( + do_CreateInstance(NS_PREFLOCALIZEDSTRING_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + { + NS_ASSERTION(NS_SUCCEEDED(rv), "Could not createInstance in DIR_SetLocalizedStringPref"); + return; + } + + NS_ConvertUTF8toUTF16 newValue(value); + + rv = newStr->SetData(newValue.get()); + if (NS_FAILED(rv)) + { + NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref data in DIR_SetLocalizedStringPref"); + return; + } + nsCOMPtr<nsIPrefLocalizedString> locStr; + if (NS_SUCCEEDED(prefBranch->GetComplexValue(prefLeaf, + NS_GET_IID(nsIPrefLocalizedString), + getter_AddRefs(locStr)))) + { + nsString data; + locStr->GetData(getter_Copies(data)); + + // Only set the pref if the data values aren't the same (i.e. don't change + // unnecessarily, but also, don't change in the case that its a chrome + // string pointing to the value we want to set the pref to). + if (newValue != data) + rv = prefBranch->SetComplexValue(prefLeaf, + NS_GET_IID(nsIPrefLocalizedString), + newStr); + } + else { + // No value set, but check the default pref branch (i.e. user may have + // cleared the pref) + nsCOMPtr<nsIPrefBranch> dPB; + rv = prefSvc->GetDefaultBranch(prefLocation.get(), + getter_AddRefs(dPB)); + + if (NS_SUCCEEDED(dPB->GetComplexValue(prefLeaf, + NS_GET_IID(nsIPrefLocalizedString), + getter_AddRefs(locStr)))) + { + // Default branch has a value + nsString data; + locStr->GetData(getter_Copies(data)); + + if (newValue != data) + // If the vales aren't the same, set the data on the main pref branch + rv = prefBranch->SetComplexValue(prefLeaf, + NS_GET_IID(nsIPrefLocalizedString), + newStr); + else + // Else if they are, kill the user pref + rv = prefBranch->ClearUserPref(prefLeaf); + } + else + // No values set anywhere, so just set the pref + rv = prefBranch->SetComplexValue(prefLeaf, + NS_GET_IID(nsIPrefLocalizedString), + newStr); + } + + NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref in DIR_SetLocalizedStringPref"); +} + + +static void DIR_SetIntPref(const char *prefRoot, const char *prefLeaf, int32_t value, int32_t defaultValue) +{ + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) + return; + + int32_t defaultPref; + nsAutoCString prefLocation(prefRoot); + + prefLocation.Append('.'); + prefLocation.Append(prefLeaf); + + if (NS_SUCCEEDED(pPref->GetIntPref(prefLocation.get(), &defaultPref))) + { + /* solve the problem where reordering user prefs must override default prefs */ + rv = pPref->SetIntPref(prefLocation.get(), value); + } + else + { + int32_t userPref; + if (NS_SUCCEEDED(pPref->GetIntPref(prefLocation.get(), &userPref))) + { + if (value != defaultValue) + rv = pPref->SetIntPref(prefLocation.get(), value); + else + rv = pPref->ClearUserPref(prefLocation.get()); + } + else + { + if (value != defaultValue) + rv = pPref->SetIntPref(prefLocation.get(), value); + } + } + + NS_ASSERTION(NS_SUCCEEDED(rv), "Could not set pref in DIR_SetIntPref"); +} + +void DIR_SavePrefsForOneServer(DIR_Server *server) +{ + if (!server) + return; + + char *prefstring; + + if (server->prefName == nullptr) + server->prefName = dir_CreateServerPrefName(server); + prefstring = server->prefName; + + server->savingServer = true; + + DIR_SetIntPref (prefstring, "position", server->position, kDefaultPosition); + + // Only save the non-default address book name + DIR_SetLocalizedStringPref(prefstring, "description", server->description); + + DIR_SetStringPref(prefstring, "filename", server->fileName, ""); + DIR_SetIntPref(prefstring, "dirType", server->dirType, LDAPDirectory); + + if (server->dirType != PABDirectory) + DIR_SetStringPref(prefstring, "uri", server->uri, ""); + + server->savingServer = false; +} + +static void DIR_SaveServerPreferences(nsTArray<DIR_Server*> *wholeList) +{ + if (wholeList) + { + nsresult rv; + nsCOMPtr<nsIPrefBranch> pPref(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv)); + if (NS_FAILED(rv)) { + NS_WARNING("DIR_SaveServerPreferences: Failed to get the pref service\n"); + return; + } + + int32_t i; + int32_t count = wholeList->Length(); + DIR_Server *server; + + for (i = 0; i < count; i++) + { + server = wholeList->ElementAt(i); + if (server) + DIR_SavePrefsForOneServer(server); + } + pPref->SetIntPref(PREF_LDAP_GLOBAL_TREE_NAME".user_id", dir_UserId); + } +} diff --git a/mailnews/addrbook/src/nsDirPrefs.h b/mailnews/addrbook/src/nsDirPrefs.h new file mode 100644 index 000000000..d27a4fcb5 --- /dev/null +++ b/mailnews/addrbook/src/nsDirPrefs.h @@ -0,0 +1,86 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef _NSDIRPREFS_H_ +#define _NSDIRPREFS_H_ + +#include "nsTArray.h" + +// +// XXX nsDirPrefs is being greatly reduced if not removed altogether. Directory +// Prefs etc. should be handled via their appropriate nsAb*Directory classes. +// + +#define kPreviousListVersion 2 +#define kCurrentListVersion 3 +#define PREF_LDAP_GLOBAL_TREE_NAME "ldap_2" +#define PREF_LDAP_VERSION_NAME "ldap_2.version" +#define PREF_LDAP_SERVER_TREE_NAME "ldap_2.servers" + +#define kMainLdapAddressBook "ldap.mab" /* v3 main ldap address book file */ + +/* DIR_Server.dirType */ +typedef enum +{ + LDAPDirectory, + HTMLDirectory, + PABDirectory, + MAPIDirectory, + FixedQueryLDAPDirectory = 777 +} DirectoryType; + +typedef enum +{ + idNone = 0, /* Special value */ + idPrefName, + idPosition, + idDescription, + idFileName, + idUri, + idType +} DIR_PrefId; + +#define DIR_Server_typedef 1 /* this quiets a redeclare warning in libaddr */ + +typedef struct DIR_Server +{ + /* Housekeeping fields */ + char *prefName; /* preference name, this server's subtree */ + int32_t position; /* relative position in server list */ + + /* General purpose fields */ + char *description; /* human readable name */ + char *fileName; /* XP path name of local DB */ + DirectoryType dirType; + char *uri; // URI of the address book + + // Set whilst saving the server to avoid updating it again + bool savingServer; +} DIR_Server; + +/* We are developing a new model for managing DIR_Servers. In the 4.0x world, the FEs managed each list. + Calls to FE_GetDirServer caused the FEs to manage and return the DIR_Server list. In our new view of the + world, the back end does most of the list management so we are going to have the back end create and + manage the list. Replace calls to FE_GetDirServers() with DIR_GetDirServers(). */ + +nsTArray<DIR_Server*>* DIR_GetDirectories(); +DIR_Server* DIR_GetServerFromList(const char* prefName); +nsresult DIR_ShutDown(void); /* FEs should call this when the app is shutting down. It frees all DIR_Servers regardless of ref count values! */ + +nsresult DIR_AddNewAddressBook(const nsAString &dirName, + const nsACString &fileName, + const nsACString &uri, + DirectoryType dirType, + const nsACString &prefName, + DIR_Server** pServer); +nsresult DIR_ContainsServer(DIR_Server* pServer, bool *hasDir); + +nsresult DIR_DeleteServerFromList (DIR_Server *); + +void DIR_SavePrefsForOneServer(DIR_Server *server); + +void DIR_SetServerFileName(DIR_Server* pServer); + +#endif /* dirprefs.h */ diff --git a/mailnews/addrbook/src/nsMapiAddressBook.cpp b/mailnews/addrbook/src/nsMapiAddressBook.cpp new file mode 100644 index 000000000..80d737fb2 --- /dev/null +++ b/mailnews/addrbook/src/nsMapiAddressBook.cpp @@ -0,0 +1,147 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ +#include "nsMapiAddressBook.h" + +#include "mozilla/Logging.h" + +#ifdef PR_LOGGING +static PRLogModuleInfo* gMapiAddressBookLog + = PR_NewLogModule("nsMapiAddressBookLog"); +#endif + +#define PRINTF(args) MOZ_LOG(gMapiAddressBookLog, mozilla::LogLevel::Debug, args) + +using namespace mozilla; + +HMODULE nsMapiAddressBook::mLibrary = NULL ; +int32_t nsMapiAddressBook::mLibUsage = 0 ; +LPMAPIINITIALIZE nsMapiAddressBook::mMAPIInitialize = NULL ; +LPMAPIUNINITIALIZE nsMapiAddressBook::mMAPIUninitialize = NULL ; +LPMAPIALLOCATEBUFFER nsMapiAddressBook::mMAPIAllocateBuffer = NULL ; +LPMAPIFREEBUFFER nsMapiAddressBook::mMAPIFreeBuffer = NULL ; +LPMAPILOGONEX nsMapiAddressBook::mMAPILogonEx = NULL ; + +BOOL nsMapiAddressBook::mInitialized = FALSE ; +BOOL nsMapiAddressBook::mLogonDone = FALSE ; +LPMAPISESSION nsMapiAddressBook::mRootSession = NULL ; +LPADRBOOK nsMapiAddressBook::mRootBook = NULL ; + +BOOL nsMapiAddressBook::LoadMapiLibrary(void) +{ + if (mLibrary) { ++ mLibUsage ; return TRUE ; } + HMODULE libraryHandle = LoadLibrary("MAPI32.DLL") ; + + if (!libraryHandle) { return FALSE ; } + FARPROC entryPoint = GetProcAddress(libraryHandle, "MAPIGetNetscapeVersion") ; + + if (entryPoint) { + FreeLibrary(libraryHandle) ; + libraryHandle = LoadLibrary("MAPI32BAK.DLL") ; + if (!libraryHandle) { return FALSE ; } + } + mLibrary = libraryHandle ; + ++ mLibUsage ; + mMAPIInitialize = reinterpret_cast<LPMAPIINITIALIZE>(GetProcAddress(mLibrary, "MAPIInitialize")) ; + if (!mMAPIInitialize) { return FALSE ; } + mMAPIUninitialize = reinterpret_cast<LPMAPIUNINITIALIZE>(GetProcAddress(mLibrary, "MAPIUninitialize")) ; + if (!mMAPIUninitialize) { return FALSE ; } + mMAPIAllocateBuffer = reinterpret_cast<LPMAPIALLOCATEBUFFER>(GetProcAddress(mLibrary, "MAPIAllocateBuffer")) ; + if (!mMAPIAllocateBuffer) { return FALSE ; } + mMAPIFreeBuffer = reinterpret_cast<LPMAPIFREEBUFFER>(GetProcAddress(mLibrary, "MAPIFreeBuffer")) ; + if (!mMAPIFreeBuffer) { return FALSE ; } + mMAPILogonEx = reinterpret_cast<LPMAPILOGONEX>(GetProcAddress(mLibrary, "MAPILogonEx")) ; + if (!mMAPILogonEx) { return FALSE ; } + MAPIINIT_0 mapiInit = { MAPI_INIT_VERSION, MAPI_MULTITHREAD_NOTIFICATIONS } ; + HRESULT retCode = mMAPIInitialize(&mapiInit) ; + + if (HR_FAILED(retCode)) { + PRINTF(("Cannot initialize MAPI %08x.\n", retCode)) ; return FALSE ; + } + mInitialized = TRUE ; + retCode = mMAPILogonEx(0, NULL, NULL, + MAPI_NO_MAIL | + MAPI_USE_DEFAULT | + MAPI_EXTENDED | + MAPI_NEW_SESSION, + &mRootSession) ; + if (HR_FAILED(retCode)) { + PRINTF(("Cannot logon to MAPI %08x.\n", retCode)) ; return FALSE ; + } + mLogonDone = TRUE ; + retCode = mRootSession->OpenAddressBook(0, NULL, 0, &mRootBook) ; + if (HR_FAILED(retCode)) { + PRINTF(("Cannot open MAPI address book %08x.\n", retCode)) ; + } + return HR_SUCCEEDED(retCode) ; +} + +void nsMapiAddressBook::FreeMapiLibrary(void) +{ + if (mLibrary) { + if (-- mLibUsage == 0) { + { + if (mRootBook) { mRootBook->Release() ; } + if (mRootSession) { + if (mLogonDone) { + mRootSession->Logoff(NULL, 0, 0) ; + mLogonDone = FALSE ; + } + mRootSession->Release() ; + } + if (mInitialized) { + mMAPIUninitialize() ; + mInitialized = FALSE ; + } + } + FreeLibrary(mLibrary) ; + mLibrary = NULL ; + } + } +} + +nsMapiAddressBook::nsMapiAddressBook(void) +: nsAbWinHelper() +{ + BOOL result = Initialize() ; + + NS_ASSERTION(result == TRUE, "Couldn't initialize Mapi Helper") ; + MOZ_COUNT_CTOR(nsMapiAddressBook) ; +} + +nsMapiAddressBook::~nsMapiAddressBook(void) +{ + MutexAutoLock guard(*mMutex) ; + + FreeMapiLibrary() ; + MOZ_COUNT_DTOR(nsMapiAddressBook) ; +} + +BOOL nsMapiAddressBook::Initialize(void) +{ + if (mAddressBook) { return TRUE ; } + MutexAutoLock guard(*mMutex) ; + + if (!LoadMapiLibrary()) { + PRINTF(("Cannot load library.\n")) ; + return FALSE ; + } + mAddressBook = mRootBook ; + return TRUE ; +} + +void nsMapiAddressBook::AllocateBuffer(ULONG aByteCount, LPVOID *aBuffer) +{ + mMAPIAllocateBuffer(aByteCount, aBuffer) ; +} + +void nsMapiAddressBook::FreeBuffer(LPVOID aBuffer) +{ + mMAPIFreeBuffer(aBuffer) ; +} + + + + + diff --git a/mailnews/addrbook/src/nsMapiAddressBook.h b/mailnews/addrbook/src/nsMapiAddressBook.h new file mode 100644 index 000000000..ed19b01be --- /dev/null +++ b/mailnews/addrbook/src/nsMapiAddressBook.h @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ +#ifndef nsMapiAddressBook_h___ +#define nsMapiAddressBook_h___ + +#include "mozilla/Attributes.h" +#include "nsAbWinHelper.h" + +class nsMapiAddressBook : public nsAbWinHelper +{ +public : + nsMapiAddressBook(void) ; + virtual ~nsMapiAddressBook(void) ; + +protected : + // Class members to handle the library/entry points + static HMODULE mLibrary ; + static int32_t mLibUsage ; + static LPMAPIINITIALIZE mMAPIInitialize ; + static LPMAPIUNINITIALIZE mMAPIUninitialize ; + static LPMAPIALLOCATEBUFFER mMAPIAllocateBuffer ; + static LPMAPIFREEBUFFER mMAPIFreeBuffer ; + static LPMAPILOGONEX mMAPILogonEx ; + // Shared session and address book used by all instances. + // For reasons best left unknown, MAPI doesn't seem to like + // having different threads playing with supposedly different + // sessions and address books. They ll end up fighting over + // the same resources, with hangups and GPF resulting. Not nice. + // So it seems that if everybody (as long as some client is + // still alive) is using the same sessions and address books, + // MAPI feels better. And who are we to get in the way of MAPI + // happiness? Thus the following class members: + static BOOL mInitialized ; + static BOOL mLogonDone ; + static LPMAPISESSION mRootSession ; + static LPADRBOOK mRootBook ; + + // Load the MAPI environment + BOOL Initialize(void) ; + // Allocation of a buffer for transmission to interfaces + virtual void AllocateBuffer(ULONG aByteCount, LPVOID *aBuffer) override; + // Destruction of a buffer provided by the interfaces + virtual void FreeBuffer(LPVOID aBuffer) override; + // Library management + static BOOL LoadMapiLibrary(void) ; + static void FreeMapiLibrary(void) ; + +private : +} ; + +#endif // nsMapiAddressBook_h___ + diff --git a/mailnews/addrbook/src/nsMsgVCardService.cpp b/mailnews/addrbook/src/nsMsgVCardService.cpp new file mode 100644 index 000000000..91f6f522e --- /dev/null +++ b/mailnews/addrbook/src/nsMsgVCardService.cpp @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsMsgVCardService.h" +#include "nsVCard.h" +#include "prmem.h" +#include "plstr.h" + +NS_IMPL_ISUPPORTS(nsMsgVCardService, nsIMsgVCardService) + +nsMsgVCardService::nsMsgVCardService() +{ +} + +nsMsgVCardService::~nsMsgVCardService() +{ +} + +NS_IMETHODIMP_(void) nsMsgVCardService::CleanVObject(VObject * o) +{ + cleanVObject(o); +} + +NS_IMETHODIMP_(VObject *) nsMsgVCardService::NextVObjectInList(VObject * o) +{ + return nextVObjectInList(o); +} + +NS_IMETHODIMP_(VObject *) nsMsgVCardService::Parse_MIME(const char *input, uint32_t len) +{ + return parse_MIME(input, (unsigned long)len); +} + +NS_IMETHODIMP_(char *) nsMsgVCardService::FakeCString(VObject * o) +{ + return fakeCString(vObjectUStringZValue(o)); +} + +NS_IMETHODIMP_(VObject *) nsMsgVCardService::IsAPropertyOf(VObject * o, const char *id) +{ + return isAPropertyOf(o,id); +} + +NS_IMETHODIMP_(char *) nsMsgVCardService::WriteMemoryVObjects(const char *s, int32_t *len, VObject * list, bool expandSpaces) +{ + return writeMemoryVObjects((char *)s, len, list, expandSpaces); +} + +NS_IMETHODIMP_(VObject *) nsMsgVCardService::NextVObject(VObjectIterator * i) +{ + return nextVObject(i); +} + +NS_IMETHODIMP_(void) nsMsgVCardService::InitPropIterator(VObjectIterator * i, VObject * o) +{ + initPropIterator(i,o); +} + +NS_IMETHODIMP_(int32_t) nsMsgVCardService::MoreIteration(VObjectIterator * i) +{ + return ((int32_t)moreIteration(i)); +} + +NS_IMETHODIMP_(const char *) nsMsgVCardService::VObjectName(VObject * o) +{ + return vObjectName(o); +} + +NS_IMETHODIMP_(char *) nsMsgVCardService::VObjectAnyValue(VObject * o) +{ + char *retval = (char *)PR_MALLOC(strlen((char *)vObjectAnyValue(o)) + 1); + if (retval) + PL_strcpy(retval, (char *) vObjectAnyValue(o)); + return retval; +} diff --git a/mailnews/addrbook/src/nsMsgVCardService.h b/mailnews/addrbook/src/nsMsgVCardService.h new file mode 100644 index 000000000..7e7da8766 --- /dev/null +++ b/mailnews/addrbook/src/nsMsgVCardService.h @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#ifndef nsMsgVCardService_h___ +#define nsMsgVCardService_h___ + +#include "nsIMsgVCardService.h" +#include "nsISupports.h" + +class nsMsgVCardService : public nsIMsgVCardService +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGVCARDSERVICE + + nsMsgVCardService(); + +private: + virtual ~nsMsgVCardService(); +}; + +#endif /* nsMsgVCardService_h___ */ diff --git a/mailnews/addrbook/src/nsVCard.cpp b/mailnews/addrbook/src/nsVCard.cpp new file mode 100644 index 000000000..b8c2455b2 --- /dev/null +++ b/mailnews/addrbook/src/nsVCard.cpp @@ -0,0 +1,1571 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/*************************************************************************** +(C) Copyright 1996 Apple Computer, Inc., AT&T Corp., International +Business Machines Corporation and Siemens Rolm Communications Inc. + +For purposes of this license notice, the term Licensors shall mean, +collectively, Apple Computer, Inc., AT&T Corp., International +Business Machines Corporation and Siemens Rolm Communications Inc. +The term Licensor shall mean any of the Licensors. + +Subject to acceptance of the following conditions, permission is hereby +granted by Licensors without the need for written agreement and without +license or royalty fees, to use, copy, modify and distribute this +software for any purpose. + +The above copyright notice and the following four paragraphs must be +reproduced in all copies of this software and any software including +this software. + +THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS AND NO LICENSOR SHALL HAVE +ANY OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS OR +MODIFICATIONS. + +IN NO EVENT SHALL ANY LICENSOR BE LIABLE TO ANY PARTY FOR DIRECT, +INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT +OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +EACH LICENSOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO ANY WARRANTY OF NONINFRINGEMENT OR THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. + +The software is provided with RESTRICTED RIGHTS. Use, duplication, or +disclosure by the government are subject to restrictions set forth in +DFARS 252.227-7013 or 48 CFR 52.227-19, as applicable. + +***************************************************************************/ + +/* + * src: vcc.c + * doc: Parser for vCard and vCalendar. Note that this code is + * generated by a yacc parser generator. Generally it should not + * be edited by hand. The real source is vcc.y. The #line directives + * can be commented out here to make it easier to trace through + * in a debugger. However, if a bug is found it should + * + * the vcc.y that _this_ vcc.c comes from is lost. + * I couldn't find it in the 4.x tree + * I bet we took it from IMC's original SDK, but the SDK has been taken down. + * see http://www.imc.org/imc-vcard/mail-archive/msg00460.html + * + * for what it's worth, see + * http://softwarestudio.org/libical/ + * http://lxr.mozilla.org/mozilla/source/other-licenses/libical/src/libicalvcal/vcc.y + * http://lxr.mozilla.org/mozilla/source/other-licenses/libical/src/libicalvcal/vcc.c + */ +#include "nsVCard.h" +#include "nsVCardObj.h" +#include "prprf.h" +#include "nscore.h" +#include <string.h> +#include <ctype.h> + +#ifndef lint +char yysccsid[] = "@(#)yaccpar 1.4 (Berkeley) 02/25/90"; +#endif +/*#line 2 "vcc.y" */ + +/* debugging utilities */ +#define DBG_(x) + +#ifndef _NO_LINE_FOLDING +#define _SUPPORT_LINE_FOLDING +#endif + +/**** External Functions ****/ + +/* assign local name to parser variables and functions so that + we can use more than one yacc based parser. +*/ + +#define yyparse mime_parse +#define yylex mime_lex +#define yyerror mime_error +#define yychar mime_char +/* #define p_yyval p_mime_val */ +#undef yyval +#define yyval mime_yyval +/* #define p_yylval p_mime_lval */ +#undef yylval +#define yylval mime_yylval +#define yydebug mime_debug +#define yynerrs mime_nerrs +#define yyerrflag mime_errflag +#define yyss mime_ss +#define yyssp mime_ssp +#define yyvs mime_vs +#define yyvsp mime_vsp +#define yylhs mime_lhs +#define yylen mime_len +#define yydefred mime_defred +#define yydgoto mime_dgoto +#define yysindex mime_sindex +#define yyrindex mime_rindex +#define yygindex mime_gindex +#define yytable mime_table +#define yycheck mime_check +#define yyname mime_name +#define yyrule mime_rule +#define YYPREFIX "mime_" + +#include "prmem.h" +#include "plstr.h" + +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef TRUE +#define TRUE 1 +#endif + +/**** Types, Constants ****/ + +#define YYDEBUG 0 /* 1 to compile in some debugging code */ +#define PR_MAXTOKEN 256 /* maximum token (line) length */ +#define YYSTACKSIZE 50 /* ~unref ?*/ +#define PR_MAXLEVEL 10 /* max # of nested objects parseable */ + /* (includes outermost) */ + + +/**** Global Variables ****/ +int mime_lineNum, mime_numErrors; /* yyerror() can use these */ +static VObject* vObjList; +static VObject *curProp; +static VObject *curObj; +static VObject* ObjStack[PR_MAXLEVEL]; +static int ObjStackTop; + + +/* A helpful utility for the rest of the app. */ +#ifdef __cplusplus +extern "C" { +#endif + + extern void yyerror(const char *s); + extern char** fieldedProp; + +#ifdef __cplusplus + } +#endif + +int yyparse(); + +enum LexMode { + L_NORMAL, + L_VCARD, + L_VCAL, + L_VEVENT, + L_VTODO, + L_VALUES, + L_BASE64, + L_QUOTED_PRINTABLE + }; + +/**** Private Forward Declarations ****/ +static int pushVObject(const char *prop); +static VObject* popVObject(); +static int lexGeta(); +static int lexGetc_(); +static int lexGetc(); +static void lexSkipLookahead(); +static int lexLookahead(); +static void lexSkipWhite(); +static void lexClearToken(); +static char * lexStr(); +static char * lexGetDataFromBase64(); +static char * lexGetQuotedPrintable(); +static char * lexGet1Value(); +static char * lexGetWord(); +static void finiLex(); + +static VObject* parse_MIMEHelper(); + +/*static char* lexDataFromBase64();*/ +static void lexPopMode(int top); +static int lexWithinMode(enum LexMode mode); +static void lexPushMode(enum LexMode mode); +static void enterProps(const char *s); +static void enterAttr(const char *s1, const char *s2); +static void enterValues(const char *value); + +/*#line 250 "vcc.y" */ +typedef union { + char *str; + VObject *vobj; + } YYSTYPE; +/*#line 253 "y_tab.c"*/ +#define EQ 257 +#define COLON 258 +#define DOT 259 +#define SEMICOLON 260 +#define SPACE 261 +#define HTAB 262 +#define LINESEP 263 +#define NEWLINE 264 +#define BEGIN_VCARD 265 +#define END_VCARD 266 +#define BEGIN_VCAL 267 +#define END_VCAL 268 +#define BEGIN_VEVENT 269 +#define END_VEVENT 270 +#define BEGIN_VTODO 271 +#define END_VTODO 272 +#define ID 273 +#define STRING 274 +#define YYERRCODE 256 +short yylhs[] = { -1, + 0, 7, 6, 6, 5, 5, 9, 3, 10, 3, + 8, 8, 14, 11, 11, 16, 12, 12, 15, 15, + 17, 18, 18, 1, 19, 13, 13, 2, 2, 21, + 4, 22, 4, 20, 20, 23, 23, 23, 26, 24, + 27, 24, 28, 25, 29, 25, +}; +short yylen[] = { 2, + 1, 0, 3, 1, 1, 1, 0, 4, 0, 3, + 2, 1, 0, 5, 1, 0, 3, 1, 2, 1, + 2, 1, 3, 1, 0, 4, 1, 1, 0, 0, + 4, 0, 3, 2, 1, 1, 1, 1, 0, 4, + 0, 3, 0, 4, 0, 3, +}; +short yydefred[] = { 0, + 0, 0, 0, 5, 6, 0, 1, 0, 0, 0, + 0, 0, 15, 24, 0, 0, 0, 0, 10, 0, + 0, 38, 0, 0, 36, 37, 33, 3, 0, 8, + 11, 13, 0, 0, 0, 0, 31, 34, 0, 17, + 0, 0, 0, 42, 0, 46, 0, 21, 19, 28, + 0, 0, 40, 44, 0, 25, 14, 23, 0, 26, +}; +short yydgoto[] = { 3, + 15, 51, 4, 5, 6, 7, 12, 22, 8, 9, + 17, 18, 52, 42, 40, 29, 41, 48, 59, 23, + 10, 11, 24, 25, 26, 33, 34, 35, 36, +}; +short yysindex[] = { -227, + 0, 0, 0, 0, 0, 0, 0, -249, -262, -253, + -258, -227, 0, 0, 0, -234, -249, -215, 0, 0, + 0, 0, -223, -253, 0, 0, 0, 0, -247, 0, + 0, 0, -249, -222, -249, -225, 0, 0, -224, 0, + -247, -221, -220, 0, -218, 0, -206, 0, 0, 0, + -208, -207, 0, 0, -224, 0, 0, 0, -221, 0, +}; +short yyrindex[] = { 0, + -245, -254, 0, 0, 0, 1, 0, 0, 0, 0, + 0, 0, 0, 0, -219, 0, -235, 0, 0, -244, + -250, 0, 0, -213, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + -201, -255, 0, 0, 0, 0, -216, 0, 0, 0, + -205, 0, 0, 0, 0, 0, 0, 0, -255, 0, +}; +short yygindex[] = { 0, + -9, 0, 0, 0, 0, 47, 0, -8, 0, 0, + 0, 0, 2, 0, 19, 0, 0, 0, 0, 38, + 0, 0, 0, 0, 0, 0, 0, 0, 0, +}; +#define YYTABLESIZE 268 +short yytable[] = { 16, + 4, 30, 13, 19, 29, 43, 13, 29, 31, 27, + 7, 39, 39, 32, 30, 20, 30, 21, 30, 14, + 9, 45, 43, 14, 43, 41, 45, 7, 39, 47, + 12, 30, 12, 12, 12, 12, 12, 1, 18, 2, + 16, 22, 32, 22, 37, 58, 46, 44, 14, 53, + 55, 56, 50, 54, 35, 57, 20, 27, 28, 49, + 60, 38, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 2, 0, 2, +}; +short yycheck[] = { 8, + 0, 256, 256, 266, 260, 256, 256, 263, 17, 268, + 256, 256, 260, 268, 269, 269, 271, 271, 273, 273, + 266, 272, 273, 273, 33, 270, 35, 273, 273, 39, + 266, 266, 268, 269, 270, 271, 272, 265, 258, 267, + 260, 258, 258, 260, 268, 55, 272, 270, 273, 270, + 257, 260, 274, 272, 268, 263, 258, 263, 12, 41, + 59, 24, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, 265, -1, 267, +}; +#define YYFINAL 3 +#ifndef YYDEBUG +#define YYDEBUG 0 +#endif +#define YYPR_MAXTOKEN 274 +#if YYDEBUG +char *yyname[] = { +"end-of-file",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, +0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"EQ","COLON","DOT","SEMICOLON", +"SPACE","HTAB","LINESEP","NEWLINE","BEGIN_VCARD","END_VCARD","BEGIN_VCAL", +"END_VCAL","BEGIN_VEVENT","END_VEVENT","BEGIN_VTODO","END_VTODO","ID","STRING", +}; +char *yyrule[] = { +"$accept : mime", +"mime : vobjects", +"$$1 :", +"vobjects : vobject $$1 vobjects", +"vobjects : vobject", +"vobject : vcard", +"vobject : vcal", +"$$2 :", +"vcard : BEGIN_VCARD $$2 items END_VCARD", +"$$3 :", +"vcard : BEGIN_VCARD $$3 END_VCARD", +"items : item items", +"items : item", +"$$4 :", +"item : prop COLON $$4 values LINESEP", +"item : error", +"$$5 :", +"prop : name $$5 attr_params", +"prop : name", +"attr_params : attr_param attr_params", +"attr_params : attr_param", +"attr_param : SEMICOLON attr", +"attr : name", +"attr : name EQ name", +"name : ID", +"$$6 :", +"values : value SEMICOLON $$6 values", +"values : value", +"value : STRING", +"value :", +"$$7 :", +"vcal : BEGIN_VCAL $$7 calitems END_VCAL", +"$$8 :", +"vcal : BEGIN_VCAL $$8 END_VCAL", +"calitems : calitem calitems", +"calitems : calitem", +"calitem : eventitem", +"calitem : todoitem", +"calitem : items", +"$$9 :", +"eventitem : BEGIN_VEVENT $$9 items END_VEVENT", +"$$10 :", +"eventitem : BEGIN_VEVENT $$10 END_VEVENT", +"$$11 :", +"todoitem : BEGIN_VTODO $$11 items END_VTODO", +"$$12 :", +"todoitem : BEGIN_VTODO $$12 END_VTODO", +}; +#endif +#define yyclearin (yychar=(-1)) +#define yyerrok (yyerrflag=0) +#ifndef YYSTACKSIZE +#ifdef YYPR_MAXDEPTH +#define YYSTACKSIZE YYPR_MAXDEPTH +#else +#define YYSTACKSIZE 300 +#endif +#endif +int yydebug; +int yynerrs; +int yyerrflag; +int yychar; +short *yyssp; +YYSTYPE *yyvsp; +YYSTYPE yyval; +YYSTYPE yylval; +#define yystacksize YYSTACKSIZE +short yyss[YYSTACKSIZE]; +YYSTYPE yyvs[YYSTACKSIZE]; +/*#line 444 "vcc.y"*/ +/******************************************************************************/ +static int pushVObject(const char *prop) + { + VObject *newObj; + if (ObjStackTop == PR_MAXLEVEL) + return FALSE; + + ObjStack[++ObjStackTop] = curObj; + + if (curObj) { + newObj = addProp(curObj,prop); + curObj = newObj; + } + else + curObj = newVObject(prop); + + return TRUE; + } + + +/******************************************************************************/ +/* This pops the recently built vCard off the stack and returns it. */ +static VObject* popVObject() + { + VObject *oldObj; + if (ObjStackTop < 0) { + yyerror("pop on empty Object Stack\n"); + return 0; + } + oldObj = curObj; + curObj = ObjStack[ObjStackTop--]; + + return oldObj; + } + +extern "C" void deleteString(char *p); + +static void enterValues(const char *value) + { + if (fieldedProp && *fieldedProp) { + if (value) { + addPropValue(curProp,*fieldedProp,value); + } + /* else this field is empty, advance to next field */ + fieldedProp++; + } + else { + if (value) { + setVObjectUStringZValue_(curProp,fakeUnicode(value,0)); + } + } + deleteString((char *)value); + } + +static void enterProps(const char *s) + { + curProp = addGroup(curObj,s); + deleteString((char *)s); + } + +static void enterAttr(const char *s1, const char *s2) +{ + const char *p1, *p2 = nullptr; + p1 = lookupProp_(s1); + if (s2) { + VObject *a; + p2 = lookupProp_(s2); + a = addProp(curProp,p1); + setVObjectStringZValue(a,p2); + } + else + addProp(curProp,p1); + if (PL_strcasecmp(p1,VCBase64Prop) == 0 || (s2 && PL_strcasecmp(p2,VCBase64Prop)==0)) + lexPushMode(L_BASE64); + else if (PL_strcasecmp(p1,VCQuotedPrintableProp) == 0 + || (s2 && PL_strcasecmp(p2,VCQuotedPrintableProp)==0)) + lexPushMode(L_QUOTED_PRINTABLE); + deleteString((char *)s1); deleteString((char *)s2); +} + + +#define PR_MAX_LEX_LOOKAHEAD_0 32 +#define PR_MAX_LEX_LOOKAHEAD 64 +#define PR_MAX_LEX_MODE_STACK_SIZE 10 +#define LEXMODE() (lexBuf.lexModeStack[lexBuf.lexModeStackTop]) + +struct LexBuf { + /* input */ + char *inputString; + unsigned long curPos; + unsigned long inputLen; + /* lookahead buffer */ + /* -- lookahead buffer is short instead of char so that EOF + / can be represented correctly. + */ + unsigned long len; + short buf[PR_MAX_LEX_LOOKAHEAD]; + unsigned long getPtr; + /* context stack */ + unsigned long lexModeStackTop; + enum LexMode lexModeStack[PR_MAX_LEX_MODE_STACK_SIZE]; + /* token buffer */ + unsigned long maxToken; + char *strs; + unsigned long strsLen; + } lexBuf; + +static void lexPushMode(enum LexMode mode) + { + if (lexBuf.lexModeStackTop == (PR_MAX_LEX_MODE_STACK_SIZE-1)) + yyerror("lexical context stack overflow"); + else { + lexBuf.lexModeStack[++lexBuf.lexModeStackTop] = mode; + } + } + +static void lexPopMode(int top) + { + /* special case of pop for ease of error recovery -- this + version will never underflow */ + if (top) + lexBuf.lexModeStackTop = 0; + else + if (lexBuf.lexModeStackTop > 0) lexBuf.lexModeStackTop--; + } + +static int lexWithinMode(enum LexMode mode) { + unsigned long i; + for (i=0;i<lexBuf.lexModeStackTop;i++) + if (mode == lexBuf.lexModeStack[i]) return 1; + return 0; + } + +static int lexGetc_() +{ + /* get next char from input, no buffering. */ + if (lexBuf.curPos == lexBuf.inputLen) + return EOF; + else if (lexBuf.inputString) + return *(lexBuf.inputString + lexBuf.curPos++); + + return -1; +} + +static int lexGeta() + { + ++lexBuf.len; + return (lexBuf.buf[lexBuf.getPtr] = lexGetc_()); + } + +static int lexGeta_(int i) + { + ++lexBuf.len; + return (lexBuf.buf[(lexBuf.getPtr+i)%PR_MAX_LEX_LOOKAHEAD] = lexGetc_()); + } + +static void lexSkipLookahead() { + if (lexBuf.len > 0 && lexBuf.buf[lexBuf.getPtr]!=EOF) { + /* don't skip EOF. */ + lexBuf.getPtr = (lexBuf.getPtr + 1) % PR_MAX_LEX_LOOKAHEAD; + lexBuf.len--; + } + } + +static int lexLookahead() { + int c = (lexBuf.len)? + lexBuf.buf[lexBuf.getPtr]: + lexGeta(); + /* do the \r\n -> \n or \r -> \n translation here */ + if (c == '\r') { + int a = (lexBuf.len>1)? + lexBuf.buf[(lexBuf.getPtr+1)%PR_MAX_LEX_LOOKAHEAD]: + lexGeta_(1); + if (a == '\n') { + lexSkipLookahead(); + } + lexBuf.buf[lexBuf.getPtr] = c = '\n'; + } + else if (c == '\n') { + int a = (lexBuf.len>1)? + lexBuf.buf[lexBuf.getPtr+1]: + lexGeta_(1); + if (a == '\r') { + lexSkipLookahead(); + } + lexBuf.buf[lexBuf.getPtr] = '\n'; + } + return c; + } + +static int lexGetc() { + int c = lexLookahead(); + if (lexBuf.len > 0 && lexBuf.buf[lexBuf.getPtr]!=EOF) { + /* EOF will remain in lookahead buffer */ + lexBuf.getPtr = (lexBuf.getPtr + 1) % PR_MAX_LEX_LOOKAHEAD; + lexBuf.len--; + } + return c; + } + +static void lexSkipLookaheadWord() { + if (lexBuf.strsLen <= lexBuf.len) { + lexBuf.len -= lexBuf.strsLen; + lexBuf.getPtr = (lexBuf.getPtr + lexBuf.strsLen) % PR_MAX_LEX_LOOKAHEAD; + } + } + +static void lexClearToken() + { + lexBuf.strsLen = 0; + } + +static void lexAppendc(int c) + { + lexBuf.strs[lexBuf.strsLen] = c; + /* append up to zero termination */ + if (c == 0) return; + lexBuf.strsLen++; + if (lexBuf.strsLen >= lexBuf.maxToken) { + /* double the token string size */ + lexBuf.maxToken <<= 1; + lexBuf.strs = (char*) PR_Realloc(lexBuf.strs,lexBuf.maxToken); + } + } + +static char* lexStr() { + return dupStr(lexBuf.strs,lexBuf.strsLen+1); + } + +static void lexSkipWhite() { + int c = lexLookahead(); + while (c == ' ' || c == '\t') { + lexSkipLookahead(); + c = lexLookahead(); + } + } + +static char* lexGetWord() { + int c; + lexSkipWhite(); + lexClearToken(); + c = lexLookahead(); + while (c != EOF && !PL_strchr("\t\n ;:=",(char)c)) { + lexAppendc(c); + lexSkipLookahead(); + c = lexLookahead(); + } + lexAppendc(0); + return lexStr(); + } + +#if 0 +static void lexPushLookahead(char *s, int len) { + int putptr; + if (len == 0) len = PL_strlen(s); + putptr = lexBuf.getPtr - len; + /* this function assumes that length of word to push back + / is not greater than PR_MAX_LEX_LOOKAHEAD. + */ + if (putptr < 0) putptr += PR_MAX_LEX_LOOKAHEAD; + lexBuf.getPtr = putptr; + while (*s) { + lexBuf.buf[putptr] = *s++; + putptr = (putptr + 1) % PR_MAX_LEX_LOOKAHEAD; + } + lexBuf.len += len; + } +#endif + +static void lexPushLookaheadc(int c) { + int putptr; + /* can't putback EOF, because it never leaves lookahead buffer */ + if (c == EOF) return; + putptr = (int) lexBuf.getPtr - 1; + if (putptr < 0) putptr += PR_MAX_LEX_LOOKAHEAD; + lexBuf.getPtr = putptr; + lexBuf.buf[putptr] = c; + lexBuf.len += 1; + } + +static char* lexLookaheadWord() { + /* this function can lookahead word with max size of PR_MAX_LEX_LOOKAHEAD_0 + / and thing bigger than that will stop the lookahead and return 0; + / leading white spaces are not recoverable. + */ + int c; + int len = 0; + int curgetptr = 0; + lexSkipWhite(); + lexClearToken(); + curgetptr = (int) lexBuf.getPtr; /* remember! */ + while (len < (PR_MAX_LEX_LOOKAHEAD_0)) { + c = lexGetc(); + len++; + if (c == EOF || PL_strchr("\t\n ;:=", (char)c)) { + lexAppendc(0); + /* restore lookahead buf. */ + lexBuf.len += len; + lexBuf.getPtr = curgetptr; + return lexStr(); + } + else + lexAppendc(c); + } + lexBuf.len += len; /* char that has been moved to lookahead buffer */ + lexBuf.getPtr = curgetptr; + return 0; + } + +#ifdef _SUPPORT_LINE_FOLDING +static void handleMoreRFC822LineBreak(int c) { + /* support RFC 822 line break in cases like + * ADR: foo; + * morefoo; + * more foo; + */ + if (c == ';') { + int a; + lexSkipLookahead(); + /* skip white spaces */ + a = lexLookahead(); + while (a == ' ' || a == '\t') { + lexSkipLookahead(); + a = lexLookahead(); + } + if (a == '\n') { + lexSkipLookahead(); + a = lexLookahead(); + if (a == ' ' || a == '\t') { + /* continuation, throw away all the \n and spaces read so + * far + */ + lexSkipWhite(); + lexPushLookaheadc(';'); + } + else { + lexPushLookaheadc('\n'); + lexPushLookaheadc(';'); + } + } + else { + lexPushLookaheadc(';'); + } + } + } + +static char* lexGet1Value() { +/* int size = 0; */ + int c; + lexSkipWhite(); + c = lexLookahead(); + lexClearToken(); + while (c != EOF && c != ';') { + if (c == '\n') { + int a; + lexSkipLookahead(); + a = lexLookahead(); + if (a == ' ' || a == '\t') { + lexAppendc(' '); + lexSkipLookahead(); + } + else { + lexPushLookaheadc('\n'); + break; + } + } + else if (c == '\\') { + int a; + lexSkipLookahead(); + a = lexLookahead(); + if (a == '\\' || a == ',' || a == ';' || a == ':') { + lexAppendc(a); + } + else if (a == 'n' || a == 'N') { + lexAppendc('\n'); + } + else { + lexAppendc(c); + lexAppendc(a); + } + lexSkipLookahead(); + } + else { + lexAppendc(c); + lexSkipLookahead(); + } + c = lexLookahead(); + } + lexAppendc(0); + handleMoreRFC822LineBreak(c); + return c==EOF?0:lexStr(); + } +#endif + + +#ifndef _SUPPORT_LINE_FOLDING +static char* lexGetStrUntil(char *termset) { + int c = lexLookahead(); + lexClearToken(); + while (c != EOF && !PL_strchr(termset,c)) { + lexAppendc(c); + lexSkipLookahead(); + c = lexLookahead(); + } + lexAppendc(0); + return c==EOF?0:lexStr(); + } +#endif /* ! _SUPPORT_LINE_FOLDING */ + +static int match_begin_name(int end) { + char *n = lexLookaheadWord(); + int token = ID; + if (n) { + if (!PL_strcasecmp(n,"vcard")) token = end?END_VCARD:BEGIN_VCARD; + else if (!PL_strcasecmp(n,"vcalendar")) token = end?END_VCAL:BEGIN_VCAL; + else if (!PL_strcasecmp(n,"vevent")) token = end?END_VEVENT:BEGIN_VEVENT; + else if (!PL_strcasecmp(n,"vtodo")) token = end?END_VTODO:BEGIN_VTODO; + deleteString(n); + return token; + } + return 0; + } + +void initLex(const char *inputstring, unsigned long inputlen) + { + /* initialize lex mode stack */ + lexBuf.lexModeStack[lexBuf.lexModeStackTop=0] = L_NORMAL; + + /* iniatialize lex buffer. */ + lexBuf.inputString = (char*) inputstring; + lexBuf.inputLen = inputlen; + lexBuf.curPos = 0; + + lexBuf.len = 0; + lexBuf.getPtr = 0; + + lexBuf.maxToken = PR_MAXTOKEN; + lexBuf.strs = (char*)PR_CALLOC(PR_MAXTOKEN); + lexBuf.strsLen = 0; + + } + +static void finiLex() { + PR_FREEIF(lexBuf.strs); + } + + +/******************************************************************************/ +/* This parses and converts the base64 format for binary encoding into + * a decoded buffer (allocated with new). See RFC 1521. + */ +static char * lexGetDataFromBase64() + { + unsigned long bytesLen = 0, bytesMax = 0; + int quadIx = 0, pad = 0; + unsigned long trip = 0; + unsigned char b; + int c; + unsigned char *bytes = nullptr; + unsigned char *oldBytes = nullptr; + + DBG_(("db: lexGetDataFromBase64\n")); + while (1) { + c = lexGetc(); + if (c == '\n') { + ++mime_lineNum; + if (lexLookahead() == '\n') { + /* a '\n' character by itself means end of data */ + break; + } + else continue; /* ignore '\n' */ + } + else { + if ((c >= 'A') && (c <= 'Z')) + b = (unsigned char)(c - 'A'); + else if ((c >= 'a') && (c <= 'z')) + b = (unsigned char)(c - 'a') + 26; + else if ((c >= '0') && (c <= '9')) + b = (unsigned char)(c - '0') + 52; + else if (c == '+') + b = 62; + else if (c == '/') + b = 63; + else if (c == '=' && (quadIx == 2 || quadIx == 3)) { + b = 0; + pad++; + } else if ((c == ' ') || (c == '\t')) { + continue; + } else { /* error condition */ + if (bytes) + PR_Free (bytes); + else if (oldBytes) + PR_Free (oldBytes); + /* error recovery: skip until 2 adjacent newlines. */ + DBG_(("db: invalid character 0x%x '%c'\n", c,c)); + if (c != EOF) { + c = lexGetc(); + while (c != EOF) { + if (c == '\n' && lexLookahead() == '\n') { + ++mime_lineNum; + break; + } + c = lexGetc(); + } + } + return NULL; + } + trip = (trip << 6) | b; + if (++quadIx == 4) { + unsigned char outBytes[3]; + int numOut; + int i; + for (i = 0; i < 3; i++) { + outBytes[2-i] = (unsigned char)(trip & 0xFF); + trip >>= 8; + } + numOut = 3 - pad; + if (bytesLen + numOut > bytesMax) { + if (!bytes) { + bytesMax = 1024; + } else { + bytesMax <<= 2; + oldBytes = bytes; + } + bytes = (unsigned char*) PR_Realloc(oldBytes, bytesMax); + if (!bytes) { + mime_error("out of memory while processing BASE64 data\n"); + break; + } + } + if (bytes) { + memcpy(bytes + bytesLen, outBytes, numOut); + bytesLen += numOut; + } + trip = 0; + quadIx = 0; + pad = 0; + } + } + } /* while */ + DBG_(("db: bytesLen = %d\n", bytesLen)); + /* kludge: all this won't be necessary if we have tree form + representation */ + if (bytes) { + setValueWithSize(curProp,bytes,(unsigned int)bytesLen); + PR_FREEIF(bytes); + } + else if (oldBytes) { + setValueWithSize(curProp,oldBytes,(unsigned int)bytesLen); + PR_FREEIF(oldBytes); + } + return 0; + } + +static int match_begin_end_name(int end) { + int token; + lexSkipWhite(); + if (lexLookahead() != ':') return ID; + lexSkipLookahead(); + lexSkipWhite(); + token = match_begin_name(end); + if (token == ID) { + lexPushLookaheadc(':'); + DBG_(("db: ID '%s'\n", yylval.str)); + return ID; + } + else if (token != 0) { + lexSkipLookaheadWord(); + deleteString(yylval.str); + DBG_(("db: begin/end %d\n", token)); + return token; + } + return 0; + } + +static char* lexGetQuotedPrintable() + { + char cur; +/* unsigned long len = 0; */ + + lexClearToken(); + do { + cur = lexGetc(); + switch (cur) { + case '=': { + int c = 0; + int next[2]; + int tab [1]; + int i; + for (i = 0; i < 2; i++) { + next[i] = lexGetc(); + if (next[i] >= '0' && next[i] <= '9') + c = c * 16 + next[i] - '0'; + else if (next[i] >= 'A' && next[i] <= 'F') + c = c * 16 + next[i] - 'A' + 10; + else + break; + } + if (i == 0) { + /* single '=' follow by LINESEP is continuation sign? */ + if (next[0] == '\n') { + tab[0] = lexGetc(); + if (tab[0] == '\t') + lexSkipWhite(); + ++mime_lineNum; + } + else { + lexAppendc(cur); + /* lexPushLookaheadc('='); + goto EndString; */ + } + } + else if (i == 1) { + lexPushLookaheadc(next[1]); + lexPushLookaheadc(next[0]); + lexAppendc('='); + } else { + lexAppendc(c); + } + break; + } /* '=' */ + case '\n': { + lexPushLookaheadc('\n'); + goto EndString; + } + case ';': { + lexPushLookaheadc(';'); + goto EndString; + } + case (char)EOF: + break; + default: + lexAppendc(cur); + break; + } /* switch */ + } while (cur != (char)EOF); + +EndString: + lexAppendc(0); + return lexStr(); + } /* LexQuotedPrintable */ + +static int yylex() { +/* int token = 0; */ + + int lexmode = LEXMODE(); + if (lexmode == L_VALUES) { + int c = lexGetc(); + if (c == ';') { + DBG_(("db: SEMICOLON\n")); +#ifdef _SUPPORT_LINE_FOLDING + lexPushLookaheadc(c); + handleMoreRFC822LineBreak(c); + lexSkipLookahead(); +#endif + return SEMICOLON; + } + else if (PL_strchr("\n",(char)c)) { + ++mime_lineNum; + /* consume all line separator(s) adjacent to each other */ + c = lexLookahead(); + while (PL_strchr("\n",(char)c)) { + lexSkipLookahead(); + c = lexLookahead(); + ++mime_lineNum; + } + DBG_(("db: LINESEP\n")); + return LINESEP; + } + else { + char *p = 0; + lexPushLookaheadc(c); + if (lexWithinMode(L_BASE64)) { + /* get each char and convert to bin on the fly... */ + p = lexGetDataFromBase64(); + yylval.str = p; + return !p && lexLookahead() == EOF ? 0 : STRING; + } + else if (lexWithinMode(L_QUOTED_PRINTABLE)) { + p = lexGetQuotedPrintable(); + } + else { +#ifdef _SUPPORT_LINE_FOLDING + p = lexGet1Value(); +#else + p = lexGetStrUntil(";\n"); +#endif + } + if (p && (*p || lexLookahead() != EOF)) { + DBG_(("db: STRING: '%s'\n", p)); + yylval.str = p; + return STRING; + } + else return 0; + } + } + else { + /* normal mode */ + while (1) { + int c = lexGetc(); + switch(c) { + case ':': { + /* consume all line separator(s) adjacent to each other */ + /* ignoring linesep immediately after colon. */ + c = lexLookahead(); + while (PL_strchr("\n",(char)c)) { + lexSkipLookahead(); + c = lexLookahead(); + ++mime_lineNum; + } + DBG_(("db: COLON\n")); + return COLON; + } + case ';': + DBG_(("db: SEMICOLON\n")); + return SEMICOLON; + case '=': + DBG_(("db: EQ\n")); + return EQ; + /* ignore whitespace in this mode */ + case '\t': + case ' ': continue; + case '\n': { + ++mime_lineNum; + continue; + } + case EOF: return 0; + break; + default: { + lexPushLookaheadc(c); + if (isalpha(c)) { + char *t = lexGetWord(); + yylval.str = t; + if (!PL_strcasecmp(t, "BEGIN")) { + return match_begin_end_name(0); + } + else if (!PL_strcasecmp(t,"END")) { + return match_begin_end_name(1); + } + else { + DBG_(("db: ID '%s'\n", t)); + return ID; + } + } + else { + /* unknown token */ + return 0; + } + break; + } + } + } + } + return 0; + } + + +/***************************************************************************/ +/*** Public Functions ****/ +/***************************************************************************/ + +static VObject* parse_MIMEHelper() + { + ObjStackTop = -1; + mime_numErrors = 0; + mime_lineNum = 1; + vObjList = 0; + curObj = 0; + + if (yyparse() != 0) + return 0; + + finiLex(); + return vObjList; + } + +/******************************************************************************/ +VObject* parse_MIME(const char *input, unsigned long len) + { + initLex(input, len); + return parse_MIMEHelper(); + } + +static MimeErrorHandler mimeErrorHandler; + +void registerMimeErrorHandler(MimeErrorHandler me) + { + mimeErrorHandler = me; + } + +void mime_error(const char *s) +{ + char msg[256]; + if (mimeErrorHandler) { + PR_snprintf(msg, sizeof(msg), "%s at line %d", s, mime_lineNum); + mimeErrorHandler(msg); + } +} + +/*#line 1221 "y_tab.c"*/ +#define YYABORT goto yyabort +#define YYACCEPT goto yyaccept +#define YYERROR goto yyerrlab +int +yyparse() +{ + int yym, yyn, yystate; +#if YYDEBUG + char *yys; + extern char *getenv(); + + if (yys = getenv("YYDEBUG")) + { + yyn = *yys; + if (yyn >= '0' && yyn <= '9') + yydebug = yyn - '0'; + } +#endif + + yynerrs = 0; + yyerrflag = 0; + yychar = (-1); + + yyssp = yyss; + yyvsp = yyvs; + *yyssp = yystate = 0; + +yyloop: + if ((yyn = yydefred[yystate])) goto yyreduce; + if (yychar < 0) + { + if ((yychar = yylex()) < 0) yychar = 0; +#if YYDEBUG + if (yydebug) + { + yys = 0; + if (yychar <= YYPR_MAXTOKEN) yys = yyname[yychar]; + if (!yys) yys = "illegal-symbol"; + printf("yydebug: state %d, reading %d (%s)\n", yystate, + yychar, yys); + } +#endif + } + if ((yyn = yysindex[yystate]) && (yyn += yychar) >= 0 && + yyn <= YYTABLESIZE && yycheck[yyn] == yychar) + { +#if YYDEBUG + if (yydebug) + printf("yydebug: state %d, shifting to state %d\n", + yystate, yytable[yyn]); +#endif + if (yyssp >= yyss + yystacksize - 1) + { + goto yyoverflow; + } + *++yyssp = yystate = yytable[yyn]; + *++yyvsp = yylval; + yychar = (-1); + if (yyerrflag > 0) --yyerrflag; + goto yyloop; + } + if ((yyn = yyrindex[yystate]) && (yyn += yychar) >= 0 && + yyn <= YYTABLESIZE && yycheck[yyn] == yychar) + { + yyn = yytable[yyn]; + goto yyreduce; + } + if (yyerrflag) goto yyinrecovery; +#ifdef lint + goto yynewerror; +#endif +/*yynewerror: */ + yyerror("syntax error"); +#ifdef lint + goto yyerrlab; +#endif +yyerrlab: + ++yynerrs; +yyinrecovery: + if (yyerrflag < 3) + { + yyerrflag = 3; + for (;;) + { + if ((yyn = yysindex[*yyssp]) && (yyn += YYERRCODE) >= 0 && + yyn <= YYTABLESIZE && yycheck[yyn] == YYERRCODE) + { +#if YYDEBUG + if (yydebug) + printf("yydebug: state %d, error recovery shifting\ + to state %d\n", *yyssp, yytable[yyn]); +#endif + if (yyssp >= yyss + yystacksize - 1) + { + goto yyoverflow; + } + *++yyssp = yystate = yytable[yyn]; + *++yyvsp = yylval; + goto yyloop; + } + else + { +#if YYDEBUG + if (yydebug) + printf("yydebug: error recovery discarding state %d\n", + *yyssp); +#endif + if (yyssp <= yyss) goto yyabort; + --yyssp; + --yyvsp; + } + } + } + else + { + if (yychar == 0) goto yyabort; +#if YYDEBUG + if (yydebug) + { + yys = 0; + if (yychar <= YYPR_MAXTOKEN) yys = yyname[yychar]; + if (!yys) yys = "illegal-symbol"; + printf("yydebug: state %d, error recovery discards token %d (%s)\n", + yystate, yychar, yys); + } +#endif + yychar = (-1); + goto yyloop; + } +yyreduce: +#if YYDEBUG + if (yydebug) + printf("yydebug: state %d, reducing by rule %d (%s)\n", + yystate, yyn, yyrule[yyn]); +#endif + yym = yylen[yyn]; + yyval = yyvsp[1-yym]; + switch (yyn) + { +case 2: +/*#line 282 "vcc.y"*/ +{ addList(&vObjList, yyvsp[0].vobj ); curObj = 0; } +break; +case 4: +/*#line 285 "vcc.y"*/ +{ addList(&vObjList, yyvsp[0].vobj ); curObj = 0; } +break; +case 7: +/*#line 294 "vcc.y"*/ +{ + lexPushMode(L_VCARD); + if (!pushVObject(VCCardProp)) YYERROR; + } +break; +case 8: +/*#line 299 "vcc.y"*/ +{ + lexPopMode(0); + yyval.vobj = popVObject(); + } +break; +case 9: +/*#line 304 "vcc.y"*/ +{ + lexPushMode(L_VCARD); + if (!pushVObject(VCCardProp)) YYERROR; + } +break; +case 10: +/*#line 309 "vcc.y"*/ +{ + lexPopMode(0); + yyval.vobj = popVObject(); + } +break; +case 13: +/*#line 320 "vcc.y"*/ +{ + lexPushMode(L_VALUES); + } +break; +case 14: +/*#line 324 "vcc.y"*/ +{ + if (lexWithinMode(L_BASE64) || lexWithinMode(L_QUOTED_PRINTABLE)) + lexPopMode(0); + lexPopMode(0); + } +break; +case 16: +/*#line 332 "vcc.y"*/ +{ + enterProps(yyvsp[0].str ); + } +break; +case 18: +/*#line 337 "vcc.y"*/ +{ + enterProps(yyvsp[0].str ); + } +break; +case 22: +/*#line 350 "vcc.y"*/ +{ + enterAttr(yyvsp[0].str ,0); + } +break; +case 23: +/*#line 354 "vcc.y"*/ +{ + enterAttr(yyvsp[-2].str ,yyvsp[0].str ); + + } +break; +case 25: +/*#line 363 "vcc.y"*/ +{ enterValues(yyvsp[-1].str ); } +break; +case 27: +/*#line 365 "vcc.y"*/ +{ enterValues(yyvsp[0].str ); } +break; +case 29: +/*#line 370 "vcc.y"*/ +{ yyval.str = 0; } +break; +case 30: +/*#line 375 "vcc.y"*/ +{ if (!pushVObject(VCCalProp)) YYERROR; } +break; +case 31: +/*#line 378 "vcc.y"*/ +{ yyval.vobj = popVObject(); } +break; +case 32: +/*#line 380 "vcc.y"*/ +{ if (!pushVObject(VCCalProp)) YYERROR; } +break; +case 33: +/*#line 382 "vcc.y"*/ +{ yyval.vobj = popVObject(); } +break; +case 39: +/*#line 397 "vcc.y"*/ +{ + lexPushMode(L_VEVENT); + if (!pushVObject(VCEventProp)) YYERROR; + } +break; +case 40: +/*#line 403 "vcc.y"*/ +{ + lexPopMode(0); + popVObject(); + } +break; +case 41: +/*#line 408 "vcc.y"*/ +{ + lexPushMode(L_VEVENT); + if (!pushVObject(VCEventProp)) YYERROR; + } +break; +case 42: +/*#line 413 "vcc.y"*/ +{ + lexPopMode(0); + popVObject(); + } +break; +case 43: +/*#line 421 "vcc.y"*/ +{ + lexPushMode(L_VTODO); + if (!pushVObject(VCTodoProp)) YYERROR; + } +break; +case 44: +/*#line 427 "vcc.y"*/ +{ + lexPopMode(0); + popVObject(); + } +break; +case 45: +/*#line 432 "vcc.y"*/ +{ + lexPushMode(L_VTODO); + if (!pushVObject(VCTodoProp)) YYERROR; + } +break; +case 46: +/*#line 437 "vcc.y"*/ +{ + lexPopMode(0); + popVObject(); + } +break; +/*#line 1520 "y_tab.c"*/ + } + yyssp -= yym; + yystate = *yyssp; + yyvsp -= yym; + yym = yylhs[yyn]; + if (yystate == 0 && yym == 0) + { +#if YYDEBUG + if (yydebug) + printf("yydebug: after reduction, shifting from state 0 to\ + state %d\n", YYFINAL); +#endif + yystate = YYFINAL; + *++yyssp = YYFINAL; + *++yyvsp = yyval; + if (yychar < 0) + { + if ((yychar = yylex()) < 0) yychar = 0; +#if YYDEBUG + if (yydebug) + { + yys = 0; + if (yychar <= YYPR_MAXTOKEN) yys = yyname[yychar]; + if (!yys) yys = "illegal-symbol"; + printf("yydebug: state %d, reading %d (%s)\n", + YYFINAL, yychar, yys); + } +#endif + } + if (yychar == 0) goto yyaccept; + goto yyloop; + } + if ((yyn = yygindex[yym]) && (yyn += yystate) >= 0 && + yyn <= YYTABLESIZE && yycheck[yyn] == yystate) + yystate = yytable[yyn]; + else + yystate = yydgoto[yym]; +#if YYDEBUG + if (yydebug) + printf("yydebug: after reduction, shifting from state %d \ +to state %d\n", *yyssp, yystate); +#endif + if (yyssp >= yyss + yystacksize - 1) + { + goto yyoverflow; + } + *++yyssp = yystate; + *++yyvsp = yyval; + goto yyloop; +yyoverflow: + yyerror("yacc stack overflow"); +yyabort: + return (1); +yyaccept: + return (0); +} diff --git a/mailnews/addrbook/src/nsVCard.h b/mailnews/addrbook/src/nsVCard.h new file mode 100644 index 000000000..041cf0042 --- /dev/null +++ b/mailnews/addrbook/src/nsVCard.h @@ -0,0 +1,64 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + + +/*************************************************************************** +(C) Copyright 1996 Apple Computer, Inc., AT&T Corp., International +Business Machines Corporation and Siemens Rolm Communications Inc. + +For purposes of this license notice, the term Licensors shall mean, +collectively, Apple Computer, Inc., AT&T Corp., International +Business Machines Corporation and Siemens Rolm Communications Inc. +The term Licensor shall mean any of the Licensors. + +Subject to acceptance of the following conditions, permission is hereby +granted by Licensors without the need for written agreement and without +license or royalty fees, to use, copy, modify and distribute this +software for any purpose. + +The above copyright notice and the following four paragraphs must be +reproduced in all copies of this software and any software including +this software. + +THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS AND NO LICENSOR SHALL HAVE +ANY OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS OR +MODIFICATIONS. + +IN NO EVENT SHALL ANY LICENSOR BE LIABLE TO ANY PARTY FOR DIRECT, +INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT +OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +EACH LICENSOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO ANY WARRANTY OF NONINFRINGEMENT OR THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. + +The software is provided with RESTRICTED RIGHTS. Use, duplication, or +disclosure by the government are subject to restrictions set forth in +DFARS 252.227-7013 or 48 CFR 52.227-19, as applicable. + +***************************************************************************/ + +#ifndef __VCC_H__ +#define __VCC_H__ 1 + +#include "nsVCardObj.h" + +#ifdef __cplusplus +extern "C" { +#endif + +VObject* parse_MIME(const char *input, unsigned long len); + +typedef void (*MimeErrorHandler)(char *); + +void registerMimeErrorHandler(MimeErrorHandler); + +#ifdef __cplusplus +} +#endif + +#endif /* __VCC_H__ */ diff --git a/mailnews/addrbook/src/nsVCardObj.cpp b/mailnews/addrbook/src/nsVCardObj.cpp new file mode 100644 index 000000000..bf8ceb2fb --- /dev/null +++ b/mailnews/addrbook/src/nsVCardObj.cpp @@ -0,0 +1,1330 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/*************************************************************************** +(C) Copyright 1996 Apple Computer, Inc., AT&T Corp., International +Business Machines Corporation and Siemens Rolm Communications Inc. + +For purposes of this license notice, the term Licensors shall mean, +collectively, Apple Computer, Inc., AT&T Corp., International +Business Machines Corporation and Siemens Rolm Communications Inc. +The term Licensor shall mean any of the Licensors. + +Subject to acceptance of the following conditions, permission is hereby +granted by Licensors without the need for written agreement and without +license or royalty fees, to use, copy, modify and distribute this +software for any purpose. + +The above copyright notice and the following four paragraphs must be +reproduced in all copies of this software and any software including +this software. + +THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS AND NO LICENSOR SHALL HAVE +ANY OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS OR +MODIFICATIONS. + +IN NO EVENT SHALL ANY LICENSOR BE LIABLE TO ANY PARTY FOR DIRECT, +INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT +OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +EACH LICENSOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO ANY WARRANTY OF NONINFRINGEMENT OR THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. + +The software is provided with RESTRICTED RIGHTS. Use, duplication, or +disclosure by the government are subject to restrictions set forth in +DFARS 252.227-7013 or 48 CFR 52.227-19, as applicable. + +***************************************************************************/ + +/* + * doc: vobject and APIs to construct vobject, APIs pretty print + * vobject, and convert a vobject into its textual representation. + */ + +#include "prlog.h" +#include "nsVCard.h" +#include "nsVCardObj.h" +#include "prmem.h" +#include "plstr.h" +#include "prprf.h" +#include "nsStringGlue.h" + +/* debugging utilities */ +#define DBG_(x) + +#ifdef __cplusplus +extern "C" { +#endif + + char **fieldedProp; + +#ifdef __cplusplus + } +#endif + + + +static VObject* newVObject_(const char *id); +#if 0 +static int vObjectValueType(VObject *o); +static void initVObjectIterator(VObjectIterator *i, VObject *o); +#endif + +/*---------------------------------------------------------------------- + The following functions involve with memory allocation: + newVObject + deleteVObject + dupStr + deleteString + newStrItem + deleteStrItem + ----------------------------------------------------------------------*/ + +static bool needsQuotedPrintable (const char *s) +{ + const unsigned char *p = (const unsigned char *)s; + + while (*p) { + if (*p & 0x80 || *p == '\015' || *p == '\012') + return true; + p++; + } + + return false; +} + +VObject* newVObject_(const char *id) +{ + VObject *p = (VObject*) new(VObject); + p->next = 0; + p->id = id; + p->prop = 0; + VALUE_TYPE(p) = 0; + ANY_VALUE_OF(p) = 0; + return p; +} + +VObject* newVObject(const char *id) +{ + return newVObject_(lookupStr(id)); +} + +void deleteVObject(VObject *p) +{ + unUseStr(p->id); + delete (p); +} + +char* dupStr(const char *s, unsigned int size) +{ + char *t; + if (size == 0) { + size = PL_strlen(s); + } + t = (char*)PR_CALLOC(size+1); + if (t) { + memcpy(t,s,size); + t[size] = 0; + return t; + } + else { + return (char*)0; + } +} + +static StrItem* newStrItem(const char *s, StrItem *next) +{ + StrItem *p = (StrItem*)PR_CALLOC(sizeof(StrItem)); + p->next = next; + p->s = s; + p->refCnt = 1; + return p; +} + +extern "C" +void deleteString(char *p) +{ + if (p) + PR_Free ((void*)p); +} + +extern "C" +void deleteStrItem(StrItem *p) +{ + if (p) + PR_FREEIF (p); +} + + + +/*---------------------------------------------------------------------- + The following function provide accesses to VObject's value. + ----------------------------------------------------------------------*/ + +const char* vObjectName(VObject *o) +{ + return NAME_OF(o); +} + +void setVObjectName(VObject *o, const char* id) +{ + NAME_OF(o) = id; +} + +const char* vObjectStringZValue(VObject *o) +{ + return STRINGZ_VALUE_OF(o); +} + +void setVObjectStringZValue(VObject *o, const char *s) +{ + STRINGZ_VALUE_OF(o) = dupStr(s,0); + VALUE_TYPE(o) = VCVT_STRINGZ; +} + +void setVObjectStringZValue_(VObject *o, const char *s) +{ + STRINGZ_VALUE_OF(o) = s; + VALUE_TYPE(o) = VCVT_STRINGZ; +} + +const vwchar_t* vObjectUStringZValue(VObject *o) +{ + return USTRINGZ_VALUE_OF(o); +} + +void setVObjectUStringZValue(VObject *o, const vwchar_t *s) +{ + USTRINGZ_VALUE_OF(o) = (vwchar_t*) dupStr((char*)s,(uStrLen(s)+1)*2); + VALUE_TYPE(o) = VCVT_USTRINGZ; +} + +void setVObjectUStringZValue_(VObject *o, const vwchar_t *s) +{ + USTRINGZ_VALUE_OF(o) = s; + VALUE_TYPE(o) = VCVT_USTRINGZ; +} + +unsigned int vObjectIntegerValue(VObject *o) +{ + return INTEGER_VALUE_OF(o); +} + +void setVObjectIntegerValue(VObject *o, unsigned int i) +{ + INTEGER_VALUE_OF(o) = i; + VALUE_TYPE(o) = VCVT_UINT; +} + +unsigned long vObjectLongValue(VObject *o) +{ + return LONG_VALUE_OF(o); +} + +void setVObjectLongValue(VObject *o, unsigned long l) +{ + LONG_VALUE_OF(o) = l; + VALUE_TYPE(o) = VCVT_ULONG; +} + +void* vObjectAnyValue(VObject *o) +{ + return ANY_VALUE_OF(o); +} + +void setVObjectAnyValue(VObject *o, void *t) +{ + ANY_VALUE_OF(o) = t; + VALUE_TYPE(o) = VCVT_RAW; +} + +VObject* vObjectVObjectValue(VObject *o) +{ + return VOBJECT_VALUE_OF(o); +} + +void setVObjectVObjectValue(VObject *o, VObject *p) +{ + VOBJECT_VALUE_OF(o) = p; + VALUE_TYPE(o) = VCVT_VOBJECT; +} + +#if 0 +int vObjectValueType(VObject *o) +{ + return VALUE_TYPE(o); +} +#endif + + +/*---------------------------------------------------------------------- + The following functions can be used to build VObject. + ----------------------------------------------------------------------*/ + +VObject* addVObjectProp(VObject *o, VObject *p) +{ + /* circular link list pointed to tail */ + /* + o {next,id,prop,val} + V + pn {next,id,prop,val} + V + ... + p1 {next,id,prop,val} + V + pn + --> + o {next,id,prop,val} + V + pn {next,id,prop,val} + V + p {next,id,prop,val} + ... + p1 {next,id,prop,val} + V + pn + */ + + VObject *tail = o->prop; + if (tail) { + p->next = tail->next; + o->prop = tail->next = p; + } + else { + o->prop = p->next = p; + } + return p; +} + +VObject* addProp(VObject *o, const char *id) +{ + return addVObjectProp(o,newVObject(id)); +} + +VObject* addProp_(VObject *o, const char *id) +{ + return addVObjectProp(o,newVObject_(id)); +} + +void addList(VObject **o, VObject *p) +{ + p->next = 0; + if (*o == 0) { + *o = p; + } + else { + VObject *t = *o; + while (t->next) { + t = t->next; + } + t->next = p; + } +} + +VObject* nextVObjectInList(VObject *o) +{ + return o->next; +} + +VObject* setValueWithSize_(VObject *prop, void *val, unsigned int size) +{ + VObject *sizeProp; + setVObjectAnyValue(prop, val); + sizeProp = addProp(prop,VCDataSizeProp); + setVObjectLongValue(sizeProp, size); + return prop; +} + +VObject* setValueWithSize(VObject *prop, void *val, unsigned int size) +{ + void *p = dupStr((const char *)val,size); + return setValueWithSize_(prop,p,p?size:0); +} + +void initPropIterator(VObjectIterator *i, VObject *o) +{ + i->start = o->prop; + i->next = 0; +} + +#if 0 +void initVObjectIterator(VObjectIterator *i, VObject *o) +{ + i->start = o->next; + i->next = 0; +} +#endif + +int moreIteration(VObjectIterator *i) +{ + return (i->start && (i->next==0 || i->next!=i->start)); +} + +VObject* nextVObject(VObjectIterator *i) +{ + if (i->start && i->next != i->start) { + if (i->next == 0) { + i->next = i->start->next; + return i->next; + } + else { + i->next = i->next->next; + return i->next; + } + } + else return (VObject*)0; +} + +VObject* isAPropertyOf(VObject *o, const char *id) +{ + VObjectIterator i; + initPropIterator(&i,o); + while (moreIteration(&i)) { + VObject *each = nextVObject(&i); + if (!PL_strcasecmp(id,each->id)) + return each; + } + return (VObject*)0; +} + +VObject* addGroup(VObject *o, const char *g) +{ + /* + a.b.c + --> + prop(c) + prop(VCGrouping=b) + prop(VCGrouping=a) + */ + char *dot = PL_strrchr(g,'.'); + if (dot) { + VObject *p, *t; + char *gs, *n = dot+1; + gs = dupStr(g,0); /* so we can write to it. */ + t = p = addProp_(o,lookupProp(n)); + dot = PL_strrchr(gs,'.'); + *dot = 0; + do { + dot = PL_strrchr(gs,'.'); + if (dot) { + n = dot+1; + *dot=0; + } + else + n = gs; + /* property(VCGroupingProp=n); + * and the value may have VCGrouping property + */ + t = addProp(t,VCGroupingProp); + setVObjectStringZValue(t,lookupProp_(n)); + } while (n != gs); + deleteString(gs); + return p; + } + else + return addProp_(o,lookupProp(g)); +} + +VObject* addPropValue(VObject *o, const char *p, const char *v) +{ + VObject *prop; + prop = addProp(o,p); + if (v) { + setVObjectUStringZValue_(prop, fakeUnicode(v,0)); + if (needsQuotedPrintable (v)) { + if (PL_strcasecmp (VCCardProp, vObjectName(o)) == 0) + addProp (prop, VCQuotedPrintableProp); + else + addProp (o, VCQuotedPrintableProp); + } + } + else + setVObjectUStringZValue_(prop, fakeUnicode("",0)); + + return prop; +} + +VObject* addPropSizedValue_(VObject *o, const char *p, const char *v, + unsigned int size) +{ + VObject *prop; + prop = addProp(o,p); + setValueWithSize_(prop, (void*)v, size); + return prop; +} + +VObject* addPropSizedValue(VObject *o, const char *p, const char *v, + unsigned int size) +{ + return addPropSizedValue_(o,p,dupStr(v,size),size); +} + +void cleanVObject(VObject *o) +{ + if (o == 0) return; + if (o->prop) { + /* destroy time: cannot use the iterator here. + Have to break the cycle in the circular link + list and turns it into regular NULL-terminated + list -- since at some point of destruction, + the reference entry for the iterator to work + will not longer be valid. + */ + VObject *p; + p = o->prop->next; + o->prop->next = 0; + do { + VObject *t = p->next; + cleanVObject(p); + p = t; + } while (p); + } + switch (VALUE_TYPE(o)) { + case VCVT_USTRINGZ: + case VCVT_STRINGZ: + case VCVT_RAW: + /* assume they are all allocated by malloc. */ + if ((char*) STRINGZ_VALUE_OF(o)) + PR_Free ((char*)STRINGZ_VALUE_OF(o)); + break; + case VCVT_VOBJECT: + cleanVObject(VOBJECT_VALUE_OF(o)); + break; + } + deleteVObject(o); +} + +void cleanVObjects(VObject *list) +{ + while (list) { + VObject *t = list; + list = nextVObjectInList(list); + cleanVObject(t); + } +} + +/*---------------------------------------------------------------------- + The following is a String Table Facilities. + ----------------------------------------------------------------------*/ + +#define STRTBLSIZE 255 + +static StrItem *strTbl[STRTBLSIZE]; + +static unsigned int hashStr(const char *s) +{ + unsigned int h = 0; + int i; + for (i=0;s[i];i++) { + h += s[i]*i; + } + return h % STRTBLSIZE; +} + +void unUseStr(const char *s) +{ + StrItem *t, *p; + unsigned int h = hashStr(s); + if ((t = strTbl[h]) != 0) { + p = t; + do { + if (PL_strcasecmp(t->s,s) == 0) { + t->refCnt--; + if (t->refCnt == 0) { + if (t == strTbl[h]) { + strTbl[h] = t->next; + } + else { + p->next = t->next; + } + deleteString((char *)t->s); + deleteStrItem(t); + return; + } + } + p = t; + t = t->next; + } while (t); + } +} + +struct PreDefProp { + const char *name; + const char *alias; + const char** fields; + unsigned int flags; + }; + +/* flags in PreDefProp */ +#define PD_BEGIN 0x1 +#define PD_INTERNAL 0x2 + +static const char *adrFields[] = { + VCPostalBoxProp, + VCExtAddressProp, + VCStreetAddressProp, + VCCityProp, + VCRegionProp, + VCPostalCodeProp, + VCCountryNameProp, + 0 +}; + +static const char *nameFields[] = { + VCFamilyNameProp, + VCGivenNameProp, + VCAdditionalNamesProp, + VCNamePrefixesProp, + VCNameSuffixesProp, + NULL + }; + +static const char *orgFields[] = { + VCOrgNameProp, + VCOrgUnitProp, + VCOrgUnit2Prop, + VCOrgUnit3Prop, + VCOrgUnit4Prop, + NULL + }; + +static const char *AAlarmFields[] = { + VCRunTimeProp, + VCSnoozeTimeProp, + VCRepeatCountProp, + VCAudioContentProp, + 0 + }; + +static const char *coolTalkFields[] = { + VCCooltalkAddress, + VCUseServer, + 0 + }; + +/* ExDate -- has unamed fields */ +/* RDate -- has unamed fields */ + +static const char *DAlarmFields[] = { + VCRunTimeProp, + VCSnoozeTimeProp, + VCRepeatCountProp, + VCDisplayStringProp, + 0 + }; + +static const char *MAlarmFields[] = { + VCRunTimeProp, + VCSnoozeTimeProp, + VCRepeatCountProp, + VCEmailAddressProp, + VCNoteProp, + 0 + }; + +static const char *PAlarmFields[] = { + VCRunTimeProp, + VCSnoozeTimeProp, + VCRepeatCountProp, + VCProcedureNameProp, + 0 + }; + +static struct PreDefProp propNames[] = { + { VC7bitProp, 0, 0, 0 }, + { VC8bitProp, 0, 0, 0 }, + { VCAAlarmProp, 0, AAlarmFields, 0 }, + { VCAdditionalNamesProp, 0, 0, 0 }, + { VCAdrProp, 0, adrFields, 0 }, + { VCAgentProp, 0, 0, 0 }, + { VCAIFFProp, 0, 0, 0 }, + { VCAOLProp, 0, 0, 0 }, + { VCAppleLinkProp, 0, 0, 0 }, + { VCAttachProp, 0, 0, 0 }, + { VCAttendeeProp, 0, 0, 0 }, + { VCATTMailProp, 0, 0, 0 }, + { VCAudioContentProp, 0, 0, 0 }, + { VCAVIProp, 0, 0, 0 }, + { VCBase64Prop, 0, 0, 0 }, + { VCBBSProp, 0, 0, 0 }, + { VCBirthDateProp, 0, 0, 0 }, + { VCBMPProp, 0, 0, 0 }, + { VCBodyProp, 0, 0, 0 }, + { VCBusinessRoleProp, 0, 0, 0 }, + { VCCalProp, 0, 0, PD_BEGIN }, + { VCCaptionProp, 0, 0, 0 }, + { VCCardProp, 0, 0, PD_BEGIN }, + { VCCarProp, 0, 0, 0 }, + { VCCategoriesProp, 0, 0, 0 }, + { VCCellularProp, 0, 0, 0 }, + { VCCGMProp, 0, 0, 0 }, + { VCCharSetProp, 0, 0, 0 }, + { VCCIDProp, VCContentIDProp, 0, 0 }, + { VCCISProp, 0, 0, 0 }, + { VCCityProp, 0, 0, 0 }, + { VCClassProp, 0, 0, 0 }, + { VCCommentProp, 0, 0, 0 }, + { VCCompletedProp, 0, 0, 0 }, + { VCContentIDProp, 0, 0, 0 }, + { VCCountryNameProp, 0, 0, 0 }, + { VCDAlarmProp, 0, DAlarmFields, 0 }, + { VCDataSizeProp, 0, 0, PD_INTERNAL }, + { VCDayLightProp, 0, 0 ,0 }, + { VCDCreatedProp, 0, 0, 0 }, + { VCDeliveryLabelProp, 0, 0, 0 }, + { VCDescriptionProp, 0, 0, 0 }, + { VCDIBProp, 0, 0, 0 }, + { VCDisplayStringProp, 0, 0, 0 }, + { VCDomesticProp, 0, 0, 0 }, + { VCDTendProp, 0, 0, 0 }, + { VCDTstartProp, 0, 0, 0 }, + { VCDueProp, 0, 0, 0 }, + { VCEmailAddressProp, 0, 0, 0 }, + { VCEncodingProp, 0, 0, 0 }, + { VCEndProp, 0, 0, 0 }, + { VCEventProp, 0, 0, PD_BEGIN }, + { VCEWorldProp, 0, 0, 0 }, + { VCExNumProp, 0, 0, 0 }, + { VCExpDateProp, 0, 0, 0 }, + { VCExpectProp, 0, 0, 0 }, + { VCExtAddressProp, 0, 0, 0 }, + { VCFamilyNameProp, 0, 0, 0 }, + { VCFaxProp, 0, 0, 0 }, + { VCFullNameProp, 0, 0, 0 }, + { VCGeoLocationProp, 0, 0, 0 }, + { VCGeoProp, 0, 0, 0 }, + { VCGIFProp, 0, 0, 0 }, + { VCGivenNameProp, 0, 0, 0 }, + { VCGroupingProp, 0, 0, 0 }, + { VCHomeProp, 0, 0, 0 }, + { VCIBMMailProp, 0, 0, 0 }, + { VCInlineProp, 0, 0, 0 }, + { VCInternationalProp, 0, 0, 0 }, + { VCInternetProp, 0, 0, 0 }, + { VCISDNProp, 0, 0, 0 }, + { VCJPEGProp, 0, 0, 0 }, + { VCLanguageProp, 0, 0, 0 }, + { VCLastModifiedProp, 0, 0, 0 }, + { VCLastRevisedProp, 0, 0, 0 }, + { VCLocationProp, 0, 0, 0 }, + { VCLogoProp, 0, 0, 0 }, + { VCMailerProp, 0, 0, 0 }, + { VCMAlarmProp, 0, MAlarmFields, 0 }, + { VCMCIMailProp, 0, 0, 0 }, + { VCMessageProp, 0, 0, 0 }, + { VCMETProp, 0, 0, 0 }, + { VCModemProp, 0, 0, 0 }, + { VCMPEG2Prop, 0, 0, 0 }, + { VCMPEGProp, 0, 0, 0 }, + { VCMSNProp, 0, 0, 0 }, + { VCNamePrefixesProp, 0, 0, 0 }, + { VCNameProp, 0, nameFields, 0 }, + { VCNameSuffixesProp, 0, 0, 0 }, + { VCNoteProp, 0, 0, 0 }, + { VCOrgNameProp, 0, 0, 0 }, + { VCOrgProp, 0, orgFields, 0 }, + { VCOrgUnit2Prop, 0, 0, 0 }, + { VCOrgUnit3Prop, 0, 0, 0 }, + { VCOrgUnit4Prop, 0, 0, 0 }, + { VCOrgUnitProp, 0, 0, 0 }, + { VCPagerProp, 0, 0, 0 }, + { VCPAlarmProp, 0, PAlarmFields, 0 }, + { VCParcelProp, 0, 0, 0 }, + { VCPartProp, 0, 0, 0 }, + { VCPCMProp, 0, 0, 0 }, + { VCPDFProp, 0, 0, 0 }, + { VCPGPProp, 0, 0, 0 }, + { VCPhotoProp, 0, 0, 0 }, + { VCPICTProp, 0, 0, 0 }, + { VCPMBProp, 0, 0, 0 }, + { VCPostalBoxProp, 0, 0, 0 }, + { VCPostalCodeProp, 0, 0, 0 }, + { VCPostalProp, 0, 0, 0 }, + { VCPowerShareProp, 0, 0, 0 }, + { VCPreferredProp, 0, 0, 0 }, + { VCPriorityProp, 0, 0, 0 }, + { VCProcedureNameProp, 0, 0, 0 }, + { VCProdIdProp, 0, 0, 0 }, + { VCProdigyProp, 0, 0, 0 }, + { VCPronunciationProp, 0, 0, 0 }, + { VCPSProp, 0, 0, 0 }, + { VCPublicKeyProp, 0, 0, 0 }, + { VCQPProp, VCQuotedPrintableProp, 0, 0 }, + { VCQuickTimeProp, 0, 0, 0 }, + { VCQuotedPrintableProp, 0, 0, 0 }, + { VCRDateProp, 0, 0, 0 }, + { VCRegionProp, 0, 0, 0 }, + { VCRelatedToProp, 0, 0, 0 }, + { VCRepeatCountProp, 0, 0, 0 }, + { VCResourcesProp, 0, 0, 0 }, + { VCRNumProp, 0, 0, 0 }, + { VCRoleProp, 0, 0, 0 }, + { VCRRuleProp, 0, 0, 0 }, + { VCRSVPProp, 0, 0, 0 }, + { VCRunTimeProp, 0, 0, 0 }, + { VCSequenceProp, 0, 0, 0 }, + { VCSnoozeTimeProp, 0, 0, 0 }, + { VCStartProp, 0, 0, 0 }, + { VCStatusProp, 0, 0, 0 }, + { VCStreetAddressProp, 0, 0, 0 }, + { VCSubTypeProp, 0, 0, 0 }, + { VCSummaryProp, 0, 0, 0 }, + { VCTelephoneProp, 0, 0, 0 }, + { VCTIFFProp, 0, 0, 0 }, + { VCTimeZoneProp, 0, 0, 0 }, + { VCTitleProp, 0, 0, 0 }, + { VCTLXProp, 0, 0, 0 }, + { VCTodoProp, 0, 0, PD_BEGIN }, + { VCTranspProp, 0, 0, 0 }, + { VCUniqueStringProp, 0, 0, 0 }, + { VCURLProp, 0, 0, 0 }, + { VCURLValueProp, 0, 0, 0 }, + { VCValueProp, 0, 0, 0 }, + { VCVersionProp, 0, 0, 0 }, + { VCVideoProp, 0, 0, 0 }, + { VCVoiceProp, 0, 0, 0 }, + { VCWAVEProp, 0, 0, 0 }, + { VCWMFProp, 0, 0, 0 }, + { VCWorkProp, 0, 0, 0 }, + { VCX400Prop, 0, 0, 0 }, + { VCX509Prop, 0, 0, 0 }, + { VCXRuleProp, 0, 0, 0 }, + { VCCooltalk, 0, coolTalkFields, 0 }, + { VCCooltalkAddress, 0, 0, 0 }, + { VCUseServer, 0, 0, 0 }, + { VCUseHTML, 0, 0, 0 }, + { 0,0,0,0 } + }; + + +static struct PreDefProp* lookupPropInfo(const char* str) +{ + /* brute force for now, could use a hash table here. */ + int i; + + for (i = 0; propNames[i].name; i++) + if (PL_strcasecmp(str, propNames[i].name) == 0) { + return &propNames[i]; + } + + return 0; +} + + +const char* lookupProp_(const char* str) +{ + int i; + + for (i = 0; propNames[i].name; i++) + if (PL_strcasecmp(str, propNames[i].name) == 0) { + const char* s; + s = propNames[i].alias?propNames[i].alias:propNames[i].name; + return lookupStr(s); + } + return lookupStr(str); +} + + +const char* lookupProp(const char* str) +{ + int i; + + for (i = 0; propNames[i].name; i++) + if (PL_strcasecmp(str, propNames[i].name) == 0) { + const char *s; + fieldedProp = (char **)propNames[i].fields; + s = propNames[i].alias?propNames[i].alias:propNames[i].name; + return lookupStr(s); + } + fieldedProp = 0; + return lookupStr(str); +} + + +/*---------------------------------------------------------------------- + APIs to Output text form. + ----------------------------------------------------------------------*/ +#define OFILE_REALLOC_SIZE 256 + +static void appendcOFile_(OFile *fp, char c) +{ + if (fp->fail) + return; +stuff: + if (fp->len+1 < fp->limit) { + fp->s[fp->len] = c; + fp->len++; + return; + } + else if (fp->alloc) { + fp->limit = fp->limit + OFILE_REALLOC_SIZE; + char* newBuf = (char *) PR_Realloc(fp->s, fp->limit); + if (newBuf) { + fp->s = newBuf; + goto stuff; + } + } + if (fp->alloc) + PR_FREEIF(fp->s); + fp->s = 0; + fp->fail = 1; +} + +static void appendcOFile(OFile *fp, char c) +{ +/* int i = 0; */ + if (c == '\n') { + /* write out as <CR><LF> */ + /* for (i = 0; i < LINEBREAK_LEN; i++) + appendcOFile_(fp,LINEBREAK [ i ]); */ + appendcOFile_(fp,0xd); + appendcOFile_(fp,0xa); + } + else + appendcOFile_(fp,c); +} + +static void appendsOFile(OFile *fp, const char *s) +{ + int i, slen; + slen = PL_strlen (s); + for (i=0; i<slen; i++) { + appendcOFile(fp,s[i]); + } +} + +static void initMemOFile(OFile *fp, char *s, int len) +{ + fp->s = s; + fp->len = 0; + fp->limit = s?len:0; + fp->alloc = s?0:1; + fp->fail = 0; +} + + +static int writeBase64(OFile *fp, unsigned char *s, long len) +{ + long cur = 0; + int i, numQuads = 0; + unsigned long trip; + unsigned char b; + char quad[5]; +#define PR_MAXQUADS 16 + + quad[4] = 0; + + while (cur < len) { + /* collect the triplet of bytes into 'trip' */ + trip = 0; + for (i = 0; i < 3; i++) { + b = (cur < len) ? *(s + cur) : 0; + cur++; + trip = trip << 8 | b; + } + /* fill in 'quad' with the appropriate four characters */ + for (i = 3; i >= 0; i--) { + b = (unsigned char)(trip & 0x3F); + trip = trip >> 6; + if ((3 - i) < (cur - len)) + quad[i] = '='; /* pad char */ + else if (b < 26) quad[i] = (char)b + 'A'; + else if (b < 52) quad[i] = (char)(b - 26) + 'a'; + else if (b < 62) quad[i] = (char)(b - 52) + '0'; + else if (b == 62) quad[i] = '+'; + else quad[i] = '/'; + } + /* now output 'quad' with appropriate whitespace and line ending */ + appendsOFile(fp, (numQuads == 0 ? " " : "")); + appendsOFile(fp, quad); + appendsOFile(fp, ((cur >= len)?"\n" :(numQuads==PR_MAXQUADS-1?"\n" : ""))); + numQuads = (numQuads + 1) % PR_MAXQUADS; + } + appendcOFile(fp,'\n'); + + return 1; +} + +static void writeQPString(OFile *fp, const char *s) +{ + const unsigned char *p = (const unsigned char *)s; + int current_column = 0; + static const char hexdigits[] = "0123456789ABCDEF"; + bool white = false; + bool contWhite = false; + bool mb_p = false; + + if (needsQuotedPrintable (s)) + { + while (*p) { + if (*p == '\r' || *p == '\n') + { + /* Whitespace cannot be allowed to occur at the end of the line. + So we encode " \n" as " =\n\n", that is, the whitespace, a + soft line break, and then a hard line break. + */ + + if (white) + { + appendcOFile(fp,'='); + appendcOFile(fp,'\n'); + appendcOFile(fp,'\t'); + appendsOFile(fp,"=0D"); + appendsOFile(fp,"=0A"); + appendcOFile(fp,'='); + appendcOFile(fp,'\n'); + appendcOFile(fp,'\t'); + } + else + { + appendsOFile(fp,"=0D"); + appendsOFile(fp,"=0A"); + appendcOFile(fp,'='); + appendcOFile(fp,'\n'); + appendcOFile(fp,'\t'); + contWhite = false; + } + + /* If its CRLF, swallow two chars instead of one. */ + if (*p == '\r' && *(p+1) == '\n') + p++; + white = false; + current_column = 0; + } + else + { + if ((*p >= 33 && *p <= 60) || /* safe printing chars */ + (*p >= 62 && *p <= 126) || + (mb_p && (*p == 61 || *p == 127 || *p == 0x1B))) + { + appendcOFile(fp,*p); + current_column++; + white = false; + contWhite = false; + } + else if (*p == ' ' || *p == '\t') /* whitespace */ + { + if (contWhite) + { + appendcOFile(fp,'='); + appendcOFile(fp,hexdigits[*p >> 4]); + appendcOFile(fp,hexdigits[*p & 0xF]); + current_column += 3; + contWhite = false; + } + else + { + appendcOFile(fp,*p); + current_column++; + } + white = true; + } + else /* print as =FF */ + { + appendcOFile(fp,'='); + appendcOFile(fp,hexdigits[*p >> 4]); + appendcOFile(fp,hexdigits[*p & 0xF]); + current_column += 3; + white = false; + contWhite = false; + } + + NS_ASSERTION(current_column <= 76, "1.10 <rhp@netscape.com> 06 Jan 2000 08:01"); /* Hard limit required by spec */ + + if (current_column >= 73 || ((*(p+1) == ' ') && (current_column + 3 >= 73))) /* soft line break: "=\r\n" */ + { + appendcOFile(fp,'='); + appendcOFile(fp,'\n'); + appendcOFile(fp,'\t'); + current_column = 0; + if (white) + contWhite = true; + else + contWhite = false; + white = false; + } + } + p++; + } /* while */ + } /* if */ + else + { + while (*p) { + appendcOFile(fp,*p); + p++; + } + } +} + + +static void writeValue(OFile *fp, VObject *o, unsigned long size) +{ + if (o == 0) return; + switch (VALUE_TYPE(o)) { + case VCVT_USTRINGZ: { + char *s = fakeCString(USTRINGZ_VALUE_OF(o)); + writeQPString(fp, s); + deleteString(s); + break; + } + case VCVT_STRINGZ: { + writeQPString(fp, STRINGZ_VALUE_OF(o)); + break; + } + case VCVT_UINT: { + char buf[11]; + sprintf(buf,"%u", INTEGER_VALUE_OF(o)); + appendsOFile(fp, buf); + break; + } + case VCVT_ULONG: { + char buf[21]; + sprintf(buf,"%lu", LONG_VALUE_OF(o)); + appendsOFile(fp, buf); + break; + } + case VCVT_RAW: { + appendcOFile(fp,'\n'); + writeBase64(fp,(unsigned char*)(ANY_VALUE_OF(o)),size); + break; + } + case VCVT_VOBJECT: + appendcOFile(fp,'\n'); + writeVObject_(fp,VOBJECT_VALUE_OF(o)); + break; + } +} + +static void writeAttrValue(OFile *fp, VObject *o, int* length) +{ + int ilen = 0; + if (NAME_OF(o)) { + struct PreDefProp *pi; + pi = lookupPropInfo(NAME_OF(o)); + if (pi && ((pi->flags & PD_INTERNAL) != 0)) return; + appendcOFile(fp,';'); + if (*length != -1) + (*length)++; + appendsOFile(fp,NAME_OF(o)); + if (*length != -1) + (*length) += PL_strlen (NAME_OF(o)); + } + else { + appendcOFile(fp,';'); + (*length)++; + } + if (VALUE_TYPE(o)) { + appendcOFile(fp,'='); + if (*length != -1) { + (*length)++; + for (ilen = 0; ilen < MAXMOZPROPNAMESIZE - (*length); ilen++) + appendcOFile(fp,' '); + } + writeValue(fp,o,0); + } +} + +static void writeGroup(OFile *fp, VObject *o) +{ + nsAutoCString buf(NAME_OF(o)); + + while ((o=isAPropertyOf(o,VCGroupingProp)) != 0) { + buf.Insert(NS_LITERAL_CSTRING("."), 0); + buf.Insert(STRINGZ_VALUE_OF(o), 0); + } + appendsOFile(fp, buf.get()); +} + +static int inList(const char **list, const char *s) +{ + if (list == 0) return 0; + while (*list) { + if (PL_strcasecmp(*list,s) == 0) return 1; + list++; + } + return 0; +} + +static void writeProp(OFile *fp, VObject *o) +{ + int length = -1; + //int ilen = 0; + + if (NAME_OF(o)) { + struct PreDefProp *pi; + VObjectIterator t; + const char **fields_ = 0; + pi = lookupPropInfo(NAME_OF(o)); + if (pi && ((pi->flags & PD_BEGIN) != 0)) { + writeVObject_(fp,o); + return; + } + if (isAPropertyOf(o,VCGroupingProp)) + writeGroup(fp,o); + else + appendsOFile(fp,NAME_OF(o)); + if (pi) fields_ = pi->fields; + initPropIterator(&t,o); + while (moreIteration(&t)) { + const char *s; + VObject *eachProp = nextVObject(&t); + s = NAME_OF(eachProp); + if (PL_strcasecmp(VCGroupingProp,s) && !inList(fields_,s)) + writeAttrValue(fp,eachProp, &length); + } + if (fields_) { + int i = 0, n = 0; + const char** fields = fields_; + /* output prop as fields */ + appendcOFile(fp,':'); + while (*fields) { + VObject *tt = isAPropertyOf(o,*fields); + i++; + if (tt) n = i; + fields++; + } + fields = fields_; + for (i=0;i<n;i++) { + writeValue(fp,isAPropertyOf(o,*fields),0); + fields++; + if (i<(n-1)) appendcOFile(fp,';'); + } + } + } + + if (VALUE_TYPE(o)) { + unsigned long size = 0; + VObject *p = isAPropertyOf(o,VCDataSizeProp); + if (p) size = LONG_VALUE_OF(p); + appendcOFile(fp,':'); + writeValue(fp,o,size); + } + appendcOFile(fp,'\n'); +} + +void writeVObject_(OFile *fp, VObject *o) +{ + //int ilen = 0; + if (NAME_OF(o)) { + struct PreDefProp *pi; + pi = lookupPropInfo(NAME_OF(o)); + + if (pi && ((pi->flags & PD_BEGIN) != 0)) { + VObjectIterator t; + const char *begin = NAME_OF(o); + appendsOFile(fp,"begin:"); + appendsOFile(fp,begin); + appendcOFile(fp,'\n'); + initPropIterator(&t,o); + while (moreIteration(&t)) { + VObject *eachProp = nextVObject(&t); + writeProp(fp, eachProp); + } + appendsOFile(fp,"end:"); + appendsOFile(fp,begin); + appendsOFile(fp,"\n\n"); + } + } +} + +char* writeMemVObject(char *s, int *len, VObject *o) +{ + OFile ofp; + initMemOFile(&ofp,s,len?*len:0); + writeVObject_(&ofp,o); + if (len) *len = ofp.len; + appendcOFile(&ofp,0); + return ofp.s; +} + +extern "C" +char * writeMemoryVObjects(char *s, int *len, VObject *list, bool expandSpaces) +{ + OFile ofp; + initMemOFile(&ofp,s,len?*len:0); + while (list) { + writeVObject_(&ofp,list); + list = nextVObjectInList(list); + } + if (len) *len = ofp.len; + appendcOFile(&ofp,0); + return ofp.s; +} + +/*---------------------------------------------------------------------- + APIs to do fake Unicode stuff. + ----------------------------------------------------------------------*/ +vwchar_t* fakeUnicode(const char *ps, int *bytes) +{ + vwchar_t *r, *pw; + int len = strlen(ps)+1; + + pw = r = (vwchar_t*)PR_CALLOC(sizeof(vwchar_t)*len); + if (bytes) + *bytes = len * sizeof(vwchar_t); + + while (*ps) { + if (*ps == '\n') + *pw = (vwchar_t)0x2028; + else if (*ps == '\r') + *pw = (vwchar_t)0x2029; + else + *pw = (vwchar_t)(unsigned char)*ps; + ps++; pw++; + } + *pw = (vwchar_t)0; + + return r; +} + +int uStrLen(const vwchar_t *u) +{ + if (!u) + return 0; + int i = 0; + while (*u != (vwchar_t)0) { u++; i++; } + return i; +} + +char* fakeCString(const vwchar_t *u) +{ + char *s, *t; + int len = uStrLen(u) + 1; + t = s = (char*)PR_CALLOC(len); + if (u) { + while (*u) { + if (*u == (vwchar_t)0x2028) + *t = '\n'; + else if (*u == (vwchar_t)0x2029) + *t = '\r'; + else + *t = (char)*u; + u++; t++; + } + } + *t = 0; + return s; +} + +const char* lookupStr(const char *s) +{ + StrItem *t; + unsigned int h = hashStr(s); + if ((t = strTbl[h]) != 0) { + do { + if (PL_strcasecmp(t->s,s) == 0) { + t->refCnt++; + return t->s; + } + t = t->next; + } while (t); + } + s = dupStr(s,0); + strTbl[h] = newStrItem(s,strTbl[h]); + return s; +} diff --git a/mailnews/addrbook/src/nsVCardObj.h b/mailnews/addrbook/src/nsVCardObj.h new file mode 100644 index 000000000..b7ca36997 --- /dev/null +++ b/mailnews/addrbook/src/nsVCardObj.h @@ -0,0 +1,396 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ +/*************************************************************************** +(C) Copyright 1996 Apple Computer, Inc., AT&T Corp., International +Business Machines Corporation and Siemens Rolm Communications Inc. + +For purposes of this license notice, the term Licensors shall mean, +collectively, Apple Computer, Inc., AT&T Corp., International +Business Machines Corporation and Siemens Rolm Communications Inc. +The term Licensor shall mean any of the Licensors. + +Subject to acceptance of the following conditions, permission is hereby +granted by Licensors without the need for written agreement and without +license or royalty fees, to use, copy, modify and distribute this +software for any purpose. + +The above copyright notice and the following four paragraphs must be +reproduced in all copies of this software and any software including +this software. + +THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS AND NO LICENSOR SHALL HAVE +ANY OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS OR +MODIFICATIONS. + +IN NO EVENT SHALL ANY LICENSOR BE LIABLE TO ANY PARTY FOR DIRECT, +INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES OR LOST PROFITS ARISING OUT +OF THE USE OF THIS SOFTWARE EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +EACH LICENSOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO ANY WARRANTY OF NONINFRINGEMENT OR THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. + +The software is provided with RESTRICTED RIGHTS. Use, duplication, or +disclosure by the government are subject to restrictions set forth in +DFARS 252.227-7013 or 48 CFR 52.227-19, as applicable. + +***************************************************************************/ + +/* + +The vCard/vCalendar C interface is implemented in the set +of files as follows: + +vcc.y, yacc source, and vcc.c, the yacc output you will use +implements the core parser + +vobject.c implements an API that insulates the caller from +the parser and changes in the vCard/vCalendar BNF + +port.h defines compilation environment dependent stuff + +vcc.h and vobject.h are header files for their .c counterparts + +vcaltmp.h and vcaltmp.c implement vCalendar "macro" functions +which you may find useful. + +test.c is a standalone test driver that exercises some of +the features of the APIs provided. Invoke test.exe on a +VCARD/VCALENDAR input text file and you will see the pretty +print output of the internal representation (this pretty print +output should give you a good idea of how the internal +representation looks like -- there is one such output in the +following too). Also, a file with the .out suffix is generated +to show that the internal representation can be written back +in the original text format. + +For more information on this API see the readme.txt file +which accompanied this distribution. + + Also visit: + + http://www.versit.com + http://www.ralden.com + +*/ + + +#ifndef __VOBJECT_H__ +#define __VOBJECT_H__ 1 + +/* +Unfortunately, on the Mac (and possibly other platforms) with our current, out-dated +libraries (Plauger), |wchar_t| is defined incorrectly, which breaks vcards. + +We can't fix Plauger because it doesn't come with source. Later, when we +upgrade to MSL, we can make this evil hack go away. In the mean time, +vcards are not allowed to use the (incorrectly defined) |wchar_t| type. Instead, +they will use an appropriately defined local type |vwchar_t|. +*/ + +typedef wchar_t vwchar_t; + +#ifdef __cplusplus +extern "C" { +#endif + +#define VC7bitProp "7bit" +#define VC8bitProp "8bit" +#define VCAAlarmProp "aalarm" +#define VCAdditionalNamesProp "addn" +#define VCAdrProp "adr" +#define VCAgentProp "agent" +#define VCAIFFProp "aiff" +#define VCAOLProp "aol" +#define VCAppleLinkProp "applelink" +#define VCAttachProp "attach" +#define VCAttendeeProp "attendee" +#define VCATTMailProp "attmail" +#define VCAudioContentProp "audiocontent" +#define VCAVIProp "avi" +#define VCBase64Prop "base64" +#define VCBBSProp "bbs" +#define VCBirthDateProp "bday" +#define VCBMPProp "bmp" +#define VCBodyProp "body" +#define VCBusinessRoleProp "role" +#define VCCalProp "vcalendar" +#define VCCaptionProp "cap" +#define VCCardProp "vcard" +#define VCCarProp "car" +#define VCCategoriesProp "categories" +#define VCCellularProp "cell" +#define VCCGMProp "cgm" +#define VCCharSetProp "cs" +#define VCCIDProp "cid" +#define VCCISProp "cis" +#define VCCityProp "l" +#define VCClassProp "class" +#define VCCommentProp "note" +#define VCCompletedProp "completed" +#define VCContentIDProp "content-id" +#define VCCountryNameProp "c" +#define VCDAlarmProp "dalarm" +#define VCDataSizeProp "datasize" +#define VCDayLightProp "daylight" +#define VCDCreatedProp "dcreated" +#define VCDeliveryLabelProp "label" +#define VCDescriptionProp "description" +#define VCDIBProp "dib" +#define VCDisplayStringProp "displaystring" +#define VCDomesticProp "dom" +#define VCDTendProp "dtend" +#define VCDTstartProp "dtstart" +#define VCDueProp "due" +#define VCEmailAddressProp "email" +#define VCEncodingProp "encoding" +#define VCEndProp "end" +#define VCEventProp "vevent" +#define VCEWorldProp "eworld" +#define VCExNumProp "exnum" +#define VCExpDateProp "exdate" +#define VCExpectProp "expect" +#define VCExtAddressProp "ext add" +#define VCFamilyNameProp "f" +#define VCFaxProp "fax" +#define VCFullNameProp "fn" +#define VCGeoProp "geo" +#define VCGeoLocationProp "geo" +#define VCGIFProp "gif" +#define VCGivenNameProp "g" +#define VCGroupingProp "grouping" +#define VCHomeProp "home" +#define VCIBMMailProp "ibmmail" +#define VCInlineProp "inline" +#define VCInternationalProp "intl" +#define VCInternetProp "internet" +#define VCISDNProp "isdn" +#define VCJPEGProp "jpeg" +#define VCLanguageProp "lang" +#define VCLastModifiedProp "last-modified" +#define VCLastRevisedProp "rev" +#define VCLocationProp "location" +#define VCLogoProp "logo" +#define VCMailerProp "mailer" +#define VCMAlarmProp "malarm" +#define VCMCIMailProp "mcimail" +#define VCMessageProp "msg" +#define VCMETProp "met" +#define VCModemProp "modem" +#define VCMPEG2Prop "mpeg2" +#define VCMPEGProp "mpeg" +#define VCMSNProp "msn" +#define VCNamePrefixesProp "npre" +#define VCNameProp "n" +#define VCNameSuffixesProp "nsuf" +#define VCNoteProp "note" +#define VCOrgNameProp "orgname" +#define VCOrgProp "org" +#define VCOrgUnit2Prop "oun2" +#define VCOrgUnit3Prop "oun3" +#define VCOrgUnit4Prop "oun4" +#define VCOrgUnitProp "oun" +#define VCPagerProp "pager" +#define VCPAlarmProp "palarm" +#define VCParcelProp "parcel" +#define VCPartProp "part" +#define VCPCMProp "pcm" +#define VCPDFProp "pdf" +#define VCPGPProp "pgp" +#define VCPhotoProp "photo" +#define VCPICTProp "pict" +#define VCPMBProp "pmb" +#define VCPostalBoxProp "box" +#define VCPostalCodeProp "pc" +#define VCPostalProp "postal" +#define VCPowerShareProp "powershare" +#define VCPreferredProp "pref" +#define VCPriorityProp "priority" +#define VCProcedureNameProp "procedurename" +#define VCProdIdProp "prodid" +#define VCProdigyProp "prodigy" +#define VCPronunciationProp "sound" +#define VCPSProp "ps" +#define VCPublicKeyProp "key" +#define VCQPProp "qp" +#define VCQuickTimeProp "qtime" +#define VCQuotedPrintableProp "quoted-printable" +#define VCRDateProp "rdate" +#define VCRegionProp "r" +#define VCRelatedToProp "related-to" +#define VCRepeatCountProp "repeatcount" +#define VCResourcesProp "resources" +#define VCRNumProp "rnum" +#define VCRoleProp "role" +#define VCRRuleProp "rrule" +#define VCRSVPProp "rsvp" +#define VCRunTimeProp "runtime" +#define VCSequenceProp "sequence" +#define VCSnoozeTimeProp "snoozetime" +#define VCStartProp "start" +#define VCStatusProp "status" +#define VCStreetAddressProp "street" +#define VCSubTypeProp "subtype" +#define VCSummaryProp "summary" +#define VCTelephoneProp "tel" +#define VCTIFFProp "tiff" +#define VCTimeZoneProp "tz" +#define VCTitleProp "title" +#define VCTLXProp "tlx" +#define VCTodoProp "vtodo" +#define VCTranspProp "transp" +#define VCUniqueStringProp "uid" +#define VCURLProp "url" +#define VCURLValueProp "urlval" +#define VCValueProp "value" +#define VCVersionProp "version" +#define VCVideoProp "video" +#define VCVoiceProp "voice" +#define VCWAVEProp "wave" +#define VCWMFProp "wmf" +#define VCWorkProp "work" +#define VCX400Prop "x400" +#define VCX509Prop "x509" +#define VCXRuleProp "xrule" +#define VCCooltalk "x-mozilla-cpt" +#define VCCooltalkAddress "x-moxilla-cpadr" +#define VCUseServer "x-mozilla-cpsrv" +#define VCUseHTML "x-mozilla-html" + +/* return type of vObjectValueType: */ +#define VCVT_NOVALUE 0 + /* if the VObject has no value associated with it. */ +#define VCVT_STRINGZ 1 + /* if the VObject has value set by setVObjectStringZValue. */ +#define VCVT_USTRINGZ 2 + /* if the VObject has value set by setVObjectUStringZValue. */ +#define VCVT_UINT 3 + /* if the VObject has value set by setVObjectIntegerValue. */ +#define VCVT_ULONG 4 + /* if the VObject has value set by setVObjectLongValue. */ +#define VCVT_RAW 5 + /* if the VObject has value set by setVObjectAnyValue. */ +#define VCVT_VOBJECT 6 + /* if the VObject has value set by setVObjectVObjectValue. */ + +#define NAME_OF(o) o->id +#define VALUE_TYPE(o) o->valType +#define STRINGZ_VALUE_OF(o) o->val.strs +#define USTRINGZ_VALUE_OF(o) o->val.ustrs +#define INTEGER_VALUE_OF(o) o->val.i +#define LONG_VALUE_OF(o) o->val.l +#define ANY_VALUE_OF(o) o->val.any +#define VOBJECT_VALUE_OF(o) o->val.vobj + +typedef struct VObject VObject; + +typedef union ValueItem { + const char *strs; + const vwchar_t *ustrs; + unsigned int i; + unsigned long l; + void *any; + VObject *vobj; + } ValueItem; + +struct VObject { + VObject *next; + const char *id; + VObject *prop; + unsigned short valType; + ValueItem val; + }; + +typedef struct StrItem StrItem; + +struct StrItem { + StrItem *next; + const char *s; + unsigned int refCnt; + }; + +typedef struct OFile { + char *s; + int len; + int limit; + int alloc:1; + int fail:1; + } OFile; + +typedef struct VObjectIterator { + VObject* start; + VObject* next; + } VObjectIterator; + +VObject* newVObject(const char *id); +void deleteVObject(VObject *p); +char* dupStr(const char *s, unsigned int size); +extern "C" void deleteString(char *p); +void unUseStr(const char *s); + +void setVObjectName(VObject *o, const char* id); +void setVObjectStringZValue(VObject *o, const char *s); +void setVObjectStringZValue_(VObject *o, const char *s); +void setVObjectUStringZValue(VObject *o, const vwchar_t *s); +void setVObjectUStringZValue_(VObject *o, const vwchar_t *s); +void setVObjectIntegerValue(VObject *o, unsigned int i); +void setVObjectLongValue(VObject *o, unsigned long l); +void setVObjectAnyValue(VObject *o, void *t); +VObject* setValueWithSize(VObject *prop, void *val, unsigned int size); +VObject* setValueWithSize_(VObject *prop, void *val, unsigned int size); + +const char* vObjectName(VObject *o); +const char* vObjectStringZValue(VObject *o); +const vwchar_t* vObjectUStringZValue(VObject *o); +unsigned int vObjectIntegerValue(VObject *o); +unsigned long vObjectLongValue(VObject *o); +void* vObjectAnyValue(VObject *o); +VObject* vObjectVObjectValue(VObject *o); +void setVObjectVObjectValue(VObject *o, VObject *p); + +VObject* addVObjectProp(VObject *o, VObject *p); +VObject* addProp(VObject *o, const char *id); +VObject* addProp_(VObject *o, const char *id); +VObject* addPropValue(VObject *o, const char *p, const char *v); +VObject* addPropSizedValue_(VObject *o, const char *p, const char *v, unsigned int size); +VObject* addPropSizedValue(VObject *o, const char *p, const char *v, unsigned int size); +VObject* addGroup(VObject *o, const char *g); +void addList(VObject **o, VObject *p); + +VObject* isAPropertyOf(VObject *o, const char *id); + +VObject* nextVObjectInList(VObject *o); +void initPropIterator(VObjectIterator *i, VObject *o); +int moreIteration(VObjectIterator *i); +VObject* nextVObject(VObjectIterator *i); + +void writeVObject_(OFile *fp, VObject *o); +char* writeMemVObject(char *s, int *len, VObject *o); +extern "C" char* writeMemoryVObjects(char *s, int *len, VObject *list, bool expandSpaces); + +const char* lookupStr(const char *s); + +void cleanVObject(VObject *o); +void cleanVObjects(VObject *list); + +const char* lookupProp(const char* str); +const char* lookupProp_(const char* str); + +vwchar_t* fakeUnicode(const char *ps, int *bytes); +int uStrLen(const vwchar_t *u); +char* fakeCString(const vwchar_t *u); + +#define MAXPROPNAMESIZE 256 +#define MAXMOZPROPNAMESIZE 16 + +#ifdef __cplusplus +} +#endif + +#endif /* __VOBJECT_H__ */ + + diff --git a/mailnews/addrbook/src/nsWabAddressBook.cpp b/mailnews/addrbook/src/nsWabAddressBook.cpp new file mode 100644 index 000000000..9c991ddf5 --- /dev/null +++ b/mailnews/addrbook/src/nsWabAddressBook.cpp @@ -0,0 +1,128 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ + +#include <tchar.h> +#include "nsWabAddressBook.h" +#include "mozilla/Logging.h" +#include <algorithm> + +#ifdef PR_LOGGING +static PRLogModuleInfo* gWabAddressBookLog + = PR_NewLogModule("nsWabAddressBookLog"); +#endif + +#define PRINTF(args) MOZ_LOG(gWabAddressBookLog, mozilla::LogLevel::Debug, args) + +using namespace mozilla; + +HMODULE nsWabAddressBook::mLibrary = NULL ; +int32_t nsWabAddressBook::mLibUsage = 0 ; +LPWABOPEN nsWabAddressBook::mWABOpen = NULL ; +LPWABOBJECT nsWabAddressBook::mRootSession = NULL ; +LPADRBOOK nsWabAddressBook::mRootBook = NULL ; + +BOOL nsWabAddressBook::LoadWabLibrary(void) +{ + if (mLibrary) { ++ mLibUsage ; return TRUE ; } + // We try to fetch the location of the WAB DLL from the registry + TCHAR wabDLLPath [MAX_PATH] ; + DWORD keyType = 0 ; + ULONG byteCount = sizeof(wabDLLPath) ; + HKEY keyHandle = NULL ; + wabDLLPath [MAX_PATH - 1] = 0 ; + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, WAB_DLL_PATH_KEY, 0, KEY_READ, &keyHandle) == ERROR_SUCCESS) { + RegQueryValueEx(keyHandle, "", NULL, &keyType, (LPBYTE) wabDLLPath, &byteCount) ; + if (keyType == REG_EXPAND_SZ) { + // Expand the environment variables + DWORD bufferSize = ExpandEnvironmentStrings(wabDLLPath, NULL, 0); + if (bufferSize && bufferSize < MAX_PATH) { + TCHAR tmp[MAX_PATH]; + ExpandEnvironmentStrings(wabDLLPath, tmp, bufferSize); + _tcscpy(wabDLLPath, tmp); + } + else { + return FALSE; + } + } + } + else { + if (GetSystemDirectory(wabDLLPath, MAX_PATH)) { + _tcsncat(wabDLLPath, WAB_DLL_NAME, + std::min(_tcslen(WAB_DLL_NAME), MAX_PATH - _tcslen(wabDLLPath) - 1)); + } + else { + return FALSE; + } + } + if (keyHandle) { RegCloseKey(keyHandle) ; } + mLibrary = LoadLibrary( (lstrlen(wabDLLPath)) ? wabDLLPath : WAB_DLL_NAME ); + if (!mLibrary) { return FALSE ; } + ++ mLibUsage ; + mWABOpen = reinterpret_cast<LPWABOPEN>(GetProcAddress(mLibrary, "WABOpen")) ; + if (!mWABOpen) { return FALSE ; } + HRESULT retCode = mWABOpen(&mRootBook, &mRootSession, NULL, 0) ; + + if (HR_FAILED(retCode)) { + PRINTF(("Cannot initialize WAB %08x.\n", retCode)) ; return FALSE ; + } + return TRUE ; +} + +void nsWabAddressBook::FreeWabLibrary(void) +{ + if (mLibrary) { + if (-- mLibUsage == 0) { + if (mRootBook) { mRootBook->Release() ; } + if (mRootSession) { mRootSession->Release() ; } + FreeLibrary(mLibrary) ; + mLibrary = NULL ; + } + } +} + +nsWabAddressBook::nsWabAddressBook(void) +: nsAbWinHelper() +{ + BOOL result = Initialize() ; + + NS_ASSERTION(result == TRUE, "Couldn't initialize Wab Helper") ; + MOZ_COUNT_CTOR(nsWabAddressBook) ; +} + +nsWabAddressBook::~nsWabAddressBook(void) +{ + MutexAutoLock guard(*mMutex) ; + FreeWabLibrary() ; + MOZ_COUNT_DTOR(nsWabAddressBook) ; +} + +BOOL nsWabAddressBook::Initialize(void) +{ + if (mAddressBook) { return TRUE ; } + MutexAutoLock guard(*mMutex) ; + + if (!LoadWabLibrary()) { + PRINTF(("Cannot load library.\n")) ; + return FALSE ; + } + mAddressBook = mRootBook ; + return TRUE ; +} + +void nsWabAddressBook::AllocateBuffer(ULONG aByteCount, LPVOID *aBuffer) +{ + mRootSession->AllocateBuffer(aByteCount, aBuffer) ; +} + +void nsWabAddressBook::FreeBuffer(LPVOID aBuffer) +{ + mRootSession->FreeBuffer(aBuffer) ; +} + + + + + + diff --git a/mailnews/addrbook/src/nsWabAddressBook.h b/mailnews/addrbook/src/nsWabAddressBook.h new file mode 100644 index 000000000..35b76b213 --- /dev/null +++ b/mailnews/addrbook/src/nsWabAddressBook.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. */ +#ifndef nsWabAddressBook_h___ +#define nsWabAddressBook_h___ + +#include "mozilla/Attributes.h" +#include "nsAbWinHelper.h" +#include <wab.h> + +class nsWabAddressBook : public nsAbWinHelper +{ +public : + nsWabAddressBook(void) ; + virtual ~nsWabAddressBook(void) ; + +protected : + // Session and address book that will be shared by all instances + // (see nsMapiAddressBook.h for details) + static LPWABOBJECT mRootSession ; + static LPADRBOOK mRootBook ; + // Class members to handle library loading/entry points + static int32_t mLibUsage ; + static HMODULE mLibrary ; + static LPWABOPEN mWABOpen ; + + // Load the WAB environment + BOOL Initialize(void) ; + // Allocation of a buffer for transmission to interfaces + virtual void AllocateBuffer(ULONG aByteCount, LPVOID *aBuffer) override; + // Destruction of a buffer provided by the interfaces + virtual void FreeBuffer(LPVOID aBuffer) override; + // Manage the library + static BOOL LoadWabLibrary(void) ; + static void FreeWabLibrary(void) ; + +private : +} ; + +// Additional definitions for WAB stuff. These properties are +// only defined with regards to the default character sizes, +// and not in two _A and _W versions... +#define PR_BUSINESS_ADDRESS_CITY_A PR_LOCALITY_A +#define PR_BUSINESS_ADDRESS_COUNTRY_A PR_COUNTRY_A +#define PR_BUSINESS_ADDRESS_POSTAL_CODE_A PR_POSTAL_CODE_A +#define PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_A PR_STATE_OR_PROVINCE_A +#define PR_BUSINESS_ADDRESS_STREET_A PR_STREET_ADDRESS_A + +#define PR_BUSINESS_ADDRESS_CITY_W PR_LOCALITY_W +#define PR_BUSINESS_ADDRESS_COUNTRY_W PR_COUNTRY_W +#define PR_BUSINESS_ADDRESS_POSTAL_CODE_W PR_POSTAL_CODE_W +#define PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE_W PR_STATE_OR_PROVINCE_W +#define PR_BUSINESS_ADDRESS_STREET_W PR_STREET_ADDRESS_W + +#endif // nsWABAddressBook_h___ + |