diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 17:32:43 +0000 |
commit | 6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/addrbook/src/nsAbOutlookDirectory.cpp | |
parent | Initial commit. (diff) | |
download | thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mailnews/addrbook/src/nsAbOutlookDirectory.cpp')
-rw-r--r-- | comm/mailnews/addrbook/src/nsAbOutlookDirectory.cpp | 1418 |
1 files changed, 1418 insertions, 0 deletions
diff --git a/comm/mailnews/addrbook/src/nsAbOutlookDirectory.cpp b/comm/mailnews/addrbook/src/nsAbOutlookDirectory.cpp new file mode 100644 index 0000000000..77568a62f2 --- /dev/null +++ b/comm/mailnews/addrbook/src/nsAbOutlookDirectory.cpp @@ -0,0 +1,1418 @@ +/* -*- 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 "nsString.h" +#include "nsAbDirectoryQuery.h" +#include "nsIAbBooleanExpression.h" +#include "nsIAbManager.h" +#include "nsAbQueryStringToExpression.h" +#include "nsEnumeratorUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "mozilla/Logging.h" +#include "nsIPrefService.h" +#include "nsIPrefBranch.h" +#include "nsCRTGlue.h" +#include "nsArrayUtils.h" +#include "nsMsgUtils.h" +#include "nsQueryObject.h" +#include "mozilla/Services.h" +#include "nsIObserverService.h" +#include "mozilla/JSONStringWriteFuncs.h" + +#define PRINT_TO_CONSOLE 0 +#if PRINT_TO_CONSOLE +# define PRINTF(args) printf args +#else +static mozilla::LazyLogModule gAbOutlookDirectoryLog("AbOutlookDirectory"); +# define PRINTF(args) \ + MOZ_LOG(gAbOutlookDirectoryLog, mozilla::LogLevel::Debug, args) +#endif + +nsAbOutlookDirectory::nsAbOutlookDirectory(void) + : nsAbDirProperty(), + mDirEntry(nullptr), + mCurrentQueryId(0), + mSearchContext(-1) { + mDirEntry = new nsMapiEntry; +} + +nsAbOutlookDirectory::~nsAbOutlookDirectory(void) { + if (mDirEntry) { + delete mDirEntry; + } +} + +NS_IMPL_ISUPPORTS_INHERITED(nsAbOutlookDirectory, nsAbDirProperty, + nsIAbDirectoryQuery, nsIAbDirSearchListener) + +NS_IMETHODIMP nsAbOutlookDirectory::Init(const char* aUri) { + nsresult rv = nsAbDirProperty::Init(aUri); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString entry; + makeEntryIdFromURI(kOutlookDirectoryScheme, mURI.get(), entry); + nsAbWinHelperGuard mapiAddBook; + nsAutoString unichars; + ULONG objectType = 0; + + if (!mapiAddBook->IsOK()) return NS_ERROR_FAILURE; + + mDirEntry->Assign(entry); + if (!mapiAddBook->GetPropertyLong(*mDirEntry, PR_OBJECT_TYPE, objectType)) { + PRINTF(("Cannot get type.\n")); + return NS_ERROR_FAILURE; + } + if (!mapiAddBook->GetPropertyUString(*mDirEntry, PR_DISPLAY_NAME_W, + unichars)) { + PRINTF(("Cannot get name.\n")); + return NS_ERROR_FAILURE; + } + + m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + mCardList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (objectType == MAPI_DISTLIST) { + m_IsMailList = true; + SetDirName(unichars); + // For a mailing list, we get all the cards into our member variable. + rv = GetCards(m_AddressList, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + } else { + m_IsMailList = false; + if (unichars.IsEmpty()) { + SetDirName(u"Outlook"_ns); + } else { + SetDirName(unichars); + } + // First, get the mailing lists, then the cards. + rv = GetNodes(m_AddressList); + NS_ENSURE_SUCCESS(rv, rv); + + rv = GetCards(mCardList, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +// nsIAbDirectory methods + +NS_IMETHODIMP nsAbOutlookDirectory::GetDirType(int32_t* aDirType) { + NS_ENSURE_ARG_POINTER(aDirType); + *aDirType = nsIAbManager::MAPI_DIRECTORY_TYPE; + 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( + nsTArray<RefPtr<nsIAbDirectory>>& aNodes) { + aNodes.Clear(); + // Mailing lists don't have childnodes. + if (m_IsMailList || !m_AddressList) { + return NS_OK; + } + + uint32_t count = 0; + nsresult rv = m_AddressList->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + aNodes.SetCapacity(count); + for (uint32_t i = 0; i < count; i++) { + nsCOMPtr<nsIAbDirectory> dir = do_QueryElementAt(m_AddressList, i, &rv); + NS_ENSURE_SUCCESS(rv, rv); + aNodes.AppendElement(&*dir); + } + return NS_OK; +} + +NS_IMETHODIMP nsAbOutlookDirectory::GetChildCardCount(uint32_t* aCount) { + nsIMutableArray* srcCards = m_IsMailList ? m_AddressList : mCardList; + return srcCards->GetLength(aCount); +} + +NS_IMETHODIMP nsAbOutlookDirectory::GetChildCards( + nsTArray<RefPtr<nsIAbCard>>& aCards) { + aCards.Clear(); + + // Not a search, so just return the appropriate list of items. + nsIMutableArray* srcCards = m_IsMailList ? m_AddressList : mCardList; + uint32_t count = 0; + nsresult rv = srcCards->GetLength(&count); + NS_ENSURE_SUCCESS(rv, rv); + aCards.SetCapacity(count); + for (uint32_t i = 0; i < count; i++) { + nsCOMPtr<nsIAbCard> card = do_QueryElementAt(srcCards, i, &rv); + NS_ENSURE_SUCCESS(rv, rv); + aCards.AppendElement(&*card); + } + return NS_OK; +} + +// This is an exact copy of nsAbOSXDirectory::HasCard(). +NS_IMETHODIMP nsAbOutlookDirectory::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; +} + +// This is an exact copy of nsAbOSXDirectory::HasDirectory(). +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; +} + +// This is an exact copy of nsAbOSXDirectory::CardForEmailAddress(). +NS_IMETHODIMP +nsAbOutlookDirectory::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; +} + +nsresult nsAbOutlookDirectory::ExtractCardEntry(nsIAbCard* aCard, + nsCString& aEntry) { + aEntry.Truncate(); + + nsCString uri; + aCard->GetPropertyAsAUTF8String("OutlookEntryURI", uri); + + // If we don't have a URI, uri will be empty. makeEntryIdFromURI doesn't set + // aEntry to anything if uri is empty, so it will be truncated, allowing us + // to accept cards not initialized by us. + makeEntryIdFromURI(kOutlookCardScheme, uri.get(), aEntry); + return NS_OK; +} + +nsresult nsAbOutlookDirectory::ExtractDirectoryEntry(nsIAbDirectory* aDirectory, + nsCString& aEntry) { + aEntry.Truncate(); + nsCString uri; + nsresult rv = aDirectory->GetURI(uri); + NS_ENSURE_SUCCESS(rv, rv); + + makeEntryIdFromURI(kOutlookDirectoryScheme, uri.get(), aEntry); + + return NS_OK; +} + +NS_IMETHODIMP nsAbOutlookDirectory::DeleteCards( + const nsTArray<RefPtr<nsIAbCard>>& aCards) { + nsresult retCode = NS_OK; + nsAbWinHelperGuard mapiAddBook; + + if (!mapiAddBook->IsOK()) { + return NS_ERROR_FAILURE; + } + + nsAutoCString cardEntryString; + nsMapiEntry cardEntry; + + for (auto card : aCards) { + retCode = ExtractCardEntry(card, cardEntryString); + if (NS_SUCCEEDED(retCode) && !cardEntryString.IsEmpty()) { + cardEntry.Assign(cardEntryString); + bool success = false; + if (m_IsMailList) { + nsAutoCString uri(mURI); + // Trim off the mailing list entry ID from the mailing list URI + // to get the top-level directory entry ID. + nsAutoCString topEntryString; + int32_t slashPos = uri.RFindChar('/'); + uri.SetLength(slashPos); + makeEntryIdFromURI(kOutlookDirectoryScheme, uri.get(), topEntryString); + nsMapiEntry topDirEntry; + topDirEntry.Assign(topEntryString); + success = + mapiAddBook->DeleteEntryfromDL(topDirEntry, *mDirEntry, cardEntry); + } else { + success = mapiAddBook->DeleteEntry(*mDirEntry, cardEntry); + } + if (!success) { + PRINTF(("Cannot delete card %s.\n", cardEntryString.get())); + } else { + if (m_IsMailList) { + // It appears that removing a card from a mailing list makes + // our list go stale, so refresh it. + m_AddressList->Clear(); + GetCards(m_AddressList, nullptr); + } else if (mCardList) { + uint32_t pos; + if (NS_SUCCEEDED(mCardList->IndexOf(0, card, &pos))) + mCardList->RemoveElementAt(pos); + } + retCode = NotifyItemDeletion(card, true); + NS_ENSURE_SUCCESS(retCode, retCode); + + card->SetDirectoryUID(EmptyCString()); + } + } else { + PRINTF(("Card doesn't belong in this directory.\n")); + } + } + return NS_OK; +} + +NS_IMETHODIMP nsAbOutlookDirectory::DeleteDirectory( + nsIAbDirectory* aDirectory) { + if (!aDirectory) { + return NS_ERROR_NULL_POINTER; + } + nsresult retCode = NS_OK; + nsAbWinHelperGuard mapiAddBook; + nsAutoCString dirEntryString; + + if (!mapiAddBook->IsOK()) { + return NS_ERROR_FAILURE; + } + retCode = ExtractDirectoryEntry(aDirectory, dirEntryString); + if (NS_SUCCEEDED(retCode) && !dirEntryString.IsEmpty()) { + nsMapiEntry directoryEntry; + + directoryEntry.Assign(dirEntryString); + if (!mapiAddBook->DeleteEntry(*mDirEntry, directoryEntry)) { + PRINTF(("Cannot delete directory %s.\n", dirEntryString.get())); + } else { + uint32_t pos; + if (m_AddressList && + NS_SUCCEEDED(m_AddressList->IndexOf(0, aDirectory, &pos))) + m_AddressList->RemoveElementAt(pos); + + // Iterate over the cards of the directory to find the one + // representing the mailing list and also remove it. + if (mCardList) { + nsAutoCString listUID; + aDirectory->GetUID(listUID); + + uint32_t nbCards = 0; + nsresult rv = mCardList->GetLength(&nbCards); + NS_ENSURE_SUCCESS(rv, rv); + for (uint32_t i = 0; i < nbCards; i++) { + nsCOMPtr<nsIAbCard> card = do_QueryElementAt(mCardList, i, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString cardUID; + rv = card->GetUID(cardUID); + NS_ENSURE_SUCCESS(rv, rv); + if (cardUID.Equals(listUID)) { + mCardList->RemoveElementAt(i); + break; + } + } + } + retCode = NotifyItemDeletion(aDirectory, false); + NS_ENSURE_SUCCESS(retCode, retCode); + } + } else { + PRINTF(("Directory doesn't belong to this folder.\n")); + } + return retCode; +} + +NS_IMETHODIMP nsAbOutlookDirectory::AddCard(nsIAbCard* aCard, + nsIAbCard** aNewCard) { + NS_ENSURE_ARG_POINTER(aCard); + NS_ENSURE_ARG_POINTER(aNewCard); + + *aNewCard = nullptr; + nsresult retCode = NS_OK; + nsAbWinHelperGuard mapiAddBook; + nsMapiEntry newEntry; + nsAutoCString cardEntryString; + bool isNewCard = false; + nsCOMPtr<nsIAbDirectory> topDir; + + if (!mapiAddBook->IsOK()) { + return NS_ERROR_FAILURE; + } + + nsAutoCString ourUID; + if (!m_IsMailList) { + // We're not dealing with a mailing list, so just create a new entry. + if (!mapiAddBook->CreateEntry(*mDirEntry, newEntry)) { + return NS_ERROR_FAILURE; + } + isNewCard = true; + GetUID(ourUID); + } else { + nsAutoCString dirURI(mURI); + // Trim off the mailing list entry ID from the mailing list URI + // to get the top-level directory entry ID. + nsAutoCString topEntryString; + int32_t slashPos = dirURI.RFindChar('/'); + dirURI.SetLength(slashPos); + makeEntryIdFromURI(kOutlookDirectoryScheme, dirURI.get(), topEntryString); + nsMapiEntry topDirEntry; + topDirEntry.Assign(topEntryString); + + // Add a card to a mailing list. We distinguish two cases: + // If there is already an Outlook card, we can just add it. + // If none exists, we need to create it first. Outlook has an option + // that allows a creation of a mailing list member solely in the list + // but we don't support this for now to avoid more MAPI complication. + retCode = ExtractCardEntry(aCard, cardEntryString); + if (NS_SUCCEEDED(retCode) && !cardEntryString.IsEmpty()) { + newEntry.Assign(cardEntryString); + } else { + if (!mapiAddBook->CreateEntry(topDirEntry, newEntry)) { + return NS_ERROR_FAILURE; + } + isNewCard = true; + } + nsAutoString display; + nsAutoString email; + aCard->GetDisplayName(display); + aCard->GetPrimaryEmail(email); + if (!mapiAddBook->AddEntryToDL(topDirEntry, *mDirEntry, newEntry, + display.get(), email.get())) { + return NS_ERROR_FAILURE; + } + // The UID of the card is the top directory's UID. + nsCOMPtr<nsIAbManager> abManager( + do_GetService("@mozilla.org/abmanager;1", &retCode)); + NS_ENSURE_SUCCESS(retCode, retCode); + retCode = abManager->GetDirectory(dirURI, getter_AddRefs(topDir)); + NS_ENSURE_SUCCESS(retCode, retCode); + topDir->GetUID(ourUID); + } + + newEntry.ToString(cardEntryString); + nsAutoCString cardURI(kOutlookCardScheme); + cardURI.Append(cardEntryString); + + nsCOMPtr<nsIAbCard> newCard; + retCode = OutlookCardForURI(cardURI, getter_AddRefs(newCard)); + NS_ENSURE_SUCCESS(retCode, retCode); + + // Make sure the card has a UID before setting its directory UID. + // This is a bit of a hack. If we get the UID of the card before setting its + // directory UID, we can avoid an unwanted `ModifyCard()` call inside + // `nsAbCardProperty::SetUID()`. + nsCString dummy; + newCard->GetUID(dummy); + newCard->SetDirectoryUID(ourUID); + + if (isNewCard) { + retCode = newCard->Copy(aCard); + NS_ENSURE_SUCCESS(retCode, retCode); + + // Set a decent display name of the card. This needs to be set + // on the card and not on the related contact via `SetPropertiesUString()`. + nsAutoString displayName; + newCard->GetDisplayName(displayName); + mapiAddBook->SetPropertyUString(newEntry, PR_DISPLAY_NAME_W, + displayName.get()); + + if (m_IsMailList) { + // Observed behavior for a new card in a mailing list is that + // Outlook returns __MailUser__ as first name. That value was + // previously set as display name when creating the bare card. + nsAutoString firstName; + newCard->GetFirstName(firstName); + if (StringBeginsWith(firstName, + NS_LITERAL_STRING_FROM_CSTRING(kDummyDisplayName))) { + newCard->SetFirstName(EmptyString()); + } + } + + retCode = ModifyCardInternal(newCard, true); + NS_ENSURE_SUCCESS(retCode, retCode); + } + + if (m_IsMailList) { + m_AddressList->AppendElement(newCard); + if (isNewCard) { + // Add the new card to the cards of the top directory as well. + nsAbOutlookDirectory* topDirOL = + static_cast<nsAbOutlookDirectory*>(topDir.get()); + topDirOL->mCardList->AppendElement(newCard); + } + } else { + mCardList->AppendElement(newCard); + } + + NotifyItemAddition(newCard, true); + + newCard.forget(aNewCard); + return NS_OK; +} + +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) { + NS_ENSURE_ARG_POINTER(aMailList); + NS_ENSURE_ARG_POINTER(addedList); + if (m_IsMailList) return NS_OK; + + nsAbWinHelperGuard mapiAddBook; + nsMapiEntry newEntry; + nsAutoCString newEntryString; + + if (!mapiAddBook->IsOK()) return NS_ERROR_FAILURE; + + nsAutoString name; + nsresult rv = aMailList->GetDirName(name); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mapiAddBook->CreateDistList(*mDirEntry, newEntry, name.get())) + return NS_ERROR_FAILURE; + + newEntry.ToString(newEntryString); + nsAutoCString uri(mURI); + nsAutoCString topEntryString; + makeEntryIdFromURI(kOutlookDirectoryScheme, uri.get(), topEntryString); + uri.Append('/'); + uri.Append(newEntryString); + + RefPtr<nsAbOutlookDirectory> directory = new nsAbOutlookDirectory; + + // We will later need the URI of the parent directory, so store it here. + directory->mParentEntryId = topEntryString; + + // Light-weight initialisation. `nsAbOutlookDirectory::Init()` will get + // the object type wrong since we don't have cards yet and scan for cards + // which we don't have yet. + rv = directory->nsAbDirProperty::Init(uri.get()); + NS_ENSURE_SUCCESS(rv, rv); + directory->mDirEntry->Assign(newEntryString); + directory->m_IsMailList = true; + directory->m_AddressList = do_CreateInstance(NS_ARRAY_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIAbDirectory> newList = do_QueryObject(directory); + + rv = newList->CopyMailList(aMailList); + NS_ENSURE_SUCCESS(rv, rv); + + // Also create a card to match the list. + // That needs to happen before the notification. + nsAutoCString cardURI(kOutlookCardScheme); + cardURI.Append(newEntryString); + nsCOMPtr<nsIAbCard> newCard = + do_CreateInstance("@mozilla.org/addressbook/cardproperty;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + newCard->SetPropertyAsAUTF8String("OutlookEntryURI", cardURI); + + // This is a bit of a hack. If we set the UID of the card before setting its + // directory UID, we can avoid an unwanted `ModifyCard()` call inside + // `nsAbCardProperty::SetUID()`. + nsAutoCString listUID; + newList->GetUID(listUID); + newCard->SetUID(listUID); + nsAutoCString ourUID; + GetUID(ourUID); + newCard->SetDirectoryUID(ourUID); + newCard->SetIsMailList(true); + newCard->SetMailListURI(uri.get()); + newCard->SetDisplayName(name); + newCard->SetLastName(name); + + mCardList->AppendElement(newCard); + m_AddressList->AppendElement(newList); + + NotifyItemAddition(newList, false); + + newList.forget(addedList); + return rv; +} + +NS_IMETHODIMP nsAbOutlookDirectory::EditMailListToDatabase( + nsIAbCard* listCard) { + nsresult rv; + nsString name; + nsAbWinHelperGuard mapiAddBook; + + if (!mapiAddBook->IsOK()) return NS_ERROR_FAILURE; + + rv = GetDirName(name); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mapiAddBook->SetPropertyUString(*mDirEntry, PR_DISPLAY_NAME_W, + name.get())) + return NS_ERROR_FAILURE; + + // Iterate over the cards of the parent directory to find the one + // representing the mailing list and also change its name. + nsAutoCString uri(mURI); + // Trim off the mailing list entry ID from the mailing list URI + // to get the top-level directory entry ID. + nsAutoCString topEntryString; + int32_t slashPos = uri.RFindChar('/'); + uri.SetLength(slashPos); + nsCOMPtr<nsIAbManager> abManager( + do_GetService("@mozilla.org/abmanager;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIAbDirectory> parent; + rv = abManager->GetDirectory(uri, getter_AddRefs(parent)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString listUID; + GetUID(listUID); + + uint32_t nbCards = 0; + nsAbOutlookDirectory* olDir = + static_cast<nsAbOutlookDirectory*>(parent.get()); + rv = olDir->mCardList->GetLength(&nbCards); + NS_ENSURE_SUCCESS(rv, rv); + for (uint32_t i = 0; i < nbCards; i++) { + nsCOMPtr<nsIAbCard> card = do_QueryElementAt(olDir->mCardList, i, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString cardUID; + rv = card->GetUID(cardUID); + NS_ENSURE_SUCCESS(rv, rv); + if (cardUID.Equals(listUID)) { + card->SetDisplayName(name); + break; + } + } + + nsAutoCString dirUID; + if (listCard) { + // For mailing list cards, we use the UID of the top level directory. + listCard->GetDirectoryUID(dirUID); + NotifyItemModification(listCard, true, dirUID.get()); + } + nsCOMPtr<nsIAbDirectory> dir = do_QueryObject(this); + // Use the UID of the parent. + parent->GetUID(dirUID); + NotifyItemModification(dir, false, dirUID.get()); + return NS_OK; +} + +static nsresult FindPrimaryEmailCondition(nsIAbBooleanExpression* aLevel, + nsAString& value) { + if (!aLevel) { + return NS_ERROR_NULL_POINTER; + } + nsresult retCode = NS_OK; + nsTArray<RefPtr<nsISupports>> expressions; + + retCode = aLevel->GetExpressions(expressions); + NS_ENSURE_SUCCESS(retCode, retCode); + + for (uint32_t i = 0; i < expressions.Length(); ++i) { + RefPtr<nsIAbBooleanConditionString> condition = + do_QueryObject(expressions[i], &retCode); + if (NS_SUCCEEDED(retCode)) { + nsCString name; + retCode = condition->GetName(getter_Copies(name)); + NS_ENSURE_SUCCESS(retCode, retCode); + if (name.EqualsLiteral("PrimaryEmail")) { + // We found a leaf in the boolean expression tree that compares + // "PrimaryEmail". So return the value and be done. + retCode = condition->GetValue(getter_Copies(value)); + return retCode; + } + continue; + } + + RefPtr<nsIAbBooleanExpression> subExpression = + do_QueryObject(expressions[i], &retCode); + if (NS_SUCCEEDED(retCode)) { + // Recurse into the sub-tree. + retCode = FindPrimaryEmailCondition(subExpression, value); + // If we found our leaf there, we're done. + if (NS_SUCCEEDED(retCode)) return retCode; + } + } + return NS_ERROR_UNEXPECTED; +} + +static nsresult GetConditionValue(nsIAbDirectoryQueryArguments* aArguments, + nsAString& value) { + 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); + + // Outlook can only query the PR_ANR property. So get its value from the + // PrimaryEmail condition. + retCode = FindPrimaryEmailCondition(booleanQuery, value); + return retCode; +} + +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; + } + + // The only thing we can search here is PR_ANR. All other properties are + // skipped. Note that PR_ANR also searches in the recipient's name and + // e-mail address. + // https://docs.microsoft.com/en-us/office/client-developer/outlook/mapi/address-book-restrictions + // states: + // Ambiguous name restrictions are property restrictions using the PR_ANR + // property to match recipient names with entries in address book containers. + + SRestriction restriction; + SPropValue val; + restriction.rt = RES_PROPERTY; + restriction.res.resProperty.relop = RELOP_EQ; + restriction.res.resProperty.ulPropTag = PR_ANR_W; + restriction.res.resProperty.lpProp = &val; + restriction.res.resProperty.lpProp->ulPropTag = PR_ANR_W; + + nsAutoString value; + nsresult rv = GetConditionValue(aArguments, value); + NS_ENSURE_SUCCESS(rv, rv); + restriction.res.resProperty.lpProp->Value.lpszW = value.get(); + + rv = ExecuteQuery(&restriction, aListener, aResultLimit); + NS_ENSURE_SUCCESS(rv, rv); + + *aReturnValue = ++mCurrentQueryId; + + return NS_OK; +} + +NS_IMETHODIMP nsAbOutlookDirectory::StopQuery(int32_t aContext) { + return NS_OK; +} + +NS_IMETHODIMP nsAbOutlookDirectory::Search(const nsAString& query, + const nsAString& searchString, + nsIAbDirSearchListener* listener) { + nsresult retCode = NS_OK; + + // Note the following: We get a rather complicated query passed here from + // preference mail.addr_book.quicksearchquery.format. + // Outlook address book search only allows search by PR_ANR, which is a fuzzy + // Ambiguous Name Restriction search. + + retCode = StopSearch(); + NS_ENSURE_SUCCESS(retCode, retCode); + + nsCOMPtr<nsIAbBooleanExpression> expression; + + nsCOMPtr<nsIAbDirectoryQueryArguments> arguments = do_CreateInstance( + "@mozilla.org/addressbook/directory/query-arguments;1", &retCode); + NS_ENSURE_SUCCESS(retCode, retCode); + + retCode = nsAbQueryStringToExpression::Convert(NS_ConvertUTF16toUTF8(query), + 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, listener, -1, 0, &mSearchContext); +} + +nsresult nsAbOutlookDirectory::StopSearch(void) { + return StopQuery(mSearchContext); +} + +// nsIAbDirSearchListener +NS_IMETHODIMP nsAbOutlookDirectory::OnSearchFinished( + nsresult status, bool complete, nsITransportSecurityInfo* secInfo, + nsACString const& location) { + return NS_OK; +} + +NS_IMETHODIMP nsAbOutlookDirectory::OnSearchFoundCard(nsIAbCard* aCard) { + mCardList->AppendElement(aCard); + return NS_OK; +} + +nsresult nsAbOutlookDirectory::ExecuteQuery(SRestriction* aRestriction, + nsIAbDirSearchListener* aListener, + int32_t aResultLimit) + +{ + 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 = GetCards(resultsArray, 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); + } + + aListener->OnSearchFinished(NS_OK, true, nullptr, ""_ns); + return retCode; +} + +// This function expects the aCards array to already be created. +nsresult nsAbOutlookDirectory::GetCards(nsIMutableArray* aCards, + SRestriction* aRestriction) { + nsAbWinHelperGuard mapiAddBook; + + if (!mapiAddBook->IsOK()) return NS_ERROR_FAILURE; + + nsMapiEntryArray cardEntries; + LPSRestriction restriction = (LPSRestriction)aRestriction; + + if (!mapiAddBook->GetCards(*mDirEntry, restriction, cardEntries)) { + PRINTF(("Cannot get cards.\n")); + return NS_ERROR_FAILURE; + } + + nsresult rv; + nsAutoCString ourUID; + if (m_IsMailList) { + // Look up the parent directory (top-level directory) in the + // AddrBookManager. That relies on the fact that the top-level + // directory is already in its map before being initialised. + nsCOMPtr<nsIAbManager> abManager( + do_GetService("@mozilla.org/abmanager;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + nsAutoCString dirURI(kOutlookDirectoryScheme); + dirURI.Append(mParentEntryId); + nsCOMPtr<nsIAbDirectory> owningDir; + rv = abManager->GetDirectory(dirURI, getter_AddRefs(owningDir)); + NS_ENSURE_SUCCESS(rv, rv); + owningDir->GetUID(ourUID); + } else { + GetUID(ourUID); + } + + rv = NS_OK; + + for (ULONG card = 0; card < cardEntries.mNbEntries; ++card) { + nsAutoCString cardEntryString; + nsAutoCString cardURI(kOutlookCardScheme); + nsCOMPtr<nsIAbCard> childCard; + cardEntries.mEntries[card].ToString(cardEntryString); + cardURI.Append(cardEntryString); + + rv = OutlookCardForURI(cardURI, getter_AddRefs(childCard)); + NS_ENSURE_SUCCESS(rv, rv); + // Make sure the card has a UID before setting its directory UID. + // This is a bit of a hack. If we get the UID of the card before setting its + // directory UID, we can avoid an unwanted `ModifyCard()` call inside + // `nsAbCardProperty::SetUID()`. + nsCString dummy; + childCard->GetUID(dummy); + childCard->SetDirectoryUID(ourUID); + + aCards->AppendElement(childCard); + } + return rv; +} + +nsresult nsAbOutlookDirectory::GetNodes(nsIMutableArray* aNodes) { + NS_ENSURE_ARG_POINTER(aNodes); + + nsAbWinHelperGuard mapiAddBook; + nsMapiEntryArray nodeEntries; + + if (!mapiAddBook->IsOK()) return NS_ERROR_FAILURE; + + if (!mapiAddBook->GetNodes(*mDirEntry, nodeEntries)) { + PRINTF(("Cannot get nodes.\n")); + return NS_ERROR_FAILURE; + } + + nsresult rv = NS_OK; + + nsCOMPtr<nsIAbManager> abManager( + do_GetService("@mozilla.org/abmanager;1", &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCString topEntryString; + mDirEntry->ToString(topEntryString); + + for (ULONG node = 0; node < nodeEntries.mNbEntries; ++node) { + nsAutoCString dirEntryString; + nsAutoCString uri(kOutlookDirectoryScheme); + uri.Append(topEntryString); + uri.Append('/'); + nodeEntries.mEntries[node].ToString(dirEntryString); + uri.Append(dirEntryString); + + RefPtr<nsAbOutlookDirectory> directory = new nsAbOutlookDirectory; + + // We will later need the URI of the parent directory, so store it here. + directory->mParentEntryId = topEntryString; + directory->Init(uri.get()); + + nsCOMPtr<nsIAbDirectory> dir = do_QueryObject(directory); + aNodes->AppendElement(dir); + } + return rv; +} + +nsresult nsAbOutlookDirectory::commonNotification( + nsISupports* aItem, const char* aTopic, const char* aNotificationUID) { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + + // `dirUID` needs to stay in scope until the end of the function. + nsAutoCString dirUID; + if (!aNotificationUID) { + // Use the UID of the directory. + GetUID(dirUID); + aNotificationUID = dirUID.get(); + } + + observerService->NotifyObservers( + aItem, aTopic, NS_ConvertUTF8toUTF16(aNotificationUID).get()); + return NS_OK; +} + +nsresult nsAbOutlookDirectory::NotifyItemDeletion( + nsISupports* aItem, bool aIsCard, const char* aNotificationUID) { + const char* topic; + if (aIsCard) { + topic = m_IsMailList ? "addrbook-list-member-removed" + : "addrbook-contact-deleted"; + } else { + topic = "addrbook-list-deleted"; + } + return commonNotification(aItem, topic, aNotificationUID); +} + +nsresult nsAbOutlookDirectory::NotifyItemAddition( + nsISupports* aItem, bool aIsCard, const char* aNotificationUID) { + const char* topic; + if (aIsCard) { + topic = m_IsMailList ? "addrbook-list-member-added" + : "addrbook-contact-created"; + } else { + topic = "addrbook-list-created"; + } + return commonNotification(aItem, topic, aNotificationUID); +} + +nsresult nsAbOutlookDirectory::NotifyItemModification( + nsISupports* aItem, bool aIsCard, const char* aNotificationUID) { + return commonNotification( + aItem, aIsCard ? "addrbook-contact-updated" : "addrbook-list-updated", + aNotificationUID); +} + +nsresult nsAbOutlookDirectory::NotifyCardPropertyChanges(nsIAbCard* aOld, + nsIAbCard* aNew) { + mozilla::JSONStringWriteFunc<nsCString> jsonString; + mozilla::JSONWriter w(jsonString); + w.Start(); + w.StartObjectElement(); + bool somethingChanged = false; + for (uint32_t i = 0; i < sizeof(CardStringProperties) / sizeof(char*); i++) { + nsAutoCString oldValue; + nsAutoCString newValue; + aOld->GetPropertyAsAUTF8String(CardStringProperties[i], oldValue); + aNew->GetPropertyAsAUTF8String(CardStringProperties[i], newValue); + + if (!oldValue.Equals(newValue)) { + somethingChanged = true; + w.StartObjectProperty(mozilla::MakeStringSpan(CardStringProperties[i])); + if (oldValue.IsEmpty()) { + w.NullProperty("oldValue"); + } else { + w.StringProperty("oldValue", mozilla::MakeStringSpan(oldValue.get())); + } + if (newValue.IsEmpty()) { + w.NullProperty("newValue"); + } else { + w.StringProperty("newValue", mozilla::MakeStringSpan(newValue.get())); + } + w.EndObject(); + } + } + + for (uint32_t i = 0; i < sizeof(CardIntProperties) / sizeof(char*); i++) { + uint32_t oldValue = 0; + uint32_t newValue = 0; + aOld->GetPropertyAsUint32(CardIntProperties[i], &oldValue); + aNew->GetPropertyAsUint32(CardIntProperties[i], &newValue); + + if (oldValue != newValue) { + somethingChanged = true; + w.StartObjectProperty(mozilla::MakeStringSpan(CardIntProperties[i])); + if (oldValue == 0) { + w.NullProperty("oldValue"); + } else { + w.IntProperty("oldValue", oldValue); + } + if (newValue == 0) { + w.NullProperty("newValue"); + } else { + w.IntProperty("newValue", newValue); + } + w.EndObject(); + } + } + w.EndObject(); + w.End(); + +#if PRINT_TO_CONSOLE + printf("%s", jsonString.StringCRef().get()); +#endif + + if (somethingChanged) { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + observerService->NotifyObservers( + aNew, "addrbook-contact-properties-updated", + NS_ConvertUTF8toUTF16(jsonString.StringCRef()).get()); + } + return NS_OK; +} + +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", (wchar_t*)(unichar.get()), + errorCode)); + } +} + +#define PREF_MAIL_ADDR_BOOK_LASTNAMEFIRST "mail.addr_book.lastnamefirst" + +NS_IMETHODIMP nsAbOutlookDirectory::ModifyCard(nsIAbCard* aModifiedCard) { + return ModifyCardInternal(aModifiedCard, false); +} + +nsresult nsAbOutlookDirectory::ModifyCardInternal(nsIAbCard* aModifiedCard, + bool aIsAddition) { + NS_ENSURE_ARG_POINTER(aModifiedCard); + + nsString* properties = nullptr; + nsAutoString utility; + nsAbWinHelperGuard mapiAddBook; + + if (!mapiAddBook->IsOK()) return NS_ERROR_FAILURE; + + nsCString cardEntryString; + nsresult retCode = ExtractCardEntry(aModifiedCard, cardEntryString); + NS_ENSURE_SUCCESS(retCode, retCode); + // If we don't have the card entry, we can't work. + if (cardEntryString.IsEmpty()) return NS_ERROR_FAILURE; + + nsMapiEntry cardEntry; + cardEntry.Assign(cardEntryString); + + // Get the existing card. + nsCString uri; + nsCOMPtr<nsIAbCard> oldCard; + aModifiedCard->GetPropertyAsAUTF8String("OutlookEntryURI", uri); + // If the following fails, we didn't get the old card, not fatal. + OutlookCardForURI(uri, getter_AddRefs(oldCard)); + + // 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]); + } + } + + nsMapiEntry dirEntry; + if (m_IsMailList) { + nsAutoCString uri(mURI); + // Trim off the mailing list entry ID from the mailing list URI + // to get the top-level directory entry ID. + nsAutoCString topEntryString; + int32_t slashPos = uri.RFindChar('/'); + uri.SetLength(slashPos); + makeEntryIdFromURI(kOutlookDirectoryScheme, uri.get(), topEntryString); + dirEntry.Assign(topEntryString); + } else { + dirEntry.Assign(mDirEntry->mByteCount, mDirEntry->mEntryId); + } + + aModifiedCard->SetDisplayName(properties[index_DisplayName]); + 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_Notes]); + if (!mapiAddBook->SetPropertiesUString(dirEntry, cardEntry, + 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->GetPrimaryEmail(unichar); + if (!mapiAddBook->SetPropertyUString(cardEntry, PR_EMAIL_ADDRESS_W, + unichar.get())) { + PRINTF(("Cannot set primary email.\n")); + } + 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(cardEntry, 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(cardEntry, 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(dirEntry, cardEntry, true, PR_BIRTHDAY, + year, month, day)) { + PRINTF(("Cannot set date.\n")); + } + + if (!aIsAddition) { + NotifyItemModification(aModifiedCard, true); + if (oldCard) NotifyCardPropertyChanges(oldCard, aModifiedCard); + } + + return retCode; +} + +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; + } + int32_t offsetCR = aSource.FindChar('\r'); + aSource.SetLength(offsetCR >= 0 ? offsetCR : offset); + } +} + +nsresult nsAbOutlookDirectory::OutlookCardForURI(const nsACString& aUri, + nsIAbCard** newCard) { + NS_ENSURE_ARG_POINTER(newCard); + + nsAutoCString cardEntryString; + makeEntryIdFromURI(kOutlookCardScheme, PromiseFlatCString(aUri).get(), + cardEntryString); + + nsAbWinHelperGuard mapiAddBook; + if (!mapiAddBook->IsOK()) return NS_ERROR_FAILURE; + + nsresult rv; + nsCOMPtr<nsIAbCard> card = + do_CreateInstance("@mozilla.org/addressbook/cardproperty;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + card->SetPropertyAsAUTF8String("OutlookEntryURI", aUri); + + nsMapiEntry cardEntry; + cardEntry.Assign(cardEntryString); + + nsString unichars[index_LastProp]; + bool success[index_LastProp]; + + nsMapiEntry dirEntry; + if (m_IsMailList) { + nsAutoCString uri(mURI); + // Trim off the mailing list entry ID from the mailing list URI + // to get the top-level directory entry ID. + nsAutoCString topEntryString; + int32_t slashPos = uri.RFindChar('/'); + uri.SetLength(slashPos); + makeEntryIdFromURI(kOutlookDirectoryScheme, uri.get(), topEntryString); + dirEntry.Assign(topEntryString); + } else { + dirEntry.Assign(mDirEntry->mByteCount, mDirEntry->mEntryId); + } + + if (mapiAddBook->GetPropertiesUString(dirEntry, cardEntry, + OutlookCardMAPIProps, index_LastProp, + unichars, success)) { + if (success[index_FirstName]) card->SetFirstName(unichars[index_FirstName]); + if (success[index_LastName]) card->SetLastName(unichars[index_LastName]); + if (success[index_DisplayName]) + card->SetDisplayName(unichars[index_DisplayName]); + +#define SETPROP(name, index) \ + if (success[index]) card->SetPropertyAsAString(name, unichars[index]) + SETPROP(kNicknameProperty, index_NickName); + SETPROP(kWorkPhoneProperty, index_WorkPhoneNumber); + SETPROP(kHomePhoneProperty, index_HomePhoneNumber); + SETPROP(kFaxProperty, index_WorkFaxNumber); + SETPROP(kPagerProperty, index_PagerNumber); + SETPROP(kCellularProperty, index_MobileNumber); + SETPROP(kHomeCityProperty, index_HomeCity); + SETPROP(kHomeStateProperty, index_HomeState); + SETPROP(kHomeZipCodeProperty, index_HomeZip); + SETPROP(kHomeCountryProperty, index_HomeCountry); + SETPROP(kWorkCityProperty, index_WorkCity); + SETPROP(kWorkStateProperty, index_WorkState); + SETPROP(kWorkZipCodeProperty, index_WorkZip); + SETPROP(kWorkCountryProperty, index_WorkCountry); + SETPROP(kJobTitleProperty, index_JobTitle); + SETPROP(kDepartmentProperty, index_Department); + SETPROP(kCompanyProperty, index_Company); + SETPROP(kWorkWebPageProperty, index_WorkWebPage); + SETPROP(kHomeWebPageProperty, index_HomeWebPage); + SETPROP(kNotesProperty, index_Notes); + } + + ULONG cardType = 0; + if (mapiAddBook->GetPropertyLong(cardEntry, PR_OBJECT_TYPE, cardType)) { + card->SetIsMailList(cardType == MAPI_DISTLIST); + if (cardType == MAPI_DISTLIST) { + nsCString dirEntryString; + mDirEntry->ToString(dirEntryString); + nsAutoCString uri(kOutlookDirectoryScheme); + uri.Append(dirEntryString); + uri.Append('/'); + nsCString originalUID; + AlignListEntryStringAndGetUID(cardEntryString, originalUID); + uri.Append(cardEntryString); + card->SetMailListURI(uri.get()); + if (!originalUID.IsEmpty()) card->SetUID(originalUID); + + // In case the display is by "First Last" or "Last, First", give the card + // a name, otherwise nothing is displayed. + if (success[index_DisplayName]) + card->SetLastName(unichars[index_DisplayName]); + } + } + + nsAutoString unichar; + nsAutoString unicharBis; + if (mapiAddBook->GetPropertyUString(cardEntry, PR_EMAIL_ADDRESS_W, unichar)) { + card->SetPrimaryEmail(unichar); + } + if (mapiAddBook->GetPropertyUString(cardEntry, PR_HOME_ADDRESS_STREET_W, + unichar)) { + splitString(unichar, unicharBis); + card->SetPropertyAsAString(kHomeAddressProperty, unichar); + card->SetPropertyAsAString(kHomeAddress2Property, unicharBis); + } + if (mapiAddBook->GetPropertyUString(cardEntry, 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(dirEntry, cardEntry, true, PR_BIRTHDAY, year, + month, day)) { + card->SetPropertyAsUint32(kBirthYearProperty, year); + card->SetPropertyAsUint32(kBirthMonthProperty, month); + card->SetPropertyAsUint32(kBirthDayProperty, day); + } + + card.forget(newCard); + return NS_OK; +} + +void nsAbOutlookDirectory::AlignListEntryStringAndGetUID( + nsCString& aEntryString, nsCString& aOriginalUID) { + // Sadly when scanning for cards and finding a distribution list, the + // entry ID is different to the entry ID returned when scanning the top level + // directory for distribution lists. We make the adjustment here. + // We also retrieve the original UID from the mailing list. + nsAbWinHelperGuard mapiAddBook; + if (!mapiAddBook->IsOK()) return; + + uint32_t nbLists = 0; + nsresult rv = m_AddressList->GetLength(&nbLists); + NS_ENSURE_SUCCESS_VOID(rv); + for (uint32_t i = 0; i < nbLists; i++) { + nsCOMPtr<nsIAbDirectory> list = do_QueryElementAt(m_AddressList, i, &rv); + NS_ENSURE_SUCCESS_VOID(rv); + + // Get URI and extract entry ID. + nsAutoCString listURI; + list->GetURI(listURI); + int ind = listURI.RFindChar('/'); + listURI = Substring(listURI, ind + 1); + + if (aEntryString.Equals(listURI)) { + list->GetUID(aOriginalUID); + return; + } + if (mapiAddBook->CompareEntryIDs(aEntryString, listURI)) { + PRINTF(("Entry ID for mailing list replaced:\nWas: %s\nNow: %s\n", + aEntryString.get(), listURI.get())); + aEntryString = listURI; + list->GetUID(aOriginalUID); + return; + } + } + PRINTF(("Entry ID for mailing list not found.\n")); +} |