/* -*- 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>& 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 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>& 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 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 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>& 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 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 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 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 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(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 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 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 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 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 abManager( do_GetService("@mozilla.org/abmanager;1", &rv)); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr 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(parent.get()); rv = olDir->mCardList->GetLength(&nbCards); NS_ENSURE_SUCCESS(rv, rv); for (uint32_t i = 0; i < nbCards; i++) { nsCOMPtr 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 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> expressions; retCode = aLevel->GetExpressions(expressions); NS_ENSURE_SUCCESS(retCode, retCode); for (uint32_t i = 0; i < expressions.Length(); ++i) { RefPtr 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 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 supports; retCode = aArguments->GetExpression(getter_AddRefs(supports)); NS_ENSURE_SUCCESS(retCode, retCode); nsCOMPtr 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 expression; nsCOMPtr 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 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(aResultLimit)) { nbResults = static_cast(aResultLimit); } uint32_t i = 0; nsCOMPtr 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 abManager( do_GetService("@mozilla.org/abmanager;1", &rv)); NS_ENSURE_SUCCESS(rv, rv); nsAutoCString dirURI(kOutlookDirectoryScheme); dirURI.Append(mParentEntryId); nsCOMPtr 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 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 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 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 dir = do_QueryObject(directory); aNodes->AppendElement(dir); } return rv; } nsresult nsAbOutlookDirectory::commonNotification( nsISupports* aItem, const char* aTopic, const char* aNotificationUID) { nsCOMPtr 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 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 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(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 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 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 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 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")); }