/* -*- 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 "nsAbAddressCollector.h" #include "nsIPrefService.h" #include "nsIPrefBranch.h" #include "nsString.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 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 nsAbAddressCollector::GetCardForAddress( const char* aProperty, const nsACString& aEmailAddress, nsIAbDirectory** aDirectory) { nsresult rv; nsCOMPtr abManager( do_GetService("@mozilla.org/abmanager;1", &rv)); NS_ENSURE_SUCCESS(rv, nullptr); nsTArray> directories; rv = abManager->GetDirectories(directories); NS_ENSURE_SUCCESS(rv, nullptr); nsCOMPtr result; uint32_t count = directories.Length(); for (uint32_t i = 0; i < count; i++) { // Some implementations may return NS_ERROR_NOT_IMPLEMENTED here, // so just catch the value and continue. if (NS_FAILED(directories[i]->GetCardFromProperty( aProperty, aEmailAddress, false, getter_AddRefs(result)))) { continue; } if (result) { if (aDirectory) directories[i].forget(aDirectory); return result.forget(); } } return nullptr; } NS_IMETHODIMP nsAbAddressCollector::CollectAddress(const nsACString& aAddresses, bool aCreateCard) { // 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 names; nsTArray 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, false); } return NS_OK; } NS_IMETHODIMP nsAbAddressCollector::CollectSingleAddress(const nsACString& aEmail, const nsACString& aDisplayName, bool aCreateCard, bool aSkipCheckExisting) { if (!mDirectory) return NS_OK; nsresult rv; nsCOMPtr originDirectory; nsCOMPtr card; if (!aSkipCheckExisting) { card = GetCardForAddress(kPriEmailProperty, aEmail, getter_AddRefs(originDirectory)); // If a card has aEmail, but it's the secondary address, we don't want to // update any properties, so just return. if (!card) { card = GetCardForAddress(k2ndEmailProperty, aEmail, getter_AddRefs(originDirectory)); if (card) return NS_OK; } } if (!card && (aCreateCard || aSkipCheckExisting)) { card = do_CreateInstance("@mozilla.org/addressbook/cardproperty;1", &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)))) { nsCOMPtr 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 (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.EqualsLiteral("aol.com") || domain.EqualsLiteral("cs.com") || domain.EqualsLiteral("netscape.net")) aCard->SetPropertyAsAUTF8String(kScreenNameProperty, Substring(aEmail, 0, atPos)); else if (domain.EqualsLiteral("gmail.com") || domain.EqualsLiteral("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 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 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, abURI); if (abURI.IsEmpty()) abURI.AssignLiteral(kPersonalAddressbookUri); if (abURI == mABURI) return; mDirectory = nullptr; mABURI = abURI; nsresult rv; nsCOMPtr abManager( do_GetService("@mozilla.org/abmanager;1", &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; } }